165-物联网开发实战

开篇词 | 物联网这个趋势，你不应该错过

你好，我是郭朝斌。

很高兴能有机会和你聊聊物联网，因为我觉得，这是未来的一个趋势，即使你不在这个行业，了解一下也可能会对自己有用。

可能你已经感受到了，我们身边越来越多的硬件设备正在被嵌入芯片、注入软件，从而实现各种各样的新应用、新功能，比如智能门锁。

以前我们出门的时候，经常说要带好四样东西，“伸手要钱”，也就是身份证、手机、钥匙和钱包。随着智能手机的发展，我们现在出门已经不需要带钱包了，拿着手机扫二维码，就可以买东西。而智能门锁则让我们省去了带钥匙的麻烦，你输入密码或者指纹就可以开门，甚至还可以远程生成临时密码给别人开门。

比方说你在外面休假，然后你的亲戚临时要到你家里取东西，你完全可以生成一个临时密码发给他，这样他就可以进去了。

这些东西，一开始的时候我们都感到很新奇，但是一段时间之后就觉得很自然、很平常了，甚至还会觉得如果离开它们，生活反而会出现问题。

这一切，正是物联网这一技术趋势的体现。

下一个技术风口

所谓物联网，其实就是借助互联网的力量，实现万物互联。

互联网本质上已经实现了人与人、人与信息的连接。那么接下来，人体自身，比如你的各项生理指标；人使用的各种物品，比如水杯、桌子和椅子；甚至人所处的周围环境，例如卧室的温度、湿度和甲醛含量，都会被接入互联网，实现人与物、物与物的连接。

所以说，物联网是互联网发展的必然趋势。

这种万物互联的愿景和趋势，无疑会深刻改变我们的生活，重塑农业、制造业、交通、物流、零售和家居等各个领域。

Gartner 是一个美国的技术咨询公司，每年都会发布一些技术趋势分析报告。它有一个工具叫技术成熟度曲线。这个工具会把每一个技术发展的过程分成 5 个阶段：

一开始，技术处于萌芽期；

然后，它会迅速到达一个最高点，也就是期望膨胀期；

紧接着，这种技术就会进入泡沫破裂低谷期，有些技术在这个阶段已经被验证为不符合实际需求，就淘汰掉了；

但是还有一些技术，会进入到稳步爬升恢复期；

最后，技术进入到生产成熟期。

现在我们来看一下物联网。Gartner 曾经在 2015 年发布过一个报告，说物联网处于期望膨胀的巅峰；到了 2019 年，我们再找到它新发布的报告，就会看到智能家居、工业物联网和智能楼宇这些细分的领域，正在从恢复期走向成熟期。我在这里放了一张图，你可以去参考。

Gartner 2019年物联网技术成熟曲线（改自Gartner官网）

大公司向来对于技术趋势非常敏感，一方面它们要保持领先，另一方面它们要防止出现颠覆者。我们看看国内的一线技术公司：硬件厂商，如华为、小米；云计算企业和互联网巨头，比如阿里云、腾讯云等，已经纷纷投入大量的人力、资金布局物联网行业。

面对技术的潮起潮落，吴军博士曾说：“我们所能做的就是尽量把握每一次技术大潮。”

公司如此，作为技术人也是这样，顺应技术趋势发展，才能成为时代的弄潮儿。记得在移动互联网刚起步的阶段，大概 2013 年，我还在诺基亚从事手机相关的开发工作。我有一位同事在那个时候就选择离开诺基亚，加入了 Uber 成为中国总部早期成员，后来又去了另一家打车软件公司担任部门总监，实现了能力和职位的火箭式提升。

也许你也可以像我的这位前同事一样，抓住一个行业的机会，实现自身价值的提升。因为我很看好物联网这个技术趋势，在我看来，物联网就是这样一个机会。它的影响力正在变得越来越大，现在很火热的新基建和数字化转型都离不开它，所以它将让很多人进入职业发展的快车道。

物联网为什么不好懂？

如果你主动了解过物联网相关的知识，很可能觉得它们不太好掌握。而且你也会发现，网络上的很多相关资料，大多数内容不成体系，学起来往往不得要领。

这是为什么呢？因为物联网涉及的知识非常庞杂，包括通信技术、嵌入式开发、互联网系统、大数据、人工智能等等，甚至还有相关应用领域的专业知识。比如，你做交通物流方面的应用开发，就得了解交通物流的知识。

这些知识可以说是“盘根错节”地交织在一起，要求我们得先具备抽丝剥茧、理清思路的能力，去看清楚这些技术的作用和相互之间的关系。只有这样，我们才能在物联网这个领域获得快速的成长。

因此，在这门课中，我会为你梳理清楚物联网领域庞杂的专业知识，将“庞杂”转变为“条理”，并深入剖析其中的核心技术，最终帮你建立起一个完整的知识体系。

这个完整的知识体系，可以确保你在任何时候都不会迷路。这样，你对工作中的很多问题就会看得更加清楚，能够逐渐建立架构师的思维能力，为职场中的提升做好“内功”的准备。

为什么我能为你讲清楚？

你可能想问，既然物联网知识这么难掌握，为什么我可以讲清楚呢？

其实我也一直有这样的疑惑：我常常觉得自己对于物联网有天然的熟悉感，但是不知道为什么。直到有一次整理简历，我发现这跟我的经历有很大的关系。

我非常喜欢机器人，研究生时期主要做的研究，就是国自然（国家自然科学基金）资助的机器人相关的课题。我们几个同学还与清华的一位同学组队，代表清华大学参加了 RoboCup 机器人竞赛，之后我们又代表自己的学校参加了 FIRA 机器人竞赛，并且获得了几个奖项。

参加工作后，我在诺基亚做过移动通信、蓝牙等通信技术的相关开发；研究过手机操作系统的底层架构；移植、开发过 FireFoxOS 这类理念非常超前的手机操作系统；做过比 Apple Watch 还要早的智能手表项目；并且多次参加 Hackathon 比赛。

后来，创业过程中，我不但做过小米生态的智能硬件（那是小米平台上少有的医药器械设备）；而且还从 0 开始设计、搭建过一个完整的物联网系统，并投入到了几个大型企业的生产环境中。

正是因为从大学到工作的这些经历，我对硬件、软件、通信技术、前端和后台都有比较好的掌握，而这正好契合物联网的多种技术融合的特点。所以，我对物联网的整个知识体系非常清楚，并对相关技术有深入研究。

怎样高效入门物联网？

物联网从整个体系结构来看，可以分为三个层面。

第一是设备层，也就是各种硬件设备。

设备组件有传感器，比如测量温度、湿度、光照强度等参数的设备；也有执行器，比如控制电路通断的继电器、实现物体移动的马达等。

物联网设备，不仅涉及传统嵌入式系统的开发，而且也需要考虑通信技术，比如 Wi-Fi、蓝牙和蜂窝网络等。你只有熟悉它们的特点，才能在实践中做出正确的选择。

第二是网络层，主要关注的是设备与物联网平台的通信协议。

物联网的网络通信仍然是基于互联网的，所以底层还是 TCP/IP 协议。应用中你更多需要了解、掌握的是具体的网络协议，比如 HTTP、MQTT 和 AMQP 等。在做物联网系统设计的时候，你得搞清楚这些协议的适用场景。

第三是应用层，也就是实现具体业务逻辑的地方。

除了像普通互联网后台一样，要面对服务器框架、数据库系统、消息队列等问题外，物联网系统首先需要处理的就是海量的数据。这又可以分为三个方面：

数据存储，比如 NoSQL 数据库和时序数据库的选择。

数据处理，比如 Spark、Flink 等大数据处理框架的不同特点，批处理和流处理的适用场景等。

数据分析，如各类机器学习算法，甚至 AI 的应用。

设备层、网络层和应用层这三个层面的知识，都是你在设计和实施一个物联网系统的过程中需要掌握的。另外，现在数据隐私和系统安全越来越重要，它们贯穿系统的整个生命周期，也是需要你通盘考虑的事情。我提供了一个知识体系图，供你随时查看。

物联网知识体系图

这门课是怎样设计的？

我们应该如何高效地学习呢？在这门课里，我将通过三个循序渐进的模块，理论结合动手实践，来帮你构建物联网的知识体系。

在基础篇，我会带你快速认识物联网。

我将按照设备接入网络、与平台对话、传输数据和利用数据产生价值的路径，介绍物联网系统的基本框架，让你对设备层、网络层和应用层这三大层面留下初步的印象；然后通过智能家居场景的简易 IoT 系统设计，让你对物联网建立更直观的认知。

在进阶篇，我会带你掌握物联网的关键技术。

我将结合简易智能家居项目，为你深度剖析物联网三大层面的核心知识点，并对开源生态、用户隐私、系统安全的实践注意事项做针对性的补充。这些知识能帮你做好充足的准备迎接工作实践。

在实战篇，我会手把手带你从 0 开始完成自己的智能家居项目作品。

网上虽然已经有一些 DIY 硬件设备的资料，但是大多没有放在物联网的技术背景下去做，只是单机版本的。而这个实战项目，是我为你量身定制的。我将围绕智能家居的简易物联网系统，一步一步讲解实现步骤和注意事项。为了降低难度，我还专门做了两件事：

我采用了 Python 语言来进行嵌入式系统的开发。就算你是 0 基础，也可以做出自己的小作品。

我设计了一个硬件套装，帮你省去逐一采购的麻烦，一站式配齐所有组件。这个套装在极客商城就可以购买。

你在跟着我动手操作的过程中，肯定会对物联网的核心技术有更深刻的理解。工程项目跟游泳一样，我从来没有见过哪个人是通过看书学会游泳的。你必须不断地在实践中锻炼、提高自己的技能。

最后，我也特别希望，你能在部落里晒出自己的实战作品，跟其他同学一起学习交流。

在这三个模块之外，我还会时不时为你加个餐，介绍一下物联网的未来发展趋势和重点实践领域，比如 5G 等新型通信技术的应用。

期待能通过这门课程帮助你在物联网领域实现自己的成就，在职场获得更大的提升。让我们一起做万物互联时代的懂行者吧！

欢迎你把课程分享给同样对物联网感兴趣的朋友，一起进步；也请你多多给我留言，说说自己对这门课的期待，或者目前遇到的困惑，我一定会认真答复。

我是郭朝斌，我们下一讲见！

01 | 入门介绍：如何定义物联网？

你好，我是郭朝斌。

在课程的第一讲，我想跟你讨论一个入门级的问题：怎么定义物联网呢？

看到这个问题，你肯定希望我直接给你一个一针见血的定义，帮你打通认识物联网的任督二脉。

但是我跟你说实话，定义物联网真的不是一件容易的事情。因为物联网行业是一个新兴行业，它的内涵和外延都在发展变化的过程当中。今天是智能家居、智慧城市，明天是车联网、边缘计算，未来还会有更多……新的应用和新的理念还在源源不断地出现。

那么，我们还有办法讲清楚物联网这个概念吗？别着急，我想请你先思考一个问题，说到物联网，你会想到哪些关键词呢？是不是包括这些：互联网，传感器，5G，云计算，MQTT，大数据，人工智能，智能家居……

其实，只要你搞清楚它们跟物联网的关系，也就知道物联网到底是怎么一回事儿了。

1. 物联网和互联网有什么关系？

我先来说说物联网和互联网的关系，因为很多人都对这两个概念“傻傻分不清楚”。

有人说，物联网和互联网没有区别，手机也是一个物品，所以移动互联网就是一个物联网。

这种说法并不准确，因为他没有看到手机的本质。

手机本质上是一个移动化的电脑设备：

电脑的鼠标点击，变成了手机的手指触摸；

电脑的实体键盘，变成了手机上的虚拟按键；

电脑联网要用 Wi-Fi 或网线，手机也可以用 Wi-Fi，但舍弃了网线，支持移动蜂窝网络；

电脑的摄像头、音箱、麦克风，集成到手机上时都进行了小型化……

这也是为什么主导手机行业发展的企业，和主导电脑行业发展的企业一样，仍然是操作系统厂商和芯片厂商。谷歌就相当于微软，高通就相当于英特尔。 当然，还有同时控制操作系统和芯片的苹果公司，它在手机和电脑行业都很有分量。

所以手机和电脑一样，仍然是围绕人的需求来设计的设备。

人是信息的“生产者”，手机为人生产信息提供各种工具。这些信息可能是一篇微信公众号文章，一条语音消息，或者一段抖音短视频。

同时，人也是信息的“消费者”。这些信息的各种媒体形式都是给人浏览、收听和观看的。

所以我说，互联网是连接人，连接人与信息。人和信息的关系就像下面这张图所展示的：

传统互联网中人和信息的关系

那么，物联网和互联网相比，最本质的区别是什么呢？答案就是，设备（物）成了另一个重要的参与主体。设备跟人一样，也是信息的生产者和消费者。

怎么理解呢？我举一个例子。在智能家居的安防设计中，有一种磁力传感器，它可以监测你家窗户的开关状态。比如，当它发现窗户被打开时，这个信息将传送到云端服务器，然后服务器就会下放指令给监控摄像头，让摄像头拍摄照片、录制视频，并且推送给你。

这个过程中，磁力传感器“生产”了窗户打开这个信息，而监控摄像头和人“消费”了这个信息。这个安防系统就是在你家时刻待命的“电子保安”，跟真人保安有类似的功能。这种情况下，人、信息和设备的关系可以参见下图：

物联网中人、信息和设备的关系

看到这里，可能你会说，物联网和互联网确实是两个不同的事物。

其实，这个理解也不够准确。在刚才提到的智能家居场景中，我们可以发现，设备产生的数据，仍然是通过互联网的网络传输到互联网的云服务器上的，而且人仍然是物联网中信息的重要参与者。

所以，物联网和互联网并不是完全割裂的，物联网是互联网的发展和延伸。而且，因为接入设备在供电情况、网络环境、功能和数量上的差异，物联网也会出现很多跟传统互联网不一样的技术需求和特性。

2. 物联网和传感器有什么关系？

在智能家居安防设计的例子中，我们提到了一个很重要的技术，传感器。

有人说，物联网就是一个传感器网络，把各种传感器设备接入互联网，就组成了物联网。

这曾经是很流行的想法，有些物联网的书籍，甚至直接把物联网的设备层称为“感知层”。这可能是因为，在物联网发展的初期，传感数据的远程收集是主要的应用领域。比如 MQTT 协议，就是 IBM 在 20 世纪 90 年代，为了监测偏远地区的石油和天然气管道而开发的。

但是物联网早就不只是通过传感器设备监测数据那么简单了，而是已经发展到了基于信息实现设备联动控制的阶段。比如，我在下班的路上就可以通过手机，提前打开电饭煲开始熬粥；打开热水器，进门前为我准备好热水。

不过有一点不得不说，因为传感器设备一般是计算资源少、网络环境差、电池供电的资源受限设备，所以传感器网络技术，尤其是无线传感器网络（WSN，Wireless Sensor Networking）技术，极大地推动了物联网的发展。

3. 物联网和 5G 有什么关系？

除了刚才说的无线传感器网络技术，还有一种网络通信技术也对物联网的发展有非常重要的意义，那就是 5G。

你可能经常在媒体上看到这样的言论：“5G 时代，物联网才会真正落地。”真的是这样吗？现在用于物联网的通信技术只是过渡方案吗？

我先不着急回答这个问题，因为我想先谈谈 5G 技术本身。

提到 5G，你可能首先想到的是网速快，其实这只是一方面。实际上，5G 被分为 3 个主要应用场景，分别对应 eMBB、uRLLC 和 mMTC 3 个标准：

eMBB 用于我们日常的手机移动通信，网络传输速率高主要就是指这个标准。

uRLLC 应用的场景是无人驾驶、远程手术等，所以强调的是极低时延，而且是稳定的时延和速率，这不难理解，如果远程做手术，那肯定不能出现卡顿和忽长忽短的时延。

mMTC 可以支持大规模设备的连接上网，适合智能门锁、烟感传感器、路灯等低速率、低成本、低功耗的物联网设备。

为物联网场景提供新的想象空间的，其实是 uRLLC 和 mMTC ，但是这两个标准还没有正式发布，真正部署的时间更是不确定。

不过，这并没有影响物联网的落地，原因主要有两个：

像智能门锁等场景可以选择 NB-IoT、LTE-Cat1 等已经成熟的蜂窝网络技术；

物联网的场景非常多样，Wi-Fi、BLE、LoRa 等非蜂窝技术一样有用武之地。

这些通信技术我会在下一讲为你介绍。

现在我们可以回答刚才的问题了：5G 时代，物联网才会真正落地吗？并不是这样。虽然 5G 中的 uRLLC 和 mMTC 将推动物联网在更多的场景落地，但也有很多场景不需要等到 5G 时代就可以走进现实。

4. 物联网和云计算有什么关系？

除了 5G，还有一个经常被提到的概念，那就是云计算。

云计算厂商都有各自的物联网平台，而且不遗余力地推动着物联网设备厂商和解决方案企业作为合作伙伴加入它们自己的平台。

于是很多人开始担心，云计算厂商会不会主导物联网标准的建立？

实际上，这种担心是多余的。因为云计算厂商可以把控的地方主要是物联网设备的平台接入，还有数据的存储和处理。

平台接入会涉及到网络协议的选择，目前他们主要支持 MQTT 等协议。而这些网络协议都是开放的标准，他们很难控制。

对于数据的存储和处理，云计算厂商会提供基于分布式系统的高性能、可伸缩的大数据存储产品，还有各种数据处理产品，甚至人工智能算法平台等等。虽然这的确会导致设备厂商对云计算厂商有一定的依赖性，但是产品主要还是基于社区主导的开源软件来开发的。

因此，在设备厂商、技术标准组织、开源社区、云计算厂商和网络运营商等多方参与的物联网中，云计算厂商很难主导物联网标准的建立和发展。

5. 物联网和智能家居有什么关系？

随着云计算的发展，平台对海量数据的处理能力大幅提升。也许你在生活中最直接的感受就是，智能家居设备的功能越来越多，响应速度也越来越快。

的确，从日常感知的角度来看，很多人认为，物联网的火热似乎主要体现在智能家居领域。尤其当看到很多知名公司的宣传时，这种感觉会更强烈。

其实，我们想一想那些知名家电品牌，比如西门子、海尔、格力等等，就知道家电向来是个大产业，所以很容易想到智能家居的未来充满前景。

而手机厂商、互联网巨头和创业公司当然也看到了这个机会，因此一拥而上，使智能家居成为物联网中发展迅猛的行业。这样一来，一个正常价格在 500 元左右的智能音箱，在互联网流行的补贴大战下，只需要一两百元也不奇怪。

那么，智能家居就是物联网的主要应用场景吗？

其实，物联网不只有智能家居这一个主流应用。我经常以智能家居举例，也只是因为它贴近生活，你可能更容易理解。

除了智能家居，工业制造中的工业物联网应用因为可以直接提高效率、降低人工成本，市场规模逐年增长。尤其大热的工业 4.0，更是推动了整个产业的发展。另外，还有农业，基于物联网技术，农业正在改变劳动力密集、靠经验等缺点，从而提高产量、解放人力。

小结

讨论完上面这些问题，物联网的样子，我们已经看得越来越清楚了：

物联网是互联网的发展和延伸，是设备和人一起作为主体组建的互联网。

物联网不只是通过传感器设备监测数据那么简单，而是已经发展到了基于信息实现设备联动控制的阶段。

5G 通信技术可以推动物联网在更多的场景落地，但对很多场景来说， 5G 不是必需的。

云计算平台提供的标准网络协议接口，还有海量数据存储、处理能力，这些都是物联网的重要基础设施。不过，物联网行业的发展一定是多方共同推进的。

物联网应用不止是智能家居，大家看到了智能家居的前景，但是工业、农业、交通、物流等各个领域同样是物联网的大舞台。

如果你对我在开篇词提到的物联网三大层面还有印象的话，不难发现：

这里讨论的传感器和通信技术（5G），对应了设备层；与平台接入有关的 MQTT 协议，对应了网络层；云计算的大数据存储、处理，以及智能家居等领域的应用，对应了应用层。

正是这三大层面技术的发展，推动了整个物联网的发展，并改变着我们生产和生活的各个领域。

在基础篇接下来的几讲中，我将沿着信息数据流转的路径，依次带你初步了解设备层、网络层和应用层三层相关的一些技术，然后通过智能家居场景的简易物联网系统设计，带你建立对物联网基本框架的认知。

思考题

最后，给你留一个思考题。许多互联网巨头都已经看到了智能家居的发展前景，但是它们主要发力的细分市场可能有所不同。在未来的智能家居市场争夺战中，你觉得关键的突破口会是什么呢？智能音箱是你心目中的答案吗？

欢迎你在评论区留言和我交流，也欢迎你将这一讲分享给对物联网感兴趣的朋友，大家一起交流学习。

02 | 通信技术：设备接入网络的方式有哪些？

你好，我是郭朝斌。

在上一讲的讨论中，我们得到一个结论，物联网是互联网的发展和延伸，是由设备和人作为主体共同组成的互联网。而在设备、人和信息之间搭建联系的物联网，可以分为设备层、网络层和应用层三个层面。

今天这一讲，我们先来看看物联网数据流的第一站，设备层。

物联网设备和单机设备最大的区别在哪儿呢？就在于它们接入了网络，所以这些设备才能将传感器采集的数据（生产信息）上传到云平台，然后根据平台对数据的分析做出反应（消费信息）。

因此，接入网络的技术，也就是通信技术，是你在做物联网设备设计时需要重点关注的。

想一想你的手机，刷票进地铁站会用到 NFC，追剧会用到 Wi-Fi 或者 4G，连无线耳机听音乐会用到蓝牙。正是因为集成了这么多的通信技术，它才可以满足你不同使用场景下的使用需求。

那么在物联网领域，我们有哪些通信技术可以选择呢？手机上使用的这些技术，可以用于其他物联网设备吗？

当然可以。除了刚才提到的这些，智能手机上已经不流行的 2G、3G 网络技术，在某些不需要高速率的物联网场景仍会使用到。另外，物联网行业也有一些自己专有的通信技术，比如LoRa 和 SigFox。

当然，设备接入网络不仅可以通过无线通信技术，还可以通过有线通信的方式，比如工厂中广泛应用的 PLC （Power Line Communication，电力载波通信）接入技术。但是，无线通信无疑是物联网应用的主流。

这一方面是因为有线技术的部署成本很高；另一方面，物联网是亿级数量设备的接入，如果每个设备都需要像古老的座机电话一样，连线接入，那么就对便利性构成了挑战，对网络中继设备来说更是灾难。

在这一讲中，我会介绍几种最重要的无线通信技术的特性。在后面的实战中，我们将基于这些特性来设计产品的通信方式。同时，我也会说一说在实践中需要关注的一些知识点。

无线通信技术的 4 个重要参数

在学习具体的无线通信技术之前，我们得先熟悉无线通信技术的 4 个重要参数：

频段

信道

信道带宽

传输速率

你可以把它们作为了解和分析其他通信技术的工具，指导日后的实践。

频段

我们先来看频段。无线通信当然用的是电磁波，在实际应用中，电磁波是按照频段来使用的。

频段是指电磁波频率的一个范围，对应到频谱中就像是一段一段切割开。你看“频段”这个名词，是不是很形象？

整个频谱的频段分布（图片引自光通信百科）

上面这张图展示了整个频谱的频段分布。图中的分段基于电磁波不同的特性，而通信技术则需要考虑不同的使用需求和管理，对无线电波做更进一步的分段。

在这个频谱图中，Wi-Fi 的频段是 2.412GHz-2.484GHz。这是一个非授权频段，其他的通信技术也可以使用，所以在后面你会看到蓝牙技术也基本是这个频率范围。理解了这一点，也许你就能明白，为什么 Wi-Fi 路由器和蓝牙耳机、键鼠，在某些情况下会相互干扰了。

有没有什么办法能解决通信干扰呢？答案是肯定的，其中一种有效的方法是基于信道的跳频技术。

信道

说到这，你可能会问：信道是什么？

它是信息通过无线电波传送的具体通道介质。每种通信技术的频段会被划分、规划成多个信道来使用。

比如，Wi-Fi 的频段被分为 14 个信道（中国可用的是 13 个信道，信道 14 排除在外）。这里需要注意的是，相邻信道的频段是存在重叠的。比如，Wi-Fi 的信道 1 频段是 2.401GHz～2.423GHz，信道 2 频段是 2.406GHz～2.428GHz。

信道带宽

信道频段的最大值和最小值之差，就是信道覆盖的范围大小，也叫信道带宽。比如，Wi-Fi 信道 1 的带宽是 22MHz，它是由 2.423GHz 减去 2.401GHz 得到的。

需要注意的是，22MHz 是信道 1 的实际带宽，而它的有效带宽只有 20MHz，因为其中有 2MHz 是隔离频带。隔离频带主要是起保护作用的，就像高速公路上的隔离带。

传输速率

上高速公路的时候，我们会注意当前路段对车速的要求。而在选择通信技术的时候，传输速率就是我们需要关注的重要指标。它是指数据传输的快慢，单位是比特 / 秒（bit per second，简记为 bps）。

不同的传输速率可以支撑不同的应用场景。比如，5G 峰值 20Gbps 的传输速率为移动 VR/AR 应用提供了技术可能。在 4G 时代，这种应用就会受到限制，我们只能在室内拖根线来感受 VR/AR 的魅力。

传输速率受很多因素的影响，比如信道带宽和频率。一般来说，带宽越大，传输速率就越大，就像路面越宽可以承载的通行车辆越多一样；频率比较高时，电磁环境相对比较干净、干扰少，传输速率会更高，就像道路更平坦自然可以通行更多车辆一样。

另外，为了提高传输速率，越来越多的提高频段利用率的技术也被开发出来并投入使用，比如正交频分复用。也许你对这个技术比较陌生，我再举一个更为人熟知的例子好了，MIMO（Multiple-Input Multiple-Output）技术，你可能在 Wi-Fi 路由器上或者手机宣传资料中见到过它，它是通过使用多重天线收发信号来提高传输速率的。

好了，频段、信道、信道带宽和传输速率，这 4 个无线通信技术的关键概念，咱们就讲完了。我再用一张图来表现它们的关系，帮你加深一下印象。

无线通信技术的4个概念

针对图中的信道个数，我补充说明一下，这里的 12 个信道只是举例。实际应用中，不同的通信技术有不同的信道个数；同一种技术，不同的版本也会对信道做不同的划分。

接下来，我们继续学习几种无线通信技术，包括 Wi-Fi、BLE、2G（GPRS）、LTE-Cat1&NB-IoT。因为目前家庭和办公空间等局域网中，应用最广泛的无线通信技术就是 Wi-Fi ，所以咱们就从 Wi-Fi 学起吧。

Wi-Fi

Wi-Fi 是 IEEE 802.11 无线网络标准的商品名。所以，支持 Wi-Fi 功能的设备上，都会标注 “兼容 IEEE 802.11b/g/n 2.4GHz” 之类的说明。其中，802.11b、802.11g、802.11n 就是 Wi-Fi 的不同版本。

这种版本命名方式当然不够直观，所以 Wi-Fi 联盟从 2018 年开始推进数字版本号，比如，Wi-Fi 5、Wi-Fi 6。我在这里放了一张表格，展示了新旧版本号的对应关系。

没错，你可能也发现了，蓝牙早就这样定义版本号了。数字版本号的好处是，可以让开发人员避免使用错误版本的模组或者芯片，也方便了他们与用户沟通使用中的问题。

Wi-Fi 网络分布广泛，在家里、在办公场所，包括很多公共区域都有覆盖。所以，它自然也成了很多智能设备的选择，尤其是连接电源线、不需要关心功耗的设备，往往优先考虑 Wi-Fi。

不过，实践中有一个事情需要注意，那就是 Wi-Fi 的配网。

配网失败，是 Wi-Fi 智能硬件被用户投诉和退货的非常重要的原因。据我了解，有厂家使用 2016 年的微信硬件 AirKiss2.0 配网，退货率达到了 15%，是不是非常吓人？

之所以需要“Wi-Fi 配网绑定”这个操作流程，根本原因是，物联网设备不像手机、电脑，没有便利的信息输入手段，也没有完备的信息显示器件。因此，我们无法像在手机上一样，帮设备搜索、连接上 Wi-Fi 热点。相反，我们需要借助其他手段来完成两件事情：

Wi-Fi 设备需要连接上路由器（Wi-Fi 热点或者 AP）实现数据上传和远程控制，即获取到 Wi-Fi 热点的名称（称为 SSID）和密码。

获取到 Wi-Fi 设备的 MAC 地址，或者设备生产时预存的信息，并与用户账号一起上传服务器完成绑定。

完成这两步之后，设备才可以联网使用。

一键配网技术

万事开头难，当你拿到一个 Wi-Fi 智能设备时，第一件麻烦的事情就是 Wi-Fi 配网绑定，所以很多物联网平台、模组厂家就推出了一键配网技术（SmartConfig）。

顾名思义，一键配网技术就是你只要在手机 App 上按一个按键，就可以自动完成配网的一系列操作。

然而愿望很美好，现实却很打脸。一度覆盖市面上 90% 以上 Wi-Fi 设备的一键配网技术，竟然成了导致配网失败率高的罪魁祸首。这又是为什么呢？

原来，一键配网技术的工作原理，是通过手机或 Wi-Fi 路由器发送 UDP 广播包的形式，将 Wi-Fi 的 SSID 和密码广播出去。Wi-Fi 设备在进入配网模式后，会将接收到的广播包进行解析，从而获取到 Wi-Fi 的 SSID 和密码，然后连接上路由器。同样地，Wi-Fi 设备连接上路由器后，也会广播自己的 MAC 地址，这样手机 App 就可以接收到设备的 MAC 地址完成绑定。

这个原理看着不复杂，但却有几个容易出错的地方：

很多路由器不支持 UDP 广播，例如 Wi-Fi 放大器或者关闭 UDP 广播的路由器，会导致在发广播包这步就失败了。

目前很多路由器已经支持 2.4GHz 和 5GHz 两个频段（Wi-Fi 4 之后），这两个频段的网络是相互隔离的。手机很可能连接的是 5GHz 频段，那包含 SSID 和密码的广播包也在 5GHz 频段，而目前所有 Wi-Fi 智能设备都只支持 2.4GHz 频段，这就导致无法收到广播包。

2018 年之前的 Wi-Fi 产品（比如微信 AirKiss 2.0）存在广播包解析错误的问题，很可能发生解析的密码不正确的情况，导致无法连接路由器。

路由器不支持局域网通信（比如路由器的访客网络），或者空间中有几个 SSID 相同的 Wi-Fi 热点，手机和 Wi-Fi 设备分别在不同的路由器下面，会导致设备即使连接上了路由器，但是手机无法获取设备信息的广播包的情况。

用户路由器或者设备的 Wi-Fi 模组引起的问题很难改变。遇到这类问题时，你可以让用户把手机热点打开，并且把热点的 SSID 和密码设置成与配网的 Wi-Fi 路由器一样的，然后拿到离路由器很远的地方，使用手机的 Wi-Fi 网络配置成功后，再回到路由器的环境下使用。

如果是 2.4GHz 和 5GHz 两个频段的问题，那么可以切换手机的 Wi-Fi 连接到 2.4GHz 的频段后再配置网络。

当然，这些操作的要求不低，很多用户很难独立完成，所以选择其他配网方式或者为一键配网的设备预备其他配网技术是一定要考虑的。小米米家的物联网平台的设备热点配网技术，就是一个值得借鉴的方式。它可以在不增加成本的情况下，保证基本 100% 的成功率。

设备热点配网技术是让 Wi-Fi 设备先进入 Wi-Fi AP 的模式，也就是说，由设备创建出一个 Wi-Fi 热点，然后让手机连接到这个热点，接着把路由器 Wi-Fi 的 SSID 和密码发送给 Wi-Fi 设备，同时手机也可以直接获取到设备的 MAC 地址。

这种方式的麻烦之处在于，你需要手动输入路由器 Wi-Fi 的密码。在苹果手机上，你还需要进入设置界面，手动选择连接智能设备的 Wi-Fi 热点。

其他 Wi-Fi 配网技术还有零配置配网、蓝牙配网、手机热点配网和路由器配网等技术方式，有兴趣的话，你可以单独再查询了解一下。下图展示了各种配网技术中角色之间的关系，供你参考。

各种配网技术中角色之间的关系（图片引自阿里云）

BLE

刚才我提到，连接电源线、不需要关心功耗的设备往往优先考虑 Wi-Fi，那么功耗要求严格的设备要怎么办呢？

BLE 是一个不错的选择。它的频段是非授权的 2.400GHz-2.4835GHz，采用 40 个带宽 2MHz 的通道。

听到 BLE，你也许会觉得有些陌生。其实它是 Bluetooth Low Energy 的缩写，也叫低功耗蓝牙，是对 Bluetooth 4.0 / 4.1 / 4.2 的统称。现在它已经成为低功耗物联网设备的首选，仅仅依靠一颗纽扣电池供电就可以工作数年。

而蓝牙 5 更是针对物联网增加了很多特性，比如 Mesh 组网、更远的通信距离、更快传输速率（BLE 4.2 的 2 倍）和更大数据承载量的广播包（BLE 4.2 的 8 倍），此外还有厘米级精度的定位功能。

除了这些，蓝牙技术还有一个优势，那就是应用实践非常广泛，是智能手机等设备的标配。因此，无论是从芯片模组的价格考虑，还是从生态的丰富度考虑，它都是电池供电的智能设备的理想选择。

虽然蓝牙 5 有许多很好的特性，但是它还在普及中，目前 BLE 仍然是主流，所以我们的目光还是先回到 BLE 身上吧。

BLE 的数据通信主要基于广播包和 GATT 协议。如果你对它们还不是很了解，在从事开发工作之前，最好先补充一下这方面的基础知识。我在这里提供了一份资料给你参考。

连接参数的调节对于 BLE 设备的扫描和连接等影响很大。这些参数包括广播间隔、最大连接间隔、最小连接间隔和连接监听时间等，它们都可以在设备的固件开发中进行调整。比如广播间隔会影响扫描的响应速度，也会对设备的功耗有影响，所以需要平衡功耗和响应速度的不同需求，选择一个合适的值。

如果扫描连接体验需要非常流畅迅速，你可以选择 20ms 的间隔，但是这会增加设备的功耗，减少待机时间；如果你能够容忍一定的扫描等待时间，那么可以选择 1-2 秒的广播间隔来降低功耗。

另外，如果你在 Android 手机上开发 BLE 相关的应用，还有两点是我想提醒你的。

一是不同品牌 Android 手机的芯片解决方案往往是不同的，它们的蓝牙协议栈实现也存在很大的差异。所以开发者需要针对不同的手机进行充分的测试，保证开发的应用在不同的手机上都可以正常运行。

二是因为基于 BLE 可以进行地理位置定位，所以从 Android 6.0 开始，进行 BLE 开发时需要请求位置权限。这一点经常被忽略，也是需要注意的。

2G

在 Wi-Fi 流行之前，智能手机进行数据通信的主要方式是蜂窝通信技术，也就是我们常说的“ x G”。

2G 是相对早期的蜂窝通信技术，你可能觉得这个词听起来都已经有些年代感了。不过，在一些传输数据量不大的场景中，采用 2G 技术的联网设备还有不少，比如路灯控制器、冷链运输监测设备等。

但是，你在新产品的设计中要尽量避免使用 2G 模组，因为 2G 的退网已经不可避免。

联通已经率先推进，移动也开始逐步实施，腾出的频段会用于发展 5G 网络通信。如果新产品仍然采用 2G 模组，未来无网可用时会非常麻烦，甚至在那之前你就会遇到很多因网络引起的产品体验问题。

LTE-Cat1 & NB-IoT

那么，2G 的替代选择是什么呢？从目前国内通信产业的发展看，LTE-Cat1 和 NB-IoT 是你值得关注的选项。NB-IoT 模组的价格已经和 2G 模组基本持平；LTE-Cat1 的模组相比要贵一些，但是也要远低于 4G 模组的价格。

LTE-Cat1 有一个独特优势是网络覆盖，它属于 4G 网络的低速类别，可以直接使用我们国家完善的 4G 的网络设施。而 NB-IoT 虽然已经发展多年，但是目前网络覆盖仍然不够理想，这也限制了这些年 NB-IoT 的广泛应用。

不过，NB-IoT 大概率会成为 5G 的重要组成部分。随着 5G 产业的发展，我相信 NB-IoT 仍然会广泛应用在低速率、广域网络的场景中。

那么，LTE-Cat1 和 NB-IoT 分别适合哪些应用场景呢？我们可以根据它们各自的特性来分析。

LTE-Cat1 的带宽是 20MHz，上行速率 5Mbps，下行速率 10Mbps。它有良好的移动特性，功耗比 NB-IoT 大些，但是低于传统的 2G/3G 设备。所以它适合可穿戴设备、ATM 机、自助售货机和无人机等场景。这些场景对数据传输速率有一定要求，但是又不需要达到 4G 的水平。

NB-IoT 的带宽是 180KHz，上行速率 16.9Kbps，下行速率是 26Kbps，功耗很低。所以它不适合移动环境，但却很适合智能抄表、智能灯杆和烟感报警器等低数据速率的场景。

我想，说到蜂窝网络，你最感兴趣的肯定是 5G。不过，关于 5G，值得说的东西太多了。所以今天我们先学到这里，我会在后续的加餐中跟你详细聊聊 5G 这个话题。

如果你对前面提到的 LoRa 和 SigFox 技术同样感兴趣，我也很乐意在加餐中跟你分享。

小结

现在，我们总结、回顾一下对这一讲的重点内容。

无线通信技术有 4 个重要的参数：频段、信道、带宽和传输速率。当遇到新的无线技术时，你可以把它们作为了解、分析的一个角度，进而更好地指导实践。

Wi-Fi 和 BLE 在移动互联网时代应用广泛，未来仍是理想的技术选择。连接电源线、不怎么关心功耗的设备往往优先考虑 Wi-Fi，而依靠电池供电、比较关心功耗的设备则更加适合 BLE。

对于 Wi-Fi 智能设备来说，Wi-Fi 配网绑定是比较麻烦的事情，所以很多物联网平台和模组厂家推出了一键配网技术。但是因为其不能保证 100% 的成功率，设备热点配网技术可以作为备选方案一起提供，甚至也可以作为主选方案。

2G 的退网已是趋势，在实践中可以考虑 LTE-Cat1 和 NB-IoT 两种蜂窝通信技术。对于非移动的、传输数据量很小的设备可以选择 NB-IoT；传输数据量更大，又不需要达到 4G 能力的设备可以考虑模组越来越成熟的 LTE-Cat1 技术。

总之，物联网场景丰富，设备的工作环境和传输的数据量差别很大，你在应用中要合理地评估和选择通信技术。我画了一张对比图，供你参考。

当然，关于物联网的设备层，你需要了解的远远不止这些。更多核心技术，比如组网技术，等我们到了进阶篇再逐一剖析吧。

思考题

最后，我再给你留一个思考题。

5G 技术已经逐渐走到我们的生活中，而且围绕它的应用场景，行业讨论热烈。除了高速率，我也提到过 5G 还有两个重要的特性，你还记得是什么吗？这些特性的实现原理你了解多少呢？

欢迎在留言区写下你的思考，我们一起交流学习；也欢迎你把这一讲分享给更多的朋友，一起阅读讨论。

03 | 网络协议：设备使用什么语言与互联网对话？

你好，我是郭朝斌。

在上一讲中，我为你介绍了物联网设备常用的几种无线通信技术。通过这些技术联网之后，物联网设备就可以向云平台上报数据了吗？还不可以。

成功传输数据的前提是，通信双方必须使用同一种“语言”。这就像，你拨通了比尔·盖茨的电话，如果说“喂，你好”，他大概会一头雾水；但如果说“Hello!”，他一下就能听懂了。

而物联网设备间沟通的语言，就是网络协议。说起网络协议，你可能马上就想到了 HTTP 协议。是的，在日常的 Web 开发中，我们总是需要跟它打交道，因为 HTTP 协议是互联网的主流网络协议。

类似地，应用在互联网中的网络协议，还有收发电子邮件的 POP3 、SMTP 和 IMAP 协议，以及用于区块链中的 P2P 协议。

那你马上会问了，物联网设备之间的通信，可以直接使用这些协议吗？

物联网通信的特点

想要回答这个问题，我们得先来分析一下物联网的网络通信特点。

物联网设备很大可能工作在不可靠、高延迟的网络环境中。

比如共享单车，使用 NB-IoT 这样的通信技术，本身的通信速率就只有不到几十 Kbps；要是被人停在城市的角落里，信号可能很不稳定。

假设你使用 HTTP 协议，就需要单车先发出连接请求，然后等待服务器的响应（下发开锁指令）。这样一来，受网络通信质量的影响，很可能连接经常中断，而需要单车与服务器交互多次，那用户可能就要等很长时间。

对于这种场景来说，不只是 HTTP，其他跟 HTTP 一样单向的、同步的网络协议，都不是理想的技术方案。

物联网系统中，设备数量多，而且交互非常复杂。

比如你家里的环境监测，温度、湿度、光照、二氧化碳、甲醛含量……这些都需要不同的设备测量，而且每个房间用到的设备也不同。

如果你让云平台的服务对每个设备分别做权限控制和数据阈值设置，这会非常麻烦。因为当数据的“生产者”和“消费者”直接交互时，要是没有中间角色基于共同的目标协调，双方的耦合度会很大，导致系统很难实现。

这时候，你需要把你家作为一个整体来处理，交互逻辑就会变得简单多了。

设备经常需要根据实际使用环境做增加、减少等调整。

比如你用一个设备测量房间的温度，现在这个设备损坏了，你要换一个。你肯定不希望因为设备 ID 这么一个小小的变化，就要去修改判断房屋温度的逻辑代码，这太麻烦了，也不符合软件开发的开闭原则。

这要求系统必须具备可伸缩性（或者说可扩展性），设备的增减不会导致系统逻辑的调整。

正是因为这些特点，物联网系统在选择网络通信的协议时，一般采用发布 - 订阅的通信模式。为什么说发布 - 订阅模式适合呢？我来解释一下它的设计，你就明白了。

发布 - 订阅模式

发布 - 订阅模式包含三个角色，分别是发布者（Publisher）、经纪人（Broker）和订阅者（Subscriber），它们的关系如下图所示。

三个角色之间的消息传递，基于一个重要的概念——主题。主题用于消息的过滤（或者说路由），它确定了消息的不同类别。消息传递的过程可以分为三步：

发布者负责生产数据。发布者发送某个主题的数据给经纪人，发布者不知道订阅者。

订阅者订阅经纪人管理的某个或者某几个主题。

当经纪人接收到某个主题的数据时，将数据发送给这个主题的所有订阅者。

我们举个例子你就理解了。到中午了，你有点饿。今天吃什么呢？你想了想，拿出手机打开外卖 App，选择了牛二楠家的咖喱牛肉盖饭，外加双份肥宅快乐水加冰。

当你发布牛二楠家的订单给外卖订单中心服务器时，隔壁的产品经理小王也在点外卖，他点了猪三排家的红烧排骨盖饭。

外卖订单中心收到这两个订单之后，会把你的订单发送给牛二楠家，把小王的订单发送给猪三排家。

相信你也看出来了，你和小王都是“发布者”，牛二楠家和猪三排家就是“订阅者”，而外卖订单中心就是“经纪人”。

当然，你内心可能在祈祷外卖订单中心出个错，把小王那个订单（猪三排家的红烧排骨盖饭）发送到做卤煮的猪三下水家那里，给他的午饭增加一点难忘的体验。

为什么我说发布 - 订阅模式适合物联网系统呢？因为在物联网场景中，一个传感器数据需要触发多个服务或者终端执行动作。

比如红外传感器，当它检测到有人体靠近时，就需要触发一系列动作：通知摄像头拍照，声光报警器执行报警，推送消息给主人的手机等。

怎么满足这种需求呢？我们最好让摄像头、声光报警器和手机都订阅“人体靠近”这个主题消息。当红外传感器被触发时，它发送人体靠近的消息，然后这些设备就能同时收到这个消息，接着完成系统定义的那些动作。这就是发布 - 订阅模式的工作方式。

另外，发布 - 订阅模式减少了发布者与订阅者之间的耦合度，所以网络不稳定导致的临时离线就不会影响彼此的工作。它的拓扑结构也使系统更容易扩展，更容易满足动态变化的需求。这些优点都使得发布 - 订阅模式在物联网应用协议中非常流行。

那么，具体有什么网络协议采用的是发布 - 订阅通信模式呢？MQTT 协议就是其中的佼佼者。

MQTT

MQTT（MQ Telemetry Transport）协议，是 IBM 公司在 1999 年开发的轻量级网络协议，它有三个主要特点：

采用二进制的消息内容编码格式，所以二进制数据、JSON 和图片等负载内容都可以方便传输。

协议头很紧凑，协议交互也简单，保证了网络传输流量很小。

支持 3 种 QoS（Quality of Service，服务质量）级别，便于应用根据不同的场景需求灵活选择。

这里我解释一下什么是 QoS。它是指通信双方关于消息传送可靠程度的协商。QoS 0，消息只发送一次，消息可能丢失；QoS 1 呢，发送方会接收反馈，保证消息的送达，但是可能消息会重复。QoS 2 级别，通过发送方和接收方的多次交互，保证消息有且只有一次。

现在我们说回 MQTT。刚才提到的这三个特点，让 MQTT 协议非常适合计算能力有限、网络带宽低、信号不稳定的远程设备，所以它成为了物联网系统事实上的网络协议标准。关于 MQTT 协议的详细内容，我还会在进阶篇第 8 讲为你进一步剖析。

AMQP

除了 MQTT 协议以外，也有其他采用发布 - 订阅模式的网络协议，比如 AMQP 协议。

虽然 AMQP 协议拥有庞大的特性集，比较重，不适合计算资源有限、对功耗要求严苛的物联网设备，但是它可以满足后台系统对于可靠性和可扩展性的要求。因此，它在物联网的平台系统中应用广泛。比如，在分布式系统中应用广泛的 RabbitMQ 消息中间件软件，就是基于 AMQP 实现的。

AMQP 和 MQTT 一样，也是基于 TCP 协议，采用二进制消息格式，也支持 3 个 QoS 级别。

顺带说一句，现在被广泛使用 AMQP 1.0 和 AMQP 0.9.1 这两个版本，在设计上有很大的差异。你在查询资料或者应用这两个版本的 AMQP 协议时，一定要注意看版本，避免用错。

请求 - 响应模式

刚才我介绍了发布 - 订阅模式的很多好处，但是凡事都有例外，也有一些物联网应用场景，并不适合使用这种模式。

比如，现在小区里面都有智能快递柜，当你输入取件码后，服务器会向对应的柜门发送开门指令。

在发布 - 订阅模式下，服务器知道指令发送成功了，但是它无法知道柜门是否真的打开了。这时，你就需要让柜门能够向服务器反馈一下命令的执行结果。

当然，你也可以让服务器订阅一个“柜门关闭”的主题消息，然后等待柜门发布这个消息。但是这样的话就非常繁琐、不够直接。

在这种场景下，另一种通信模式就能派上用场了，那就是请求 - 响应模式。

请求 - 响应模式有两个角色，一个是客户端（Client），另一个是服务器（Server）。

客户端是请求数据或者服务的一方。服务器则用来接收客户端的请求，并提供相应的数据或者服务。服务器在收到请求后，会获取数据，对资源数据（比如数据库）进行加工处理，准备好响应，然后返回给客户端。

请求 - 响应模式是无状态的通信方式，每个完整的请求 - 响应都是相互独立的。进一步细分的话，它还可以分为同步和异步两种。你可以看下这张图片。

我们还是以外卖来举例。当你拿出手机点好外卖后，牛二楠家的服务员会收到外卖订单，然后通知厨师准备盖饭，同时另一个服务员已经熟练地打好了快乐水。

半个小时后，外卖小哥拿着装有你心仪午餐的袋子出现在公司门口，然后交到你的手里。他满脸笑容地说，闯了 2 个红绿灯才这么快送到，希望你给个好评。

这就是请求 - 响应模式。你是“客户端”，牛二楠家是“服务器”，你发出“请求”后，等待一段时间，然后外卖小哥将“响应”给到你。流程如下面的图片所示：

HTTP

有哪些网络协议采用了请求 - 响应模式呢？HTTP 就是典型的代表。HTTP/2 协议还引入了异步请求 - 响应模式，客户端可以对请求设置不同的优先级，服务器可以根据优先级决定先响应哪个请求。

虽然 HTTP 协议的报文格式非常重，光是报文头就能达到 KB 大小，不太适合资源有限的嵌入式设备。但在一些计算资源和网络资源都比较充足的物联网设备上，HTTP 协议仍然是一个可选项。而且它和现有的 Web 系统兼容，可以利用已有的 Web 服务器资源。

CoAP

那么有没有跟 HTTP 协议类似，但是设计轻量，可以用于资源受限的物联网设备的协议呢？

有的，那就是 CoAP（Constrained Application Protocol）协议。

跟 HTTP 协议一样，CoAP 协议同样有 GET、POST、PUT、DELETE 等方法和响应状态码，同样使用 URI 而不是 Topic 来标识资源。

比如我们需要访问服务器 iotdemo.com 下面的 bedroom/temp 这个资源，那完整的资源地址是：

coap://iotdemo.com:5683/bedroom/temp

CoAP 的消息采用二进制格式，支持可确认消息和不可确认消息两种 QoS 级别。可确认消息（Confirmable Message）与 MQTT 协议的 QoS 1 类似，不可确认消息（Non-confirmable Message）对应 MQTT 协议的 QoS 0 级别。

另外，CoAP 协议基于的传输层协议是 UDP，而不是 HTTP 、 MQTT 协议的 TCP 协议，所以对于设备的计算资源要求更低。传感器设备一般只需要上传数据，不用随时接收服务器的控制命令，这都说明 CoAP 协议适合电池供电的传感器设备。

LwM2M

说完 CoAP，我再介绍一下跟它有关 LwM2M（Lightweight M2M）协议。

LwM2M 协议定义在 CoAP 协议之上，不过它在消息传输的基础上更进一步。因为它基于 IPSO （IP-base Smart Object）对设备模型进行了标准化，提供了一组轻量级设备管理和交互接口协议。

LwM2M 协议目前主要的实现是 C 语言的 Wakaama 和 Java 语言的 Leshan，相对来说应用还比较少。CoAP 协议的应用场景同样适合 LwM2M 协议，如果你希望在 CoAP 协议的基础上更方便地实现设备的管理，可以考虑 LwM2M 协议。

通信模式的共存

到这里呢，我就讲完了“发布 - 订阅模式”和“请求 - 响应模式”的各自特点，以及相应的网络协议。那你可能会问，有没有网络协议可以同时拥有这两种通信模式呢？有这种想法，说明你非常善于独立思考。确实，这样也更能满足实际场景的需求，而且从原理上看，也完全可以实现共存。

现在行业内也意识到了这一点，所以一些网络协议已经纷纷做出了尝试。比如 MQTT 5.0 中增加了请求 - 响应模式的新特性；AMQP 1.0 版本也定义了请求 - 响应模式。而 CoAP 协议呢，在新的初稿版本（Draft）中也增加了发布 - 订阅模式特性。

这种网络协议中的通信模式的共存，相比单一模式的设计都大大方便了具体场景中的应用，代表了一种网络协议的发展方向。

小结

在这一讲中，我对比了“发布 - 订阅”和“请求 - 响应”这两种主流的通信模式，并简单介绍了两种通信模式下的网络协议。

物联网应用是一个复杂的综合性系统，这要求你了解不同网络协议的功能特性和局限，并且为系统的不同部分做出合适的选择。这些选择原则，我总结为下面三点：

物联网设备通常需要运行在网络不太可靠的环境中，而且在功耗、体积和计算资源方面也有诸多限制，所以我们在设备的开发中可以考虑使用 MQTT 和 CoAP 协议。

云平台各服务之间需要快速、可靠地进行消息转发，这种情况可以选择 AMQP 协议。

一些应用需要兼容 Web 系统的 RESTful 架构，比如通过 REST 开放物联网中的资源能力，供其他应用调用，这时 HTTP 和 CoAP 协议是合适的选择。

我在这里再提供一个网络协议的思维导图，供你参考。

思考题

最后，我想给你留个问题。你还知道有哪些协议，适合用在物联网系统中吗？它们又是采用了哪种通信模式呢？

欢迎你在留言区写下你的答案，和我交流一下，也欢迎你将这一课分享给你的朋友，共同交流学习。

04 | 数据分析：数据的价值有哪些？

你好，我是郭朝斌。

在前两讲，我们了解了物联网设备层的通信技术和网络层的网络协议。设备借助通信技术，能接入网络；而统一的网络协议，保证了云平台能够“看懂”这些上报的数据。

但是，如果只是单纯地采集和存储的话，那么数据就只是数据库中的 0 和 1 ，很难谈得上有什么价值。某种程度上说，物联网系统的价值其实就在于数据的价值，而数据的价值则来源于我们对数据的分析和应用。

所以这一讲，我们来到了物联网数据流的第三站，应用层。开始下面的学习之前，你可以假设一下，如果你是物联网平台开发人员，你会怎么有效地分析和应用数据呢？

从数据的角度审视物联网

回答这个问题之前，我想先从数据的角度，带你对物联网体系结构再做一次梳理。

你可能有疑惑了，之前我们整理的物联网三大层面，不是也跟数据流向有关吗？是的，但是那三个层面是基于各部分的特性划分的，包含的技术内容更广。

而今天这一讲，我只专注数据这一个维度，对数据从“生产”到“消费”的过程做一个更细化的拆分和整理，得出一个技术体系。通过这个体系，你就能够对数据相关的技术有一个更加清晰的认识，而且学习过程中心里时刻有个宏观视角，不会“迷失”在各种技术的琐碎细节里面。

我先给出这张物联网数据分析的技术体系示意图，然后我会结合这张图片，从下往上分别讲解其中的每一步：

第一步：数据源数据采集

数据源数据采集，就是通过各种物联网设备，实现各种数据数字化的任务，同时也可能需要将数据临时存储起来，准备好数据的上传工作。

因为数据类型多种多样，比如我们常说的温度、湿度、甲醛含量，另外还有图像、声音、震动等等，所以采集这些数据的设备也具有明显的多样性。

除了传统的嵌入式系统涉及的软件和硬件开发外，传感器是这一部分需要重点关注的关键元器件。随着物联网的发展，新的应用在不断涌现，比如监测人体心率和血氧饱和度的可穿戴设备，以及在智慧农业中用来测量土壤反射率和植物颜色的光学传感器等等。

传感器是一个具有技术门槛的行业，高度依赖材料技术的进步。除了半导体和光纤这些相对传统的材料，现在一些纳米和生物材料的应用也发展很快。

采集完数据后，你就要利用网络将数据传输到云服务器。

第二步：数据传输

数据传输，就是把采集的数据快速地、可靠地上报到云平台。

这里的技术难点是实现高并发。因为物联网设备的数量巨大，而且它们在时刻不断地产生数据，所以海量的数据对云平台的性能构成了挑战。云平台只有具备高并发的能力，才能有效地、可靠地实现数据的传输。

高并发的实现，需要借助分布式的架构设计，同时使用负载均衡、消息队列和缓存等相关技术。我将在进阶篇针对这个话题再展开讲一讲。

第三步：数据存储

数据传输完成之后，你要面对问题是海量数据的存储。

比如，一辆共享单车一天会产生 1MB 的数据，如果按照 2000 万辆的保有量计算，每天就会产生 20TB 的新增数据。而在智慧城市的系统中，一个 800 万像素的摄像头，一个小时就能产生 3.6GB 的数据。那么一座城市，光是摄像头，一个月的数据量就可以达到数百 PB。

除了数据量大，数据的种类也很丰富，它们大体上可以分为 3 类，分别需要有不同的存储手段：

结构化数据，比如用户和设备的关系，用户信息、设备参数等。这类数据还是适合关系型数据库，那为了应对海量数据，你可以采用分布式数据库。另外，物联网中的传感器设备，随时间不断产生新数据。要存储这类数据，你可以选择时序数据库，来获得更高的读写和查询性能。

半结构化数据，比如 JSON 结构的数据，日志记录等。这些数据的存储一般采用 NoSQL 数据库产品。

非结构化数据，比如视频、音频等数据。它们一般采用文件的形式存储，Hadoop 体系中应用广泛的分布式文件系统 HDFS 是常用的选项。

关于数据存储的详细内容，我会在进阶篇为你进行剖析。

只有当实现了海量数据的存储，我们也才能为数据的处理打好基础。

第四步：数据处理

数据处理阶段，非常考验你对海量数据的处理能力，你需要用到大数据计算引擎。大数据的处理，从应用场景划分的话，可以分成两个大类：

批处理，顾名思义，它是对批量的数据进行统一处理的方法。比如我们需要知道一个月里，共享单车的总骑行时长和距离，骑行距离最远的单车等信息，就需要对所有单车的数据做汇总的计算，得出想要的结果。批处理的计算过程可能耗时较长，几分钟、几个小时，甚至几天都有可能。

流处理，它是对数据流做及时的处理计算。相比批处理，它具有低延时的特点，一般可以达到毫秒甚至微秒的级别。这样可以满足海量数据对于大吞吐量的处理能力的需求。比如温度、湿度数据基于高、低阈值的实时判断，就需要采用流处理的方式。

这里要说明一下，批处理延时高的主要原因，是大量数据需要从低速的磁盘中进行存取。它追求的目标和流处理一样，还是高效地处理和尽量简单地计算任务开发工作。

关于批处理和流处理的具体应用，我还会在进阶篇中为你进行讲解。

第五步：数据应用

数据应用处于整个体系的最顶端，是物联网系统的最终目的。我们关注的核心点是通过数据创造价值，而实现的基础是各种算法。

接下来，我就围绕价值和算法来讲解一下数据应用。

数据如何产生价值呢？

数据产生价值的方法，概况来说，可以分为 4 种：

可视化

挖掘

预测

控制决策

最直接的方法是可视化，也可以称为图表分析。

可视化可以非常直观地向人们展示数据的含义。比如共享单车的地理位置，如果给用户一个经纬度坐标，那无异于没有给这个信息；而在地图上以光标的方式显示出来，那用户就可以马上知道这个单车相对于自己的方位和距离。

类似的还有以曲线、柱状图表示数据变化趋势，以饼状图表示百分比数据，用网络图表示的事物关系等等。它们都可以为原始数据，和分析结果数据提供理想的展示手段。

比如我们可以查询共享单车近一年的开锁数据，然后基于这个数据，绘制一张开锁量的趋势曲线图。

另外，可视化的各种图表也可以作为我们人工分析数据的工具。

首先你需要从数据库或者 HDFS （Hadoop 的数据存储格式）中，把数据提取到 Excel 表格（或者 CSV 文件）里面；然后你可以在 Excel 软件中，使用数值计算函数得到总和、平均值等统计数值，或者使用数据透视表等功能得到新的统计表格。

除了 Excel，我们也可以利用其它图表软件来进行分析，比如 Gephi 和 NetMiner。

比图表分析更进一步的方法是挖掘分析。

我们可以基于各类算法来分析数据的规律和关联关系等。这种方法基于各种现成的模型，或者自定义的模型，可以从数据中提取出人工分析无法看出来的、隐藏着的规律和联系。

比如那个经常被提到的啤酒和尿布的例子，美国沃尔玛超市通过分析销售数据发现啤酒和尿布经常被一起购买，所以调整货架，将啤酒和尿布放在一起来促进销量（有说法认为，这是由于美国年轻的爸爸出门买尿布的时候，经常顺便买几瓶啤酒犒劳自己）。如果不是挖掘出这个隐藏的联系，我们很难凭直觉得出这个结论，这也体现了挖掘分析的价值。

还有一种进阶的方法是预测分析。

随着积累的数据和经验越来越多，我们可以用算法来构建预测模型。当新收集的数据输入给模型时，我们就可以预测出未来的状况。

还是以共享单车做例子，当单车连接到网络平台之后，共享单车出现了。走在路上，掏出兜里的手机，你就可以打开一辆单车的锁，自由骑行；到达目的地后，锁上单车，计费马上完成；你还可以在手机上查看这次骑行的花费时间和骑行速度，甚至计算出你“燃烧的卡路里”。

为什么大部分时候，共享单车可以恰巧出现在你需要它的地方呢？

因为共享单车的系统平台有充足的信息，可以分析出城市的热点区域在哪里，并且预测出第二天每个区域的单车需求量。运营人员正是基于这些数据，提前确定在什么时候、什么地点、投放多少辆车。

最后一种方法是控制决策。

物联网设备可能具备执行器，物联网系统可以基于业务目标控制这些执行器。而控制决策是采集的数据基于算法计算得到的。

比如在农业中，有经验的种植能手会观察日照、土壤等条件，然后确定施肥、灌溉的节奏。那智慧农业呢，就需要在系统中把这种经验转化为专家系统。只要获得光照强度、水分、土壤成分等数据，你就可以根据专家系统的计算结果，控制水泵和卷帘等设备。

数据分析可以使用的算法有哪些？

挖掘分析、预测分析和控制决策要用到统计分析、机器学习，包括人工智能的各种算法。这些算法分为监督学习和非监督学习两类。

监督学习算法需要我们像尽心尽责的父母，明确告诉它“什么是正确的”，“什么是错误的”。

非监督学习算法面对的是“放羊式”父母，需要自己将数据中的“异常值”区分出来。

常见的监督学习算法有决策树和 SVM（支持向量机）算法等。决策树类似我上面描述的专家系统；SVM 算法是一种分类算法，也可以用于线性和非线性回归问题，比如基于骑行习惯构建用户的画像。

非监督学习算法有 K-means 算法等。K-means 算法是聚类算法，比如，基于共享单车的使用次数和时间段，我们可以区分出故障车和正常单车。

这里，我没有提到强化学习、半监督学习等概念，毕竟它们不是这一讲的重点，简单的两句介绍可能让你增加更多疑惑。总之，使用算法的一个原则是，你先要分析数据的特征，掌握大体的倾向，然后结合你要实现的目标，再选择合适的算法。

小结

总结一下，这一讲里，我展开介绍了数据应用涉及的分析方法和相关算法。

数据的分析方法可以分为：可视化、挖掘分析、预测分析和控制决策；

具体的分析中会用到统计分析、机器学习和人工智能的各种算法，这些算法大体可以分为监督学习和非监督学习两类。

算法选择的一个重要原则是，先使用一些可视化的工具分析数据的特征，掌握数据的大体倾向，然后结合业务目标或者控制目标，选择合适的算法。

数据分析和应用是物联网系统中数据流的最后一站。通过数据分析，海量的数据为用户带来了巨大的价值。

数据的价值，首先当然是商业价值，比如超市中的电子价签，取代之前的纸质价签，超市的店面运营减少了很大的人力投入，而且提高了促销活动的效率，降低了价格标错的可能。但这不是全部，物联网数据的有效利用还会带来巨大的社会效益。

为了讲清楚数据分析应用的方法，我还从数据的角度，对物联网系统再次做了一个梳理，整理出了物联网系统中数据的技术体系。它可以分为 5 个部分，分别是数据源数据采集、数据传输、数据存储、数据处理和数据应用。

可能你也发现了，物联网的数据技术体系和大数据的体系有很多相似的地方。确实是这样，物联网的数据系统本身也是一个大数据系统，必然会用到很多传统的大数据技术，比如数据存储、数据处理相关的技术。

但是物联网系统也有自身的特点，所以在数据采集上会涉及嵌入式系统和传感器的技术；在数据传输中会用到特殊的网络协议；在数据应用中需要关注控制决策相关的技术。这些是你需要注意区分的地方。

总之，在物联网中，海量的数据将会远远超过当前互联网的数据规模。这必然对数据的存储、读取、分析处理技术提出新的挑战。相关的技术呢，我会在进阶篇中继续做深入的剖析。

这里，我再提供一个物联网数据技术的思维导图，供你参考。

思考题

最后，给你留个思考题吧。

在这一讲中，我提到了数据处理可分为批处理和流处理，并简单介绍了它们各自的特点。除了课程中的例子，你认为它们还适合哪些场景呢？

欢迎在留言区写一写你的想法，和我交流一下。也欢迎你将这节课分享给你的朋友一起学习。

05 | 系统实例：怎样设计一个简易物联网系统？

你好，我是郭朝斌。

通过前几讲的学习，你应该已经熟悉了物联网系统的基本框架，并且知道设备层、网络层和应用层大概是怎么回事儿。不过你现在的理解，可能还停留在盲人摸象的阶段，虽然已经可以说出某些局部的特点，但是还没法儿准确地画出整头大象的样子。

假如现在老板给你布置一个任务，让你设计一个物联网系统，你是胸有成竹，还是手足无措呢？为了让你能够拍着胸脯说“保证完成任务”，今天这一讲，我会教你怎么设计一个简易的物联网系统。

那么，我们拿什么项目来练手呢？我想，还是智能家居比较合适。一方面是因为它跟你的生活关系比较密切，你对它的感知比较强；另一方面，你正好可以在这个基础上完成实战训练。

这也是受到扎克伯格的启发。他在 2016 年时计划自己动手打造一个 Jarvis 系统（《钢铁侠》中的智能助手贾维斯），能够通过语音 / 文字控制家里的电器。经过近一年的努力，到年底时他基本完成了这个系统，并且写文章描述了具体的工作（如果你打不开，可以看这篇）。

课程学完之后，你再邀请朋友来家里玩，就可以展示自己亲自动手完成的一套智能家居设备，而且新的奇思妙想也能够随时动手实现出来。

想象一下，到时候你的房间也可以是一座“未来科技城”，你也可以在朋友圈“晒出”像扎克伯格一样的科技作品，这是不是很酷呢？

智能家居的发展阶段

要实现一个酷炫的智能家居项目，你就得知道智能家居现在发展到了什么阶段。如果场景设计太超前了，好高骛远，那么最后根本实现不了；而如果场景设计太落后了，都是人家早就玩剩下的，那么也就不酷了。

从自动化程度的角度分析，智能家居的发展可以分为遥控、场景联动和智能化三个阶段。这是一个智能设备和家居控制系统越来越自主，而人的参与越来越少的过程。

遥控

你可能马上就想到了电视和空调的遥控器。不过，通过 Wi-Fi、BLE 和 ZigBee 等技术手段联网的设备，还可以有多种多样的遥控方式。智能手机上的 App 和手机系统的语音助手，甚至智能音箱，说到底都是遥控的方式。

相信你也注意到了，在遥控阶段，每次跟设备的交互都需要人的参与。比如扎克伯格的智能电灯，就需要他通过语音来控制开关。

扎克伯格语音控制电灯开关（引自光谱的报道）

场景联动

在场景联动阶段，人只需要参与一次跟设备的交互就行了。你只要设置好联动的逻辑，包括触发条件和执行动作，后续的多个联网设备就会根据这个逻辑依次运行，实现一定程度的自动化。

扎克伯格在介绍中提到，他家的系统可以识别门口摄像头捕捉的人脸图片，然后根据日程和访客列表判断要不要开门，并且通知他本人。这就是控制系统、摄像头、门锁和线上日程系统的联动所实现的功能。

扎克伯格的智能家居系统识别访客（引自Margi Murphy的报道）

智能化

随着智能家居接入的设备越来越多，它们产生的各类数据越来越多，智能家居自然会发展到智能化的阶段。这时候，人已经不需要再跟设备频繁交互了。家居系统会从你之前的交互中和其他设备收集的数据中学习你的行为模式和喜好，然后控制设备自动处理很多事情，包括提供决策建议。

我还是以扎克伯格的智能家居系统为例。如果他去国会参加听证会那天，他的 T 恤加农炮不是射出一件灰色 T 恤衫，而是提醒他“今天适合穿西装打领带出席会议”，那就达到智能化水平了。不过，现在的智能水平还做不到这一点。

扎克伯格的“T恤加农炮”（引自Margi Murphy的报道）

所以现在智能家居正处在场景联动阶段，我们已经可以把一些智能产品组合起来，实现一些生活场景的自动化。如果你有兴趣，可以看看这个视频，它比较好地体现了智能家居的发展现状。

智能家居产品的设计原则

那么，在目前这个场景联动的阶段，我们要怎么围绕生活场景来设计智能家居产品呢？你只要遵守 3 个原则就行了：

专注单一领域，解决一个问题。这样问题容易定义，解决方案涉及的技术也就比较少。比如智能照明，解决电灯的联网和开闭功能就可以了。

闭环，也就是同时包含传感器、执行器和控制器。这样设备自身就可以实现一定程度的自动化。比如根据光照自动调节灯光就是一个完整的闭环。

可以实现。产品不要包含很难实现的需求或者还没有经过验证的技术，不然要么计划很容易搁浅，要么做到最后发现白白浪费了巨大的资源。

所以，我建议你做这 4 个产品场景：可以手机控制的智能电灯、可以基于光线自动调节的智能电灯、可以语音控制的智能音箱和可以基于环境温湿度和土壤湿度自动浇水的浇花器。它们在市场上都有成熟的产品代表，证明需求是真实存在且可以实现的。

智能家居场景的简易物联网系统

确定了要做的产品场景，你离物联网系统的设计又进了一步。现在你需要对这些场景进行拆解，看看它们包含的具体组件是什么，以及这些组件分别属于物联网系统的什么部分。

我以智能电灯为例，给你做一个拆解。

传感器

为了实现基于光线强度自动调节电灯打开、关闭和亮度的智能电灯，我们需要使用光照传感器。

传感器是物联网中的设备感知环境和自身状态的重要组件。在智能家居中，传感器还用于感知温度、湿度、声音、图像、气体等很多信息。比如，智能音箱中的麦克风是一种声音传感器。

执行器

我们要实现电灯的打开和关闭，也就是控制电路的通断，这时我们需要继电器。

继电器这类执行具体控制动作的组件，被称为执行器。除了电气的开闭，执行器也可以完成机械的动作和加热、制冷等功能。比如扎克伯格的系统中，空调系统、面包机和 T 恤加农炮都是执行器的范畴。

控制器

那给执行器控制命令的组件呢，就是控制器。在智能电灯中，我们使用 NodeMCU 开发板来作为控制器。

控制器是执行具体应用逻辑的单元。简单理解的话，可以认为控制器基于传感器的数据，执行应用逻辑，然后驱动执行器做出反应。控制器是一个抽象概念，它可能在网关或者边缘计算设备实现，也可能在云端的应用服务器实现。有些复杂的控制逻辑可能会涉及到大数据处理技术或者机器学习算法。

家庭网关

光照传感器为了省电，我们采用 BLE（低功耗蓝牙）技术。不过 BLE 设备需要借助桥接设备才能接入互联网，这个桥接设备被称为家庭网关。顺便提一句，一般 ZigBee 设备也需要网关来接入物联网。（需要说明的是，在实践中家庭网关的功能有可能是其他设备来承担，比如电视、台灯等。在我们的设计中，可以使用智能音箱来兼做家庭网关。）

用户界面

为了控制智能电灯的亮度和开关，我们需要使用智能手机或者智能音箱来输入执行的命令，同时通过它们来查看电灯的状态信息、设置联动场景。

这些人与设备，或者人与智能家居系统的交互手段就是用户界面。类似的还有电视屏幕、控制面板等。在未来，可能不会有特定的设备作为用户界面，用户界面将无处不在，就像《钢铁侠》里面的场景，你随时可以说话，显示信息悬浮在空中。目前主要的方式还是使用手机，基于语音或者文字来交互。

当我们按照同样的方法，拆解完这 4 个场景的基本构成组件，智能家居系统的架构图也就清晰了。

你可以看到我们最终将会打造 6 个硬件设备，它们分别是：智能电灯、光照传感器、环境温湿度传感器、土壤湿度传感器、自动浇花器和智能音箱。其中，智能音箱也承担家庭网关的功能。

我把这 6 个硬件需要的元器件整理到下面的表格中，供你参考。（如果需要，你可以自行逐一采购，也可以在极客商城一站式配齐。）

简易物联网系统的技术架构图

现在，我们把上面设计的智能家居系统的架构，代入到物联网的三层体系结构中，就得到了它的技术架构图。

设备层

传感器、执行器和控制器三个组件相互配合，构成完整的设备功能。而在智能家居中，通信技术的主流选择是 Wi-Fi、BLE（低功耗蓝牙）等。Wi-Fi 适合带电设备，BLE 适合电池供电的低功耗设备。

网络层

家庭网关可以让使用 BLE 技术的设备接入物联网。然后，网关通过 MQTT 协议连接云平台，让这些设备可以和云平台通信。

在智能家居应用中，为了自动发现设备和服务，UPnP 协议和 mDNS 协议也经常被使用。在进阶篇第 7 讲，我会详细讲解组网相关协议。

应用层

之前的课程中，我提到过云平台是数据存储和数据分析处理的支撑平台。同时，云平台也可以为你的 App 提供 API 接口，让你可以借助手机远程操控联网设备。

在实战篇，我们将在云平台中，通过 MQTT Broker 来接收温度、湿度等数据，并且开发 Web 服务，通过页面来展示相关数据。

小结

接下来，我总结一下这一讲的主要内容。

从自动化程度看，智能家居系统分为遥控、联动控制和智能化三个阶段。目前的智能家居系统已经可以达到场景联动的阶段，而未来需要关注的发展趋势是智能化。

围绕着遥控和联动控制的功能实现，我们设计了 4 个产品场景，它们分别是：可以手机控制的智能电灯、可以基于光线自动调节的智能电灯、可以语音控制的智能音箱和可以基于环境温湿度和土壤湿度自动浇水的浇花器。

在 4 个产品场景的实现过程中，你需要打造 6 个硬件设备，它们分别是：智能电灯、光照传感器、环境温湿度传感器、土壤湿度传感器、自动浇花器和智能音箱（兼做家庭网关）。

通过梳理整个简易智能家居系统，我们可以发现智能家居包括传感器、执行器、控制器、家庭网关和用户界面这几个构成部分，你可以结合我们设计的简易系统来了解各部分的功能。

从简易智能家居系统的设计过程，你应该也可以总结出物联网系统设计的一般过程。

首先，我们需要调查、分析具体领域的知识和发展趋势；

接着，确定我们要试图提供解决方案的应用场景；

然后，设计应用场景中的具体产品，包括产品的功能、使用的通信技术和网络协议等；

最后，我们可以总结解决方案的各个部分，并且与物联网三层体系结构做对应，检视解决方案是否完整、是否合理。

除了以上这些，我还想跟你说，这一讲提到的树莓派（Raspberry Pi）开发板、NodeMCU 开发板都是主流的开源硬件。开源硬件的流行对智能硬件的发展有着巨大的推进作用。如果你是一个电子发烧友（DIY），那么现在你很容易就可以基于开源硬件组合出一套简易的联网设备，也许你已经做了类似的尝试。

所以我觉得，如果只是单纯指导你一步步地实现这套系统，那么意义还不够大，也不是我的出发点。我会在后面的章节中结合物联网的具体场景（智能家居）来把物联网的相关技术和体系结构讲解清楚。

我很欣赏一句话，叫“知行合一”。当你学习一个新东西的时候，如果只是看别人做、听别人说，那都是输入，并不会很好地内化为自己的东西。所以第一步输入之后，第二步要认真思考，最后你要做输出，把你学习到的东西讲给别人就是一种输出。

而且编程和物联网开发最好的输出方式还是要动手实践。在动手的过程中你可以真正把底层原理和核心知识理解透彻，内化成你自己的知识，从而在工作中更加得心应手。这就是我们要在实战篇达到的目的。

思考题

最后，我给你留一个思考题吧。

在简易物联网系统中，我们设计了智能电灯和自动浇花器等应用场景。扎克伯格的实践中也有一些很酷的应用。你还可以为自己的家居环境想到什么场景和应用呢？另外，在本讲中，我拆解了智能电灯的构成组件，你可以使用同样的方法，拆解一下自动浇花器的组件吗？

欢迎在留言区写一写你的奇思妙想，和我一起交流一下。也欢迎你将这个课程分享给你的朋友一起交流学习。

06 | 物模型：如何定义智能电灯？

你好，我是郭朝斌。

在基础篇最后一讲的智能家居项目里，我们设计了几个小场景，其中就包括智能电灯。如果你只是想自娱自乐，做一个可以用手机 App 控制的电灯，那么只要通过代码实现控制功能就足够了。至于是怎么控制的，电灯有什么状态上报，你自己知道就行了。

但是，如果你想让智能电灯真正成为物联网系统的一部分，那就不仅仅是在封闭的、确定的场景下写几行代码的事儿了。在物联网平台上，可能有其他人开发的应用需要显示你的智能电灯的状态；也可能有别的设备，比如光照传感器、智能音箱，在场景联动中要控制灯的状态。

所以，你需要把控制电灯打开和关闭的方法，告诉这些应用和产品的开发人员。同时，这些开发人员也需要了解，智能电灯的状态信息如何获取和解析。那么，你面临的第一个问题就是，用什么方式提供这些接口信息呢？

另外，市面上不止一款智能电灯，如果要一一适配，那工作量肯定很大，而且扩展起来会很困难。那么，你面临的第二个问题就是，平台应用如何避免针对每款智能灯进行定制开发呢？

计算机领域的软件体系结构采用的是一种层的结构，所以有人说过这么一句名言：“计算机科学领域的任何问题，都可以通过增加一个间接的中间层来解决。”

按照这个思路，我们就可以在智能电灯实体和平台之间，增加一层标准规范来解决这些问题。就像，你使用不同的浏览器访问极客时间的网站，都可以看到课程的文本、音频、视频等内容，因为这些内容都是基于 HTML （HyperText Markup Language，超文本标记语言）等规范组织的。

物联网中的这层规范就是 Thing Specification Language，简称 TSL。使用 TSL 描述的物联网中的实体模型，就是“物模型”，或者叫做“产品模型”，也有叫“数据模板”的。不过，我认为“物模型”更有物联网专属的感觉，所以在咱们这门课里我都会用“物模型”这个叫法。

物模型和设备的关系是什么？

物模型是物理世界的实体东西的一个抽象，是进行数字化描述后，用于数字世界的数字模型。这么说可能有点绕，更直接一点说就是，物模型是使用计算机可以理解的语言，说清楚这个产品是什么、能做什么事情，以及可以提供哪些信息。

而抽象就是要提取出产品的共同特征，形成模型。以智能灯为例，不同的灯，尽管规格不同，但它们的属性是相似，比如都有开关状态的属性，功能逻辑也相仿。我们可以将这些特征标准化，形成智能灯的物模型。

反过来，物模型也规约了设备的功能。新增加的设备，如果是同一类型的，在设计、研发中，会遵循相同的功能定义，有相同的特征，实现相同的服务。比如，灯都应该有“开”和“关”两种状态。

为什么要使用物模型？

基于共同的抽象特征，物模型可以让应用程序不再针对一个个的产品设备，而是同一类设备采用相同的处理逻辑。这实际上是应用开发的基础。当烟感传感器的数值触发报警时，即使是不同品牌的烟感产品，应用程序也可以对数值做相同的处理和判断，否则只能分别进行数值分析。

另外，物模型中，设备的功能是明确定义的，可以方便地实现场景联动。比如，光线传感器可以基于光照强度，向智能电灯发送亮度的控制命令，或者开和关的命令。

如何定义物模型？

那么，如何定义智能电灯的物模型呢？这里我想告诉你结论，我们一般是通过属性、事件和动作这三种功能元素来定义。接下来，我就一一和你介绍。

我们知道，智能电灯的状态，要么是打开，要么是关闭；当进行控制时，这两种状态还会相互转换。此外，有些灯还可以根据需求设置不同的亮度、颜色和色温等。

它们的共同点就是，都描述了产品设备运行时的某种状态，我们用属性（Property）来表示。

属性的特点是可读可写。也就是说，应用程序可以读取属性，也可以设置设备的属性。我们还可以看到类似的例子，比如环境监测设备的温度、湿度这两个属性等。

如果智能电灯在运行过程中，出现了低电压的情况，或者发生了硬件故障，那么联网的设备可以将这些信息发送出去，通知你来及时作出处理。

这类由产品设备在运行过程中产生的信息、告警和故障等，就是事件（Event）。

一个事件可以包含多个输出参数。事件不同于属性，事件是设备上报的，不能由应用来设置。类似的例子，还有某任务完成时的消息，环境传感器检测到污染物的告警等。

我们再看生活中关于灯的一个使用场景：第一次约会的时候，你希望灯能够烘托出浪漫的气氛，就要调节灯的颜色、亮度和色温。如果分别设置属性，将会非常繁琐，这时你会想到要为灯增加一个场景模式的功能，一个命令就可以设置到浪漫模式。

这种设备可以被调用的能力或者方法，就是动作（Action），也被称作服务（Service）。

动作由应用下发给设备，设备可以返回结果给应用。从执行的流程看，动作还可以进一步分为同步和异步。这取决于动作是否是个耗时的操作，以及其他应用逻辑对于动作执行结果的依赖关系。

你可能想，设置属性也可以改变设备的状态，那它们的区别是什么呢？相比于属性，动作是应用下发到设备的控制命令；动作可通过一条指令实现更复杂的业务逻辑，比如，调低温度 5 度，旋转摄像头 30°等。

到这里，我们定义了属性、事件和动作这三类功能，也就完成了物模型的定义。

接下来，我们要做的是通过数据来描述它们。和编程语言一样，作为一种模型语言，物模型的数据也有不同的数据类型。它们主要包括六种：

布尔型（Bool）：非真即假的二值型变量。例如，开关功能只有开、关两种状态。

整数型（Int）：可用于线性调节的整数变量。例如，电灯的亮度是一个整数范围。

字符串型（String）：以字符串形式表达的功能点。例如，灯的位置。

浮点型（Float）：精度为浮点型的功能点。例如，电压值的范围是 0.0 - 24.0。

枚举型（Enum）：自定义的有限集合值。例如，灯的颜色有白色、红色、黄色等。

时间型（Timestamp）：String 类型的 UTC 时间戳。

对于整数型、浮点型的数值，它们的单位可以是百分比、电压、米等。

物模型一般是用 JSON 格式来表述模型元素。JSON 是 Web 开发中，经常使用的数据格式，相比于 XML，它更加简洁、清晰，也更轻量级。

在实践中，你手动写完 JSON 格式的物模型后，可以使用检测工具来验证语法是否正确，比如，在线检测工具 JSON Schema Lint。

接下来，我们就按照属性、事件、动作 / 服务这三个要素，一起看看如何用 JSON 格式来定义智能电灯的物模型吧。

定义智能电灯的物模型

智能电灯的开关属性是布尔类型，是必须有的属性。它可以通过 JSON 表述如下：

{
      "id": "power_switch",   //属性的唯一标识
      "name": "电灯开关",      //名称
      "desc": "控制电灯开灭",   //属性的详细描述
      "required": true,       //表示此属性是否必需包含，是
      "mode": "rw",           //属性的模式，r代表读，w代表写
      "define": {             //属性的数值定义
        "type": "bool",       //数值的类型，布尔
        "mapping": {          //具体数值的含义
          "0": "关",           //0表示灯关闭
          "1": "开"            //1表示灯打开
        }
      }
    }

智能电灯的电压是需要监控的数值，当电压低时，可以上报这个事件。这个事件有一个参数，即电压值，数据类型是浮点类型。JSON 格式的描述如下：

{
      "id": "low_voltage",      //事件唯一标识
      "name": "LowVoltage",      //事件名称
      "desc": "Alert for device voltage is low",  //事件的描述
      "type": "alert",          //事件的类型，告警
      "required": false,        //表示此属性是否必需包含，否
      "params": [                //事件的参数
        {
          "id": "voltage",        //事件参数的唯一标识
          "name": "Voltage",      //事件参数的名称
          "desc": "Current voltage",  //参数的描述
          "define": {                 //参数的数值定义
            "type": "float",          //数值类型，浮点数
            "unit": "V",              //数值的单位，伏
            "step": "1",              //数值变化的步长，1
            "min": "0.0",              //数值的最小值
            "max": "24.0",             //数值的最大值
            "start": "1"                //事件的起始值
          }
        }
      ]
    }

动作的定义，和属性、事件的定义过程类似，这里我就不再单独解释了。我们直接将所有属性、事件和动作合并，就得到了智能电灯物模型的完整 JSON 格式：

{
  "version": "1.0",            //模型版本
  "properties": [              //属性列表
    {
      "id": "power_switch",    //电灯开关属性
      "name": "电灯开关",
      "desc": "控制电灯开灭",
      "required": true,
      "mode": "rw",
      "define": {
        "type": "bool",
        "mapping": {
          "0": "关",
          "1": "开"
        }
      }
    },
    {
      "id": "brightness",        //亮度属性
      "name": "亮度",
      "desc": "灯光亮度",
      "mode": "rw",
      "define": {
        "type": "int",
        "unit": "%",
        "step": "1",
        "min": "0",
        "max": "100",
        "start": "1"
      }
    },
    {
      "id": "color",            //电灯颜色属性
      "name": "颜色",
      "desc": "灯光颜色",
      "mode": "rw",
      "define": {
        "type": "enum",
        "mapping": {
          "0": "Red",
          "1": "Green",
          "2": "Blue"
        }
      }
    },
    {
      "id": "color_temp",        //色温属性
      "name": "色温",
      "desc": "灯光冷暖",
      "mode": "rw",
      "define": {
        "type": "int",
        "min": "0",
        "max": "100",
        "start": "0",
        "step": "10",
        "unit": "%"
      }
    }
  ],
  "events": [                        //事件列表
    {
      "id": "status_report",          //运行状态报告
      "name": "DeviceStatus",
      "desc": "Report the device status",
      "type": "info",
      "required": false,
      "params": [                      //事件参数列表
        {
          "id": "status",
          "name": "running_state",
          "desc": "Report current device running state",
          "define": {
            "type": "bool",
            "mapping": {
              "0": "normal",
              "1": "fault"
            }
          }
        },
        {
          "id": "message",
          "name": "Message",
          "desc": "Some extra message",
          "define": {
            "type": "string",
            "min": "0",
            "max": "64"
          }
        }
      ]
    },
    {
      "id": "low_voltage",            //低电压告警事件
      "name": "LowVoltage",
      "desc": "Alert for device voltage is low",
      "type": "alert",
      "required": false,
      "params": [
        {
          "id": "voltage",
          "name": "Voltage",
          "desc": "Current voltage",
          "define": {
            "type": "float",
            "unit": "V",
            "step": "1",
            "min": "0.0",
            "max": "24.0",
            "start": "1"
          }
        }
      ]
    },
    {
      "id": "hardware_fault",            //硬件错误事件
      "name": "Hardware_fault",
      "desc": "Report hardware fault",
      "type": "fault",
      "required": false,
      "params": [
        {
          "id": "name",
          "name": "Name",
          "desc": "Name like: memory,tf card, censors ...",
          "define": {
            "type": "string",
            "min": "0",
            "max": "64"
          }
        },
        {
          "id": "error_code",
          "name": "Error_Code",
          "desc": "Error code for fault",
          "define": {
            "type": "int",
            "unit": "",
            "step": "1",
            "min": "0",
            "max": "2000",
            "start": "1"
          }
        }
      ]
    }
  ],
  "actions": [],                  //动作列表
  "profile": {                    //产品参数
    "ProductId": "8D1GQLE4VA",    //产品ID
    "CategoryId": "141"            //产品分类编号
  }
}

每个模型都要从头定义吗？

那我们在创建自己的新模型时，是不是每次都需要从头定义这些属性、事件和动作呢？有没有更简便的方式呢？答案当然是有的。

创建模型的时候，有拷贝和继承两种模式，这两种创建模式的不同主要体现在模型关系上。

“拷贝”模式类似于编程语言中的值拷贝，新建模型与被拷贝模型有完全相同的三元素，两个模型相互独立，模型变更互不影响。

“继承”模式就是面向对象编程中的继承概念，新建模型被定义为“子模型”，被继承的模型定义为“父模型”。

继承的具体特征是：

子模型继承父模型的所有要素，且继承的元素无法被修改。

子模型可以再被继承，支持多层的继承关系。

子模型可以创建独立的要素，但子模型中新增的要素不可以和所有上级父模型中的元素重名。

当父模型中的元素发生变更时，子模型中继承自父模型的元素同步变更，保持与父模型一致。

以我们刚刚定义的智能电灯的物模型为例，如果要增加安装位置的属性，可以继承已有的模型，然后再增加安装位置的属性。（注意：下面的 JSON 表述省略了与父模型重复的内容。）

{
...
{
      "id": "name",                  //灯位置属性
      "name": "灯位置名称",
      "desc": "灯位置名称：书房、客厅等",
      "mode": "rw",
      "required": false,
      "define": {
        "type": "string",
        "min": "0",
        "max": "64"
      }
    }
...
}

到这里，我们已经了解了物模型，并且完整实践了一遍物模型的创建。接下来，我给你延伸一下，讲两个和物模型相关的概念。

物模型的拓展应用

你也许听到过“设备影子”和“数字孪生”这两个概念，它们和我们这里说的“物模型”是什么关系呢？

设备影子

设备影子用于缓存设备状态。应用程序可以通过设备影子直接获取设备最后一次更新的属性值，而无需每次都访问设备。设备在线时，可以直接获取应用指令；设备离线后，再次上线可以主动拉取应用指令。

我们可以再想象一个场景。如果设备网络稳定，很多应用程序请求获取设备状态，设备需要根据请求响应多次，即使响应的结果是一样的。但是可能设备本身处理能力有限，其实无法负载被请求多次的情况。

使用设备影子机制，设备只需要主动同步状态给设备影子一次，多个应用程序请求设备影子获取设备状态，即可获取设备最新状态，做到应用程序和设备的解耦。

再比如，智能电灯的开关状态这个属性，手机 App 可以远程控制，你也可以在本地通过物理开关改变。如果网络不稳定，那么平台上存储的状态，和电灯设备的真实状态可能会不一致，导致后续操作逻辑错误。

设备影子可以通过双向的同步，实现服务器端和设备端属性的一致，从而解决这个问题。

数字孪生（Digital Twin）

物模型是物理实体的数字化模型，但主要针对的是物联网中应用的开发和设备的互操作。

这个模型如果更进一步，集成了物理实体的各类数据，那就是物理实体的忠实映射。同时，在物理实体的整个生命周期中，它会和实体一起进化，积累各种信息和知识，并且促进物理实体的优化。这样的模型就是物理实体的数字孪生。

在工业物联网领域，这个概念已经有了很多的探讨和应用。

比如，特斯拉公司为其生产的每一辆电动汽车都建立了数字孪生模型，相关的模型数据保存在公司的数据库中，以便在测试中排查故障，为用户提供更好的服务。

小结

总结一下，在这一讲中，我通过智能电灯的例子讲解了物模型。

物模型是物理世界中产品设备的数字化模型，它对设备的共同特征进行了抽象，同时规约了设备的设计。

物模型一般是使用 TSL 描述的 JSON 格式文件。

物模型包括属性、事件和动作三个功能元素。其中，属性可读可写；事件可以包括多个参数；动作包括应用下发的命令，和设备返回的响应信息

在实践中，定义物模型时，你需要注意物模型三个功能元素的区别，尤其要了解属性和动作的联系和不同。不好的定义会给功能实现带来困难，比如，将智能电灯的“开”和“关”，定义为两个不同的动作。

物模型在物联网系统开发中，作用重大，它为应用开发提供了统一的数据模板，方便了场景联动的实现，同时，为平台上实现设备影子提供了基础。

类似地，数字孪生也正是建立在物理实体的数字模型之上的重要技术方向。这里作为一个引子，有兴趣的话，你可以深入了解一下，也许对你在工作中做系统设计有帮助。

思考题

最后，我还是给你留个思考题作为结尾。

物模型是实战开发的基础，咱们最后再通过一个练习来强化下学习效果吧。请你定义一个环境温湿度传感器的物模型。你可以从属性、事件、动作三个元素的角度思考一下，而且一定要动手写一写。

欢迎在留言区写出你的答案，和我一起交流一下。在后面的实战中，我们也会涉及到温湿度传感器的物模型。如果你的朋友对物联网有兴趣，也欢迎你将这个课程分享给他们一起学习进步。

07 | 零配置组网：设备如何发现彼此？

你好，我是郭朝斌。

不知道你还记不记得，在基础篇的第 2 讲中，我介绍了 Wi-Fi 设备的配网方式，比如一键配网技术（Smart Config）和设备热点配网技术。这些技术已经可以实现一定程度的自动化，让设备比较方便地连接上 Wi-Fi 热点。

同时，我也提到了零配置配网方式，它试图通过已连接上 Wi-Fi 热点的设备，来实现可信任设备完全自动化的配网。

但是，你可不要把零配置配网（Zero Configuration Provisioning）和我们这一讲要谈的零配置组网（Zero Configuration Networking ，ZEROCONF）混淆了。配网只是第一步，因为物联网设备无法方便地输入信息，所以相比手机或电脑，我们引入了这个额外的操作步骤。

配网成功之后，我们还需要组网，也就是让设备获得一个自己的 IP 地址，同时也知道局域网内的路由器（Router）的 IP 地址和 DNS（Domain Name System）的 IP 地址等信息。设备自己的 IP 地址是它在 TCP/IP 网络中的唯一标识；路由器可以把设备的数据包正确地转发出去；而 DNS 服务器可以帮忙解析出数据包中需要设置的目的地 IP 地址。

温馨提示：如果你对 IP 地址和 DNS 这样的基本概念还不是很熟，可以学习《趣谈网络协议》等课程临时恶补一下。因为这一讲还会涉及不少网络基础知识，对它们有一定了解的话，你的学习效果会更好。

零配置组网就是把这些工作自动化，达到不需要用户手动去操作，甚至实现这个设备可以与网络内的其他设备配合工作的目的。

比如，你要为自己的智能家居系统加一个投屏设备（它可以外接普通电视或者显示器，让你可以将手机或者电脑的屏幕在大屏上显示出来）。你希望这个投屏设备接入 Wi-Fi 热点之后，你就可以在手机上直接发现它，并且马上开始投屏，看自己想看的电影。

这个想法非常美，但是你知道要怎么实现吗？其实并不难，你只要掌握我在这一讲介绍的零配置组网就可以了。

设备如何获取 IP 地址？

零配置组网的第一步，就是自动分配 IP 地址。

不知道你以前有没有这样的经验，在学校宿舍或者公司，当你的电脑通过网线连接网络时（现在用网线连网的方式已经不那么常见了），有时电脑桌面右下角会弹出气泡，提示“ IP 地址冲突”。

这是因为你的电脑的 IP 地址，被网络内其他电脑占用了。所以你就需要打开网络配置的界面，手动填写一个不同的 IP 地址。对于非专业人士来说，这并不是一个很简单的事情；而且对于特殊的子网掩码（比如不像“255.255.255.0”这样整齐，而是“255.255.253.0”这种），即使有经验的用户，可能也很难确定子网号和主机号分别是什么。

现在随着 Wi-Fi 的普及，我们连网的时候基本上不会再碰到这种情况了。这一方面是因为每一个 Wi-Fi 热点接入的设备数量是有限的，另一方面则是因为 Wi-Fi 路由器提供了完善的 IP 地址自动分配功能。

DHCP 协议

这个自动分配功能是基于 DHCP 协议（Dynamic Host Configuration Protocol，动态主机配置协议）实现的。DHCP 协议的前身是 BOOTP 协议（Bootstrap Protocol），从“Bootstrap”这个名字你就可以看出来，它解决的正是一个设备接入 IP 网络后，需要完成的第一件事——获取 IP 地址。

在网络发展的初期，接入网络的设备很少，所以，网络地址的分配和设置都是管理员手动来完成的。你可能想象不到，最初整个互联网的 DNS 数据库都是手动维护和更新的。

但是随着接入网络的设备越来越多，而且设备更加便携，经常移动来移动去，手动的方式就跟不上时代发展了。DHCP 协议正是在这种背景下出现的。

也许你对 DHCP 的名字很熟悉，但我还是想在这里稍微展开一下，带你了解它背后的工作原理，这样你就能在开发中更好地使用它。

DHCP 使用了服务器 - 客户端的架构模型。

当一个设备（你的手机）接入网络时，它自己就会作为 DHCP 客户端，请求网络地址。

然后 DHCP 服务器（家里的 Wi-Fi 路由器）会从地址池中挑选一个 IP 地址，分配给这个设备。

当设备不再使用这个 IP 时（你带着手机出门 / 睡觉飞行模式），DHCP 服务器会进行回收，之后再分配给其他有需要的设备（你新买的平板）使用。

DHCP 服务器与设备之间的通信是通过 UDP 传输协议完成的。因为 UDP 有一个优势，那就是不需要提前建立连接关系。DHCP 服务器的端口号是 67，设备的端口号是 68，它们一般的交互过程是这样的：

Discover：设备以广播的方式发送 DHCP Discover 消息，表示需要获取 IP 地址。

Offer：DHCP 服务器收到这个消息后，会发出 DHCP Offer 消息，作为回应。消息中带有 DHCP 服务器为设备分配的 IP 地址，也会包含其自身的 IP 地址。

Request：设备收到 DHCP Offer 消息后，将会广播一条 DHCP Request 消息，正式向 DHCP 服务器请求这个 IP 地址。

ACK：DHCP 服务器收到 DHCP Request 消息后，会判断服务器 IP 是否和自己的地址一致。如果一致，马上向设备回复 DHCP ACK 消息，并指定好 IP 地址的租用期限。

Decline：设备收到 DHCP ACK 消息后，还会验证一下 IP 地址是否可用。如果地址冲突，就说明不可用，它会发出 DHCP Decline 消息；如果地址不冲突，就可用的，设备将会按照租期使用这个 IP 地址。

Release：当设备不使用这个 IP 地址时，设备可以通过发送 DHCP Release 消息，来释放它。这样 DHCP 服务器可以重新分配这个 IP 地址。

如何让手机自动发现投影仪呢？

借助 DHCP 协议，当你的投屏设备接入 Wi-Fi 热点后，就可以自动获得一个自己的 IP 地址，比如“192.168.1.100”，同时还会自动得到路由器（Router）的 IP 地址，并且完成 DNS 的 IP 地址的自动设置。

那接下来，怎么让手机自动发现它，然后直接使用呢？

不知道你留意过没有，如果你新买了一台打印机，当这台打印机连上家里的 Wi-Fi 之后，你在电脑上打印文件时，它会自动显示出来供你选择。这是怎么实现的呢？

分析一下，一定是电脑通过某种方式知道有这台设备，并且知道它就是打印机，可以提供打印服务。打印机被电脑自动识别的这个过程，就是借助 UPnP 协议完成的。

UPnP 协议

UPnP 是 Universal Plug and Play 的简称，它要实现的目标就是网络设备的即插即用。

UPnP 由设备寻址、设备发现、设备描述、设备控制、事件通知和基于 HTML 的描述界面六部分构成。其中设备寻址同样是基于我刚介绍过的 DHCP 实现，如果网络内没有 DHCP 服务器，UPnP 会基于自己的 AutoIP 方法指定一个 IP 地址。

从整体看，UPnP 是一个多层协议构成的框架体系，每一层都以相邻的下层为基础，同时又是相邻上层的基础，直至达到应用层为止。你可以参考下面的图片。

这里我重点介绍一下第三层（从下往上数），它基于 HTTP、HTTPU、HTTPMU 协议，属于传送协议层。传送的内容都是经过“封装”之后，存放在特定的 XML 文件中的。用于设备和服务发现的 SSDP（Simple Service Discovery Protocol，简单服务发现协议）协议就是基于 XML 传送数据的。

你不要被 SSDP 协议的名字迷惑了，其实它既提供了服务发现的功能，也提供了设备发现的功能。我们可以基于 SSDP 中的 M-SEARCH 方法来查询设备，然后基于设备的响应，获得设备的服务能力的描述信息。同时设备可以通过 NOTIFY 方法向网络通知自己的服务能力。

借助 UPnP 协议，你的设备就可以自动被发现和使用了。这个自动化的过程，我们通常用一个专有名词来概括，也就是零配置组网。梳理一下，零配置组网包括三个方面的技术内核：

为网络设备自动分配 IP 地址，一般涉及 DHCP 协议和 AutoIP 方法；

自动发现和解析设备，主要是基于 SSDP 协议；

自动传播和发现各网络设备提供的服务，主要也是基于 SSDP 协议。

这里我贴一下我家里小米电视盒子的一个 UPnP 的组播包的内容。它每 3 秒就有一包，这样用户体验更好，手机或者电脑投屏时更容易发现它。

NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=66
LOCATION: http://192.168.31.188:49152/description.xml
NT: urn:schemas-upnp-org:service:ConnectionManager:1
NTS: ssdp:alive
SERVER: Linux/3.14.29, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
USN: uuid:F7CA5454-3F48-4390-8009-483842e84c17::urn:schemas-upnp-org:service:ConnectionManager:1

你可以发现，它是基于 HTTP1.1 版本，准确地说，是 HTTPMU 协议；同时你可以看到组播地址，端口号是 1900。

那为了实现 UPnP 协议，你可以使用哪些开源的代码实现呢？

比较流行的开源库是 libupnp，小米电视盒子使用的就是这个开源库。另外还有一个选择是GUPnP 项目，它包括几个不同的子项目，比如实现 SSDP 协议的GSSDP 项目。

Android 平台上有一个开源库Cling，它是4th Line 组织开发的，你可以参考。现在也只能说“参考”，使用的话，需要谨慎评估，因为它已经不再被维护。

mDNS 和 DNS-SD

除了 UPnP 协议，零配置组网还可以使用别的协议标准，比如 mDNS 和 DNS-SD 协议。

说起这两个协议，你可能比较陌生，不过苹果设备的 AirDrop 功能你可能比较熟悉。AirDrop 是通过 Bonjour 服务来发现网络上的其他苹果设备的。这个 Bonjour 服务就是 mDNS 协议和 DNS-SD 协议的具体实现。

mDNS （Multicast DNS）协议允许设备在本地的 DNS 名字空间，设置一个本地的域名。之后被询问的时候，它就通过 UDP 把 IP 地址广播出来，这样其它的设备就可以找到它。你可以简单地把 mDNS 理解为 DNS 的本地网络版本。

比如，主机 A 是 FTP 服务，它接入网络，并开启了 mDNS 服务，就会向 mDNS 服务注册服务信息：

我提供 FTP 服务，我的 IP 是 192.168.1.101，端口是 21。

当主机 B 接入相同的网络时，如果它向主机 B 的 mDNS 服务请求，需要找局域网内 FTP 服务器，主机 B 的 mDNS 就会在局域网内向其他设备的 mDNS 询问。然后，它就会找到主机 A 的 IP 地址和端口号。

DNS-SD（DNS Service Discovery） 协议，一般是和 mDNS 一起使用的。它使用三种 DNS 协议的记录类型来定义协议内容，三个记录分别是：PTR 记录、SRV 记录和 TXT 记录。它提供了服务发现的功能，作用类似于上面讲到的 SSDP 协议。

小结

总结一下，在这一讲中，我介绍了零配置组网的相关技术。零配置组网是一个技术组合，它要实现的目的是自动化网络设备的初始配置过程，让用户不需要额外的手动操作。

零配置组网的第一步是为网络设备自动分配 IP 地址。DHCP 协议是实现设备寻址的标准技术。它是工作在 UDP 协议之上的应用层协议，提供了完善的 IP 地址请求、IP 地址分配和租期管理等功能。如果网络内没有 DHCP 服务器，在 UPnP 中定义了 AutoIP 方法，设备可以自己分配 IP 地址。在最新的 UPnP 规范中 AutoIP 切换到了 Link-Local Addressing 规范。这类 IP 地址一般是“169.254.0.0/16”范围的地址。

第二步是自动发布和解析设备的名称。在 UPnP 中一般是基于 SSDP 协议，另一种替代的方案是 mDNS 协议。

第三步是自动传播和发现各网络设备提供的服务，一种选择是采用 SSDP 协议；另一种方案是基于 mDNS 的 DNS-SD 协议。

UPnP 协议整合了 DHCP、AutoIP 和 SSDP 等协议规范，提供了一个完整的组网协议框架。UPnP 协议实现的功能非常完善，比如，基于 SOAP 提供了设备控制的能力，但是也引来了很多安全隐患，这个在后面我还会介绍到。

我在这一讲介绍的方法主要关于 Wi-Fi 设备的。你可能想问，那蓝牙和 ZigBee 设备怎么办呢？其实关于蓝牙和 ZigBee 这类设备，智能家居厂家一般都会制定自己的私有协议，比如小米有 Mibeacon 协议，我会在实战篇再为你介绍。

除了这些私有协议，行业内也有一些标准组织主导的开放协议，比如 AllJoyn 协议。它为蓝牙和 ZigBee 设备提供了设备发现和控制的解决方案。现在，AllJoyn 已经和 IoTivity 合并。这个合并是为了在物联网场景下，让零配置组网技术进一步发展。

具体来说，因为物联网设备用了很多不同的通信技术，所以 AllJoyn 提供了一个抽象层。它为底层网络协议栈定义了统一的接口，使得软件工程师可以相对容易地添加和安装新的网络。

AllJoyn 还采用了一种易于理解的对象模型和远程方法调用（RMI）机制，并且它重新实现了总线协议，基于 D-BUS 规范和扩展 D-BUS 协议，用来支持分布式设备。

因为相对来说，AllJoyn 协议的行业应用还处于早期，我就不展开介绍了，有这方面需求或者兴趣的话，推荐你可以到它的官网详细了解。

我这里总结了一个思维导图，供你参考。

思考题

最后，我还是给你留一个思考题吧。

在 DHCP 协议的流程介绍中，我提到当设备收到 DHCP 服务器响应的 DHCP ACK 消息后，并不会立即使用这个 IP 地址，而是要先检测一下 IP 地址是否有效。那设备是通过什么方式检测 IP 地址的有效性的呢？

你可以在留言区和我交流你的思考。同时，也欢迎你将这一讲分享给你的朋友一起交流学习。

08 | MQTT：在实践中掌握一个通信协议

你好，我是郭朝斌。

在基础篇的第 3 讲中，我介绍了几种物联网系统中常用的网络协议。它们是物联网设备之间沟通的“语言”，使用同一种语言，双方才能通信成功。

那么，在这么多网络协议当中，最流行的是哪一种呢？毫无疑问，是 MQTT 协议，它甚至已经成为物联网系统事实上的网络协议标准。如果你想从事物联网开发，就一定要掌握 MQTT。

所以这一讲，我就会带你了解 MQTT，在实践中熟悉它的特性。

体验 MQTT

为了让你对 MQTT 有一个直观的印象，我先带你体验一下它的通信过程，

第一步是安装 hbmqtt，它是一个开源的基于 Python 语言的 MQTT Broker 软件，正好包括我们需要使用一些工具。跟其他选择相比，这个软件安装起来非常方便，因为它在 Python 的 PYPI 软件仓库中就有，所以你通过 pip 命令就可以安装。这也是选择使用它的主要原因。

不过要注意的是，hbmqtt 是基于 Python3 实现的，因此这里使用的是 pip3 工具。

pip3 install hbmqtt

安装完成后，我们就可以使用 hbmqtt 中提供的 hbmqtt_sub 和 hbmqtt_pub 这两个命令行工具了。通过名字，你应该也可以看出 hbmqtt_sub 可以充当订阅者的角色；hbmqtt_pub 可以作为消息的发布者。

至于订阅者和发布者之间的经纪人，也就是 MQTT Broker，我们使用 Eclipse 免费开放的在线 Broker 服务。打开链接，你可以看到关于端口的介绍信息，加密和非加密方式都支持，而且还有基于 Websocket 的实现，这对基于前端网页的应用来说是非常有利的。

我们先使用 1883 端口的非加密方式，然后为消息传输确定一个主题（Topic）。主题确定了消息的类别，用于消息过滤。比如我们待会儿要测试的消息，属于极客时间平台的物联网课程，所以主题可以设为“/geektime/iot”。

/geektime/iot

接着，我们在电脑的终端界面输入下面的命令，就可以订阅这个主题消息：

hbmqtt_sub --url mqtt://mqtt.eclipse.org:1883 -t /geektime/iot

如果你想了解一些命令的执行细节，可以在上面的命令中加上 “-d” 参数。

现在，我们启动另外一个终端界面，通过 hbmqtt_pub 发布一个 “/geektime/iot” 主题的消息：

hbmqtt_pub --url mqtt://mqtt.eclipse.org:1883 -t /geektime/iot -m Hello,World!

通过 Eclipse 的开放 Broker 作为“经纪人”，消息被传输到了我们通过 hbmqtt_sub 运行的订阅者那里。下图是我的终端界面上运行的结果，一个完整的消息传输过程就这样完成了。

MQTT 的生态很完善

初步体验之后，不知道你是不是有这样的感觉：原来使用 MQTT 也没有那么难啊？

确实不难，甚至可以说很简单，这主要得益于 MQTT 协议出现的时间很久远。当然，时间久远本身不是优势，能力强才是。这些长期的使用和积淀使得 MQTT 的生态非常完善，而生态是技术标准能够主导行业的关键。所以你在使用 MQTT 的时候会觉得很方便，可供挑选的方案有很多。

比如在上面的体验中，你要安装 MQTT，可以直接找到 hbmqtt 这样的项目拿来用。它的背后有 Eclipse 基金会的支持。

类似的 MQTT Broker 软件，你还可以选择基于 C 语言的Mosquitto，基于 Erlang 语言的VerneMQ等。

至于 MQTT 的客户端（Client）实现，也有成熟的 Python、C、Java 和 JavaScript 等各种编程语言的开源实现，供你参考、使用，比如Eclipse Paho 项目。

而且，还有很多商业公司在持续运营功能更丰富、支持更完备的商业版 Broker 实现，比如提供高并发能力的集群特性、方便拓展的插件机制等。这些会大大提高我们技术开发者的工作效率。比如中国一个团队开发、维护的 EMQ X，它已经完整地支持 MQTT5.0 协议。

另外，生态完善还有一个好处，那就是作为开发者，当你遇到难题时，可以很方便地找到很多相关的资料；就算资料解决不了问题，你还可以去社区中提问，寻求高手的帮助。这在实际工作中非常有用。顺便提一下，除了老牌的 Stack Overflow，你还可以关注一下 GitHub 的 Issues 模块，因为在那里可以找到很多专家。

MQTT 自身的“基因”很强大

当然，阿里云、华为云、腾讯云和微软 Azure 这些大厂，之所以不约而同地选择 MQTT 协议作为物联网设备的“第一语言”，不仅是因为 MQTT 的生态完善，MQTT 协议本身的优秀设计也是重要的因素。

它在设计上的优点体现在哪呢？我想主要有五个方面：

契合物联网大部分应用场景的发布 - 订阅模式。

能够满足物联网中资源受限设备需要的轻量级特性。

时刻关注物联网设备低功耗需求的优化设计。

针对物联网中多变的网络环境提供的多种服务质量等级。

支持在物联网应用中越来越被重视的数据安全。

接下来，我分别为你剖析一下。

发布 - 订阅模式

刚才我们体验的通信过程，是一个发布者和一个订阅者的情况。在这之后，你可以再打开一个终端界面，重复和之前一样的命令，再启动一个订阅者。

hbmqtt_sub --url mqtt://mqtt.eclipse.org:1883 -t /geektime/iot

现在，这两个订阅者都订阅了“/geektime/iot” 主题的消息。

然后，你再次使用 hbmqtt_pub 发送消息，就可以看到两个订阅者都收到了同样的消息，这是发布 - 订阅模式的典型特征。

因为采用了发布 - 订阅模式，MQTT 协议具有很多优点，比如能让一个传感器数据触发一系列动作；网络不稳定造成的临时离线不会影响工作；方便根据需求动态调整系统规模等。这使得它能满足绝大部分物联网场景的需求。

轻量级协议：减少传输数据量

MQTT 是一个轻量级的网络协议，这一点也是它在物联网系统中流行的重要原因。毕竟物联网中大量的都是计算资源有限、网络带宽低的设备。

这种“轻量级”体现在两个方面。一方面，MQTT 消息采用二进制的编码格式，而不是 HTTP 协议那样的文本的表述方式。

这有什么好处呢？那就是可以充分利用字节位，协议头可以很紧凑，从而尽量减少需要通过网络传输的数据量。

比如，我们分析 HTTP 的一个请求抓包，它的消息内容是下面这样的（注意：空格和回车、换行符都是消息的组成部分）：

GET /account HTTP/1.1    <--注释：HTTP请求行
Host: time.geekbang.com  <--注释：以下为HTTP请求头部
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
                         <--注释：这个空行是必须的，即使下面的请求体是空的

在 HTTP 协议传输的这段文本中，每个字符都要占用 1 个字节。而如果使用 MQTT 协议，一个字节就可以表示很多内容。下面的图片展示了 MQTT 的固定头的格式，这个固定头只有 2 个字节：

你可以看到，第一个字节分成了高 4 位（4～7）和低 4 位（0～3）；低 4 位是数据包标识位，其中的每一比特位又可以表示不同的含义；高 4 位是不同数据包类型的标识位。

第二个字节表示数据包头部和消息体的字节共个数，其中最高位表示有没有第三字节存在，来和第二个字节一起表示字节共个数。

如果有第三个字节，那它的最高位表示是否有第四个字节，来和第二个字节、第三个字节一起表示字节总个数。依此类推，还可能有第四个字节、第五个字节，不过这个表示可变头部和消息体的字节个数的部分，最多也只能到第五个字节，所以可以表示的最大数据包长度有 256MB。

比如，一个请求建立连接的 CONNECT 类型数据包，头部需要 14 个字节；发布消息的 PUBLISH 类型数据包头部只有 2～4 个字节。

轻量级的另一方面，体现在消息的具体交互流程设计非常简单，所以 MQTT 的交互消息类型也非常少。为了方便后面的讲解，我在这里整理了一个表格，总结了 MQTT 不同的数据包类型的功能和发消息的流向。

从表格可以看出，MQTT 3.1.1 版本一共定义了 14 种数据包的类型，在第一个字节的高 4 位中分别对应从 1 到 14 的数值。

低功耗优化：节约电量和网络资源

除了让协议足够轻量，MQTT 协议还很注重低功耗的优化设计，这主要体现在对能耗和通信次数的优化。

比如，MQTT 协议有一个 Keepalive 机制。它的作用是，在 Client 和 Broker 的连接中断时，让双方能及时发现，并重新建立 MQTT 连接，保证主题消息的可靠传输。

这个机制工作的原理是：Client 和 Broker 都基于 Keepalive 确定的时间长度，来判断一段时间内是否有消息在双方之间传输。这个 Keepalive 时间长度是在 Client 建立连接时设置的，如果超出这个时间长度，双方没有收到新的数据包，那么就判定连接断开。

虽然 Keepalive 要求一段时间内必须有数据包传输，但实际情况是，Client 和 Broker 不可能时时刻刻都在传输主题消息，这要怎么办呢？

MQTT 协议的解决方案是，定义了 PINGREQ 和 PINGRESP 这两种消息类型。它们都没有可变头部和消息体，也就是说都只有 2 个字节大小。Client 和 Broker 通过分别发送 PINGREQ 和 PINGRESP 消息，就能够满足 Keepalive 机制的要求。

我猜你也想到了，如果要一直这样“傻傻地”定期发送消息，那也太浪费电量和网络资源了。所以，如果在 Keepalive 时间长度内，Client 和 Broker 之间有数据传输，那么 Keepalive 机制也会将其计算在内，这样就不需要再通过发送 PINGREQ 和 PINGRESP 消息来判断了。

除了 Keepalive 机制，MQTT 5.0 中的重复主题特性也能帮助我们节省网络资源。

Client 在重复发送一个主题的消息时，可以从第二次开始，将主题名长度设置为 0，这样 Broker 会自动按照上次的主题来处理消息。这种情况对传感器设备来说十分常见，所以这个特性在工作中很有实际意义。

3 种 QoS 级别：可靠通信

除了计算资源有限、网络带宽低，物联网设备还经常遇到网络环境不稳定的问题，尤其是在移动通信、卫星通信这样的场景下。比如共享单车，如果用户已经锁车的这个消息，不能可靠地上传到服务器，那么计费就会出现错误，结果引起用户的抱怨。这样怎么应对呢？

这个问题产生的背景就是不稳定的通信条件，所以 MQTT 协议设计了 3 种不同的 QoS （Quality of Service，服务质量）级别。你可以根据场景灵活选择，在不同环境下保证通信是可靠的。

这 3 种级别分别是：

QoS 0，表示消息最多收到一次，即消息可能丢失，但是不会重复。

QoS 1，表示消息至少收到一次，即消息保证送达，但是可能重复。

QoS 2，表示消息只会收到一次，即消息有且只有一次。

我用一张图展示了它们各自的特点。可以看到，QoS 0 和 QoS 1 的流程相对比较简单；而 QoS 2 为了保证有且只有一次的可靠传输，流程相对复杂些。

正常情况下，QoS 2 有 PUBLISH、PUBREC、PUBREL 和 PUBCOMP 4 次交互。

至于“不正常的情况”，发送方就需要重复发送消息。比如一段时间内没有收到 PUBREC 消息，就需要再次发送 PUBLISH 消息。不过要注意，这时要把消息中的 “重复”标识设置为 1，以便接收方能正确处理。同样地，如果没有收到 PUBCOMP 消息，发送方就需要再次发送 PUBREL 消息。

剖析到这里，MQTT 协议本身的主要特性我就介绍完了，我们已经为在实战篇编写 MQTT 的相关通信代码做好了准备。但是，我还想跟你补充一个跟生产环境有关的知识点，那就是数据安全传输。

安全传输

说到安全传输，首先我们需要验证 Client 是否有权限接入 MQTT Broker。为了控制 Client 的接入，MQTT 提供了用户名 / 密码的机制。在建立连接过程中，它可以通过判断用户名和密码的正确性，来筛选有效连接请求。

但是光靠这个机制，还不能保证网络通信过程中的数据安全。因为在明文传输的方式下，不止设备数据，甚至用户名和密码都可能被其他人从网络上截获而导致泄漏，于是其他人就可以伪装成合法的设备发送数据。所以，我们还需要通信加密技术的支持。

MQTT 协议支持 SSL/TLS 加密通信方式。采用 SSL/TLS 加密之后，MQTT 将转换为 MQTTS。这有点类似于 HTTP 和 HTTPS 的关系。

我们只要将前面测试的命令修改一下，将 “mqtt://” 改为 “mqtts://”，端口改为 8883，就可以用 SSL/TLS 加密通信方式连接到 Eclipse 提供的开放 Broker。但是我最近发现，它的 SSL 证书已经过期了，因此连接会失败。（你在学习这一讲的时候，可以再试试，万一又更新了呢？）

所以，我再提供另一个方式，供你测试使用。

我们输入“mqtts://test.mosquitto.org:8883”，把开放 Broker 切换到这个链接，从链接中下载一个客户端证书，然后通过下面的命令订阅主题消息：

hbmqtt_sub --url mqtts://test.mosquitto.org:8883 -t /geektime/iot --ca-file ~/Downloads/mosquitto.org.crt

接着，我们再通过下面的命令测试发布消息：

hbmqtt_pub --url mqtts://test.mosquitto.org:8883 -t /geektime/iot -m Hello,World! --ca-file ~/Downloads/mosquitto.org.crt

最后在运行 hbmqtt_sub 命令的终端，就可以看到 Hello,World! 的消息：

小结

总结一下，在这一讲中，我带你体验了使用 MQTT 协议的通信过程，同时也为你介绍了 MQTT 协议的几个特点。

MQTT 协议的生态很好。比如，MQTT 协议的代码实现非常丰富，C 语言的有 Mosquitto，Python 语言有 Eclipse hbmqtt，而且有商业公司在运营相关软件解决方案。这表明 MQTT 协议很成熟。

MQTT 协议采用了适合物联网应用场景的发布 - 订阅模式。当然，我也提到了 MQTT 5.0 中同样增加了请求 - 响应模式，便于部分场景中的开发使用。

MQTT 协议采用二进制的消息内容编码格式，协议很精简，协议交互也简单，这些特点保证了网络传输流量很小。所以客户端（包括发布者和订阅者角色）的代码实现可以短小精悍，比如 C 语言的实现大概只占 30KB 的存储空间，Java 语言也只需要 100KB 左右大小的代码体积。

MQTT 协议在设计上考虑了很多物联网设备的低功耗需求，比如 Keepalive 机制中精简的 PINGREQ 和 PINGRESP 这两种消息类型，还有 MQTT 5.0 中新增加的重复主题特性。这也再次印证了 MQTT 的定位非常明确，那就是专注于物联网场景。

MQTT 在消息的可靠传输和安全性上，也有完整的支持，可以说“简约而不简单”。

因此，在你为物联网系统选择网络协议时，MQTT 可以作为重点考察对象。在实战篇的动手实验中，我们将使用 MQTT 协议传输设备数据，相信你已经做好了准备。

思考题

最后，我想给你留一个实践作业和一道思考题。

在介绍 QoS 等级的时候，我没有结合网络抓包来说明消息的交互过程。所以，我想请你在课后使用 Wireshark 工具，实际分析一下各个 QoS 级别的数据包类型。

同时，也请你思考一下，如果 Client 发布消息时选择的 QoS 等级是 1 ，而订阅者在订阅这个主题消息时选择的 QoS 等级是 2 ，这种情况下 Broker 会怎么处理呢？

欢迎你在留言区写一写你的实践和思考的结果，也欢迎你将这一讲分享给你的朋友一起交流学习。

09 | 边缘中心：物联网网关有多重要？

你好，我是郭朝斌。

在进阶篇的前几讲，我剖析了物联网中跟设备有关的几个技术点，包括物模型、设备的零配置组网、设备进行网络通信要用到的 MQTT 协议等。

但是，并不是所有的设备都能直接接入互联网，直接跟云平台通信。比如智能家居中的一些传感器，它们使用的通信技术是 BLE 或者 ZigBee，本身连 IP 地址都没有。那么，这样的设备要怎么联网呢？

物联网网关：设备和云平台之间的桥梁

这个时候，我们就需要借助物联网网关的能力了。举个例子，我主持设计过一个冷库温湿度监测系统，它是基于 LoRa 通信技术的。

我们知道，冷库的环境是非常复杂的。首先，库房内部的蜂窝网络信号一般都很差，一方面是因为要增强保温性能，所以墙壁做得比普通的墙体要厚；另一方面是因为库房位置通常位于城市的郊区，比较偏远，所以经常是完全没有网络信号。其次，库房的信号环境是千变万化的，因为会出现满仓、空仓等多种情况。

所以，在冷库内部部署的监测设备，如果直接连接蜂窝网络的话，完全不能实现可靠和稳定的通信。

这种情况下，我选择的解决方案就是，在有稳定网络信号的地方部署物联网网关，让冷库里面的监测设备，通过 LoRa 这种穿透能力强、空旷环境通信范围可以达到几公里的技术跟网关通信，从而间接地实现设备的联网。

我们可以看到，物联网网关正在成为整个物联网体系中不可或缺的角色。它作为物联网设备与云平台之间的桥梁，变得越来越重要。

协议转换：搭建桥梁的关键

那么，物联网网关凭什么能搭建这座桥梁呢？奥秘就在于协议转换。其实，刚才智能家居和冷库的例子，就已经体现了这个功能。

BLE 、ZigBee 和 LoRa 设备在跟网关通信的时候，需要网关基于开放的或者内部私有的协议，解析出数据；然后网关再使用跟云平台的连接协议来组织数据，完成数据传输。

这个过程自然就要求网关设备能够支持不同的通信技术。我在下面给出了一张网关通信接口的结构示意图。

看这张图跟看地图一样，上北下南，上半部分叫“北向接口”，下半部分叫“南向接口”。这可不是我在生造概念，而是行业内约定俗成的说法。

北向接口需要接入互联网，所以通常的选择有 RJ45 以太网口、光纤接口、Wi-Fi 和 4G、NB-IoT 等蜂窝网络模组等。

南向接口用来连接物联网设备，除了刚说的 BLE、ZigBee、LoRa、Wi-Fi 这些无线技术的接口，常见的还有用在工控机（Industrial Personal Computer，工业控制计算机）上的 RJ45 以太网口、RS232、RS485 等有线接口。

这里需要注意的是，每个网关设备的接口类型和个数不是固定的，因为网关产品一般会根据应用场景确定几个不同的规格型号。不同型号的网关需要支持不同类型的协议，以及不同个数协议的转换，所以网关的协议转换功能一般采用插件的软件架构方式。

插件机制这种二次开发能力非常重要。一方面，它让我们可以根据接口的情况，动态、灵活地配置协议转换功能；另一方面，它也可以方便我们开发私有协议的解析功能。

比如通过 BLE、ZigBee 或 LoRa 技术跟网关通信的设备，它们通常采用的是私有的应用层协议，这就需要我们基于设备架构设计时定义的私有协议，专门编写解析代码。

至于使用 RJ45 网口或 Wi-Fi 跟网关连接的物联网设备，除了采用基于 TCP 或者 UDP 的私有协议之外，也可能采用我们之前讲过的 MQTT 或者 CoAP 这样标准的协议。这时，我们就需要按照这些协议的格式来处理。

另外，工控机 和 PLC （Programmable Logic Controller，可编程逻辑控制器）中经常使用的标准协议有 Modbus、ProfiBus、OPC UA 和 BACnet 等。如果你在相关行业，可能对它们有一定了解。这些协议也是需要进行转换的，因为它们一般只应用于工业领域。这里我就不展开介绍了，如果感兴趣的话，你可以在 Wikipedia 上查询到详细的资料。

网关的其他功能

经过协议转换，网关就得到了通用格式的数据。对于这些数据，网关还需要进行持久化，把数据临时存储起来。

网关的存储功能可以防止因网络临时故障等原因，导致设备数据的丢失。另外，网关和设备的配置信息也需要存储在网关中，以便设备运行过程中快速读取。

同时，数据的安全性也非常重要，物联网网关需要做好这几个方面的事情：

完善的本地身份认证。这样可以防止网关设备被随意修改软件或者数据。

网关保证数据的加密传输。因为很多物联网设备的计算能力非常弱，不具备进行数据加密的能力，这时就需要借助网关来保证数据或者控制命令的加密和解密。

网关能够支持运营商专网接入，或者支持 VPN（Virtual Private Network，虚拟专用网络）技术。这里我补充说明一下，VPN 技术的好处是基于互联网网络建立加密通道。这样既保证了数据传输的安全可靠，又比建立专线成本要低。常用的 VPN 协议有 IPsec、OpenVPN 等。

除了我刚才讲到的协议转换、存储功能和安全管理，物联网网关一般还有设备管理、网关配置、空中升级这些功能模块。它们比较容易理解，我就不在这一讲展开介绍了。

到这里呢，我就把传统物联网网关的功能都讲解了。看到“传统”两个字，你可能疑惑了：还有现代网关吗？是的，现在有一个趋势就是越来越强调物联网网关的数据分析处理能力。

我在第 4 讲介绍过数据的分析处理。我们知道，云计算平台凭借着强大的处理能力、存储能力和极高的性价比，已经成为物联网系统的有力支撑。现在的物联网应用系统，主要就是基于云平台实现数据分析处理任务的。

在网关上做数据分析

既然如此，那为什么我们还要在网关上运行这些计算的任务呢？这是因为随着物联网的发展，海量的设备接入网络，随之而来的，是更加海量的数据源源不断的产生，并上传到云平台。

这就给云平台提出了很大的挑战。一方面是极大地消耗有限的网络带宽资源；另一方面，网络的不确定因素很多，有可能导致不可控的延时，从而对业务应用造成不可接受的影响。

而且物联网数据通常与物理实体关系密切。比如家庭监控摄像头中，家庭成员的肖像等视频信息是非常敏感的；而在工业场景中，很多数据是机密的。如果这些信息全部上传到云平台，会给用户带来很大的安全风险。

所以，现在行业内已经开始尝试将云平台上的部分计算服务，下沉到靠近数据发生地的“边缘设备”上进行，这就是边缘计算的由来，而物联网网关是边缘计算中最轻量级的解决方案的关键。

边缘计算的好处

怎么理解这种技术架构上的进化呢？你可以想一想，人体是如何处理各种感知信息的，是不是所有的信息都需要大脑来处理呢？

其实人体神经系统的信号是分层级传递和处理的。我们中学上生物课时都做过类似的试验，如果手指放到蜡烛上，手是下意识地快速移开，然后大脑才意识到发生了什么。这是四肢的一种非条件反射，它是由大脑皮层之下的低级别神经系统，如脑干、脊髓，直接控制完成的。它们距离四肢更近，可以保证在危险的情况下，更加快速地做出反应。

边缘计算的设计，与人体的这个构造类似。边缘设备，比如物联网网关，完成初步的数据处理，和需要及时响应的计算任务；而云平台负责需要大规模数据和复杂计算的数据分析工作，以及完成整体的协调和控制。

具体来说，云平台将原有的云计算模型的部分计算任务迁移到网络边缘设备上来；网络边缘设备（比如路由器、移动网络基站等），在数据源附近执行数据处理和数据分析任务。这样一来，就降低了云计算中心的计算负载，减轻了物联网对于网络带宽的压力，提高了数据处理的效率。

我将边缘计算模型的好处，总结为四个方面：

延迟低。数据只需要从产生设备传输到边缘设备，传输距离短，数据不需要通过其他网络，网络延迟低。

节约了主干网带宽。缓解大量数据传输所造成的网络拥堵想象。尤其像一些银行的专有网络，本身带宽非常有限，只能用于传输关键性的数据。

计算可用性好。数据在网络中的路径长度显著变短，因网络波动引起的计算服务不可用情况将有所减少。

隐私性更好。由于边缘设备距离用户近，用户的隐私数据不再需要上传到云平台，因此，在边缘计算场景下，用户的隐私也可以得到更好的保护。

边缘计算对网关的影响

技术架构的变化，一般也会对系统提出新的需求。边缘计算对网关的影响，主要体现在三个方面。

首先，为了更好地在网关中运行边缘计算任务，网关需要支持虚拟化技术，这在目前的实践中通常是采用容器技术实现。容器天然具有轻量和可移植的优点，非常适合开发人员快速测试应用程序，也方便维护人员在网关上大规模部署和更新应用程序。另外，容器技术也更利于网关使用容器自动运维平台技术，比如 Kubernetes，来实现应用程序的编排等功能。

其次，因为网络环境的不确定性，网关需要具备一定的自治能力。当网关与云平台的通信中断时，这种情况不应该影响网关处理数据的计算任务，和对物联网设备的管理。

最后，因为边缘计算的引入，我们需要在网关侧实现数据分析处理的任务。而网关的硬件资源非常多样，业务需求也千差万别，所以网关上提供统一的开发框架变得更加重要。开发框架可以为开发人员提供一致的 API 和组件的互操作能力。这样开发人员可以更加高效地实现业务功能，也更容易和其他厂商协作。

这里我把加入边缘计算能力后的网关架构图放在下面，供你参考。

怎样实现边缘计算？

我知道，你现在最可能提出的问题就是：有什么开源软件可以助力网关实现边缘计算呢？

我简单介绍一下开源的生态，也方便你更好地理解网关的相关技术。

首先说一下标准化组织。目前，在推进边缘计算的标准组织主要有三个，分别是：

Linux 基金会下的 LF Edge （Linux Foundation Edge）和致力于推进 Cloud Native 的 CNCF（Cloud Native Computing Foundation）

Eclipse 基金会下面的 Eclipse IoT 项目

OpenStack 基金会

至于容器技术方面的开源项目，主要有 KubeEdge 项目，它的主要思路是将 Kubernetes 从云端扩展到边缘设备，方便应用程序在边缘网关上的编排和调度，同时实现云端和边缘设备的协同数据处理。

关于开发框架，比较知名的是 EdgeX Foundry 项目，定位是为工业物联网提供通用的边缘计算框架。为此，它适配了很多协议，提供出标准的 API 接口。

专门针对智能家居领域的项目有 Home Edge ，它是三星贡献的一个开源项目，用于加速智能家居设备的边缘部署。

小结

总结一下，随着物联网应用场景越来越多，物联网网关正在成为“边缘中心”。它成为物联网的“云、边、端”架构（即云平台、边缘、终端三部分）中非常重要的组成部分。你在做物联网网关的架构设计和功能开发中，需要重点关注的内容有：

物联网网关是设备与云平台之间的桥梁，网关的南向接口和北向接口都非常多样。因此协议转换功能是必须包含的功能。它保证了物联网设备数据的可靠上传，和云平台控制命令的有效执行。

关注网关基础功能的实现。这些功能包括存储功能、安全管理、设备管理、网关配置和空中升级这些功能模块。它们既保证了数据的安全和可靠传输，也为远程维护提供了支持。

你需要根据业务场景，决定网关对于边缘计算的支持能力。边缘计算对于物联网网关的架构有 3 个方面的影响：

（1） 容器技术。容器技术的应用利于开发人员快速开发、测试应用程序，也方便维护人员在网关上大规模部署、更新应用程序。同时，基于容器自动运维平台技术可以实现应用程序的编排等功能。

（2）自治能力。它可以保证网络环境不稳定时，网关的数据分析处理任务的正常执行。

（3）开发框架。开发框架利于开发人员的开发工作，也有益于各部分组件的协同互操作。

思考题

最后，我还是给你留一个思考题。

边缘计算已经成为物联网的必要组成部分，行业内云计算企业、网关设备商、运营商都积极参与推进技术体系的发展。那你有使用这些边缘计算产品吗？应用在什么业务场景呢？

欢迎你在留言区和我交流，也欢迎你将这一讲分享给你的朋友一起讨论学习。

10 | 数据处理框架：批处理还是流处理？

你好，我是郭朝斌。

在第 4 讲中，我分析了物联网系统的数据技术体系。它包括 5 个部分：数据源数据采集、数据传输、数据存储、数据处理和数据应用。

不过，这还只是一个整体的认识框架。数据技术体系涉及的内容很多，虽然我在第 4 讲已经介绍了数据应用中用到的分析方法和算法，但是你还需要在这个框架的基础上，继续了解其他几个部分的知识。

所以我会从今天开始，用连续 3 讲的篇幅，分别讲一讲数据处理、数据存储和数据传输涉及的技术。每一讲分别专注其中一个主题，把它们都剖析透。至于数据源的数据采集，它跟具体的行业应用有关，不同的行业差别很大，所以我们这门课就不展开讲了。

处理海量数据时的难题

我们知道，数据分析需要用到很多算法，比如支持向量机和 K-means。那么在物联网系统的应用中，我们要怎么使用这些算法呢？

你可能会想：这算什么问题？从文件中或者数据库中读取数据，然后使用一个算法工具，比如 Python 语言的机器学习框架 Sklearn（也称为 Scikit-Learn），不就可以快速应用算法处理数据了吗？

其实没有这么简单，因为这种方式一般只适合用来学习和做研究。在真实的物联网场景中，你面临的是海量的数据。当我们面对海量数据的处理时，一切就不是这么直接和简单了。先不说高效地处理，首先你面临的挑战就是，如何把高达几 GB 甚至数 TB 的数据直接读取到内存中计算，显然直接加载到内存是不现实。

所以，对于海量数据，我们要借助大数据处理技术。

经典思路：MapReduce 的分而治之

那么，大数据处理技术是采用什么思路解决海量数据的处理任务的呢？

为了让你更好地理解，在解答这个问题之前，我想先跟你讲一个故事。

记得上大学的时候，为了提前体验一下工作招聘的流程，我参加过一些公司的笔试。笔试题目中有很多是关于大文件处理的，比如给你一个或者几个很大的文件，问你怎么找出其中出现频次 Top 100 的词。搜索引擎公司尤其爱出这种题。

对于这种问题，如果我们要提高处理的效率，就需要考虑“分而治之”的策略了：把数据分成几份，分配给几台计算机同时处理；每台计算机统计它负责的文件块中每个词出现频次，然后再将所有计算机统计的结果进行汇总，最终得到所有数据中最高频的 100 个词。

虽然这只是一道笔试题，但是它来源于搜索引擎公司真实的业务需求。搜索引擎需要对海量的网页内容进行处理，建立索引，计算权重。为此，工程师们要做很多事：

数据切分：把大文件分成小文件

数据传输：把文件分发给可用的计算机

结果汇总：把每台计算机的计算结果做汇总处理，得到最终结果

容错处理：解决多台计算机协作过程中出现的机器故障

灵活扩展：根据计算机临时的增加，随时调整计算任务的分配和汇总

显然，这不是一项轻松的工作，还涉及到很多分布式系统的技术。如果每次有不同需求的时候，我们都得重新走一遍这个过程，就要投入大量的时间和精力，太不划算了。

怎么解决这个问题呢？方法当然有很多。在大数据技术的早期，应用最广泛的方法是 MapReduce ，流行的原因很简单，就是分享和开源。

首先，谷歌（Google）基于公司内部的实践，在 2004 年发表了分布式计算框架的论文。这篇论文提出了 MapReduce 计算框架的设计思想，主要用于解决海量网页的索引生成问题。

接着，开源搜索引擎项目 Nutch 的开发人员，基于这个设计思想开发出了开源的 Hadoop MapReduce 实现。

MapReduce 是怎么设计的呢？其实他们的想法跟刚才那道笔试题的解法一样，也是分而治之。

具体来说，就是把数据分成相同大小的多份，然后相应地创建多个任务，并行地处理这些数据分片，这个的过程被定义为 Map 过程；接着，再将 Map 过程中生成的计算结果进行最终的汇总，生成输出结果，这个过程被定义为 Reduce 过程。

这两个过程合起来就是 MapReduce 了。

这个设计思路本身，还不是最关键的地方。更重要的是，它提供了一个框架，把与计算机硬件相关的容错和扩展功能都实现了。同时，它也提供了统一的开发接口，我们只需要基于业务目标，定制 Map 和 Reduce 的具体计算任务就行了。这就大大降低了我们分析海量数据的难度。

当出现一个好用的工具时，人们就会试图用它来解决一切问题。随着 MapReduce 的流行，人们开始把它应用在各种场景中，而不仅仅是计算索引，比如执行 Hive 中的 HQL 查询（这是一种 SQL 样式的交互式计算）。

这个时候，MapReduce 就显得越来越“力不从心”了，原因主要有两个方面。

一方面，MapReduce 的计算模型非常简单，只有 Map 和 Reduce 两种类型。就连对数据进行排序和分组这样简单且常见的任务时，都需要转换成 Map 和 Reduce 来进行；而像上面说到的 HQL 查询，更是需要使用多个 Map 和 Reduce 过程才能实现。

这有点像函数调用。我们使用 C、Java 和 Python 这些高级语言的时候，直接引用函数名，填上函数参数就可以了。但是如果我们使用的是汇编语言，就需要自己写代码实现函数入参的压栈、返回地址压栈、跳转到函数代码的地址、执行完成后的出栈和返回等操作。

这非常不直观，也容易出错。

另一方面，MapReduce 是基于分布式文件系统 HDFS 来实现数据存取的。注意，不只是读取源数据和写入计算结果，包括中间的计算结果的存储和数据交换也是基于 HDFS 的。

HDFS 是磁盘上的文件系统，读写的效率要远远低于内存。HDFS 之所以选择磁盘作为存储介质，是因为它出现的时代计算机内存还是很昂贵的。

这就导致 MapReduce 的效率不高。

高效率开源框架：以“快如闪电”为目标的 Spark

高效是工程师们一直追求的，不管是开发还是处理，我们都希望越快越好。为了实现高效，新的设计思想和数据处理框架开始出现，其中的翘楚是 Spark 项目。

那么，Spark 是如何打造高效率框架的呢？

首先，在计算模型上，Spark 抛弃了 MapReduce 的两个过程模型，采用了 DAG（Directed Acyclic Graph，有向无环图）模型。为什么采用 DAG 呢？我给你挖掘一下这背后的本质。

下面这张图展示了 MapReduce 处理数据时的数据流：

学过数据结构和算法的你，一定知道这就是有向无环图。所以，采用 DAG 来描述数据处理的过程，应该说是反映了数据处理过程的本质。这样一方面开发人员可以更容易地描述复杂的计算逻辑，另一方面计算框架也能更方便地自动优化整个数据流，比如避免重复计算。

其次，Spark 的数据存取充分地利用了内存。

它的数据分片被称为 Partition。然后它基于 Partition，提出了 RDD（Resilient Distributed Datasets，弹性分布式数据集）的概念。

所谓的“弹性”就是指，数据既可以存储在磁盘中，也可以存储在内存中，而且可以根据内存的使用情况动态调整存储位置。这就提高了计算的效率。

另一种思路：为实时计算而生的流处理

到这里，你可以想要问：怎么还没有说到批处理和流处理呢？

其实我刚才介绍的 MapReduce 就是批处理的经典思路和框架，而 Spark 就是目前更高效、更流行的数据批处理开源框架。

之所以没有在一开始的时候就提出来，是因为“批处理”这个概念一定是相对于其他处理方式来说的，比如流处理。如果后来没有流处理模式，我们也只会说“大数据处理”或者“分布式数据处理”，而不会专门定义一个批处理出来。

那流处理为什么会出现呢？当然是因为业务需求。随着社交网络的出现，产品中的个人信息流（Feeds）需要基于好友关系和好友的发布动态，快速地计算和显示出和本人有关系的信息。类似的需求还有个性化的广告和消息推送服务。

而在物联网中，当采集的数据传输到系统后，我们可能需要对数据进行一些预处理，处理之后再存储起来。

这些需求在现在的应用中很常见。它们的共同特点是，数据像流水一样流入系统，然后被处理，而数据的快速处理，也就是实时计算，是这个过程中的关键点。这就是流处理出现的背景。

那怎么实现呢？考虑到数据输入的速度和数据处理的速度不一定一致，我们可以按照一定的分配策略，将数据输入多个消息队列中缓存数据，每个消息队列由一个进程或者线程处理数据。

但是和我一开始提到的计算词语出现频次的例子一样，这种基于消息队列自己开发的系统，同样会遇到拓展性、容错性的问题；另外，还要保证消息队列中消息的可靠传输。

所以一些流处理框架开始出现，一方面解决这些问题，另一方面也给开发人员提供统一的开发接口，从而方便流处理的任务的开发和实现。

流处理开源框架：Storm、Spark Streaming 和 Flink

这其中最早的代表就是社交网络公司 Twitter 开发的 Storm 框架。

Storm 的一个重要概念就是数据流（Stream）。相对于批处理针对数据块的处理方式，所谓的流处理，就是针对数据流的处理方式。Storm 把 Stream 描述成是元组（Tuple）构成的一个无限的序列，如下图所示：

Stream 从水龙头（Spout）中产生，也就是说，Spout 把需要处理的数据转换为由 Tuple 构成的 Stream。然后 Stream 经过转接头（Bolt）的处理，输出新的 Stream。其中，Bolt 的处理可以是过滤、函数操作、Join 等任何操作。你可以参见下面的流程图示例：

图片中的 Spout、Bolt 和 Stream 共同构成了 Storm 中另一个重要概念，拓扑（Topology）。

你应该可以看出来 Topology 是一个 DAG（有向无环图）。Storm 框架中运行的正是一个个 Topology，而且因为是流处理，它会一直运行直到被手动终止。

基本上和 Storm 同时出现的流处理开源框架是 Spark Streaming。看到 Spark Streaming，你可能疑惑，Spark 的计算引擎不是基于 RDD 数据集，也就是数据块来处理数据的吗？它要怎么处理数据流呢？

其实无论是数据块还是数据流，都只是数据的不同使用和处理方式，它们之间是可以相互转换的。

这就像在一些编程语言标准库中的 File 操作接口，File 本身在磁盘中是按照块存储的，但是 File 操作的接口可以按照流（Stream）的方式读写文件。同样地，用户键盘输入的 Stream，或者通过网络连接 Socket 接收的数据流，也可以先缓存起来，然后作为整块的数据统一处理。

Spark Streaming 正是将数据流转换成一小段一小段的 RDD。这些小段的 RDD 构成一个流式的 RDD 序列，称为 DStream，所以它的流处理被称为“微批处理”。

显然，它的实时性取决于每小段 RDD 的大小，实时性不如 Storm 框架；不过，这种方式也使它的吞吐能力要大于 Storm。

整体来看，你可以认为 Spark（包括 Spark Streaming）基于数据块的数据模型，同时提供了批处理和流处理的能力。

那么既然数据块和数据流可以相互转换，是否存在基于数据流的数据模型，然后同时支持批处理和流处理的开源框架呢？毕竟数据输入系统的本来方式就是数据流，这样理论上可以获得更好的实时性。

答案是有的，比如 Flink。Flink 将数据块作为一种特殊的数据流，通过从文件等持久存储系统中按照 Stream（流）的方式读入和处理，来提供批处理的能力。在这个基础之上，Flink 提供了统一的批处理和流处理框架，也就是所谓的“流批一体”的数据处理框架。

Flink 虽然出现的时间不长，但凭借着优秀的设计，性能非常强，延迟可以低到微秒级别，是对实时计算性能要求的高的场景的理想选择。行业内，阿里云和腾讯云对于 Flink 的支持都非常好；很多企业也在实践中逐渐尝试使用 Flink 来替代 Storm 框架。

小结

总结一下，在这一讲中，我介绍了物联网系统的两类数据处理框架，顺便讲了很多大数据处理技术的起源和设计思想。这不是我想啰嗦，而是因为学习一个东西的时候，最有效的方式就是搞清楚它的底层原理，把握它的发展脉络。只有这样，每个知识点才能各归其位，遇到问题时你就可以顺藤摸瓜地去分析、去解决。

今天的重点，这里我再概括一下：

批处理适合海量静态数据的非实时处理，延迟比较高，也叫离线计算，主要用于离线报表、历史数据汇总等场景。

流处理适合动态输入的流式数据的实时处理，延迟低，也叫实时计算，主要用于实时监控、趋势预测、实时推荐等场景。

批处理可以选择的开源框架有 Spark 和 Flink。至于 Hadoop MapReduce，你了解一下基本原理就可以了，它在应用中应该已经被放弃了。当然，如果你有遗留系统仍然使用 MapReduce，那就只能维护着，或者找机会迁移到新的框架。

流处理的开源框架可以选择 Storm、 Spark Streaming 和 Flink 等。

另外，我还做了一张思维导图，供你在使用中参考。

技术的发展是需求推动的。随着互联网上网页数量的增多，从搜索引擎开始，大数据处理相关的技术经历了萌芽到成熟的快速发展过程，已经在电商推荐系统、广告营销、金融科技等领域得到广泛的应用。

未来随着物联网的发展，智能家居、智慧城市、工业物联网的领域应用越来越多，数据量更是极速膨胀。这一定会对大数据技术提出新的挑战和需求，新的计算框架也许也会出现，因此这是一个非常活跃的技术分支。不过，你在了解、学习新的框架时，都可以回到我这里讲的数据处理的本质来思考。

思考题

最后，给你留一个思考题吧。

这一讲我们讨论了很多批处理和流处理的内容，我们知道一个完整的业务系统，一般既需要批处理，也需要流处理，那这些不同的数据处理框架在系统中应该如何配合呢？或者说数据处理系统的架构应该是怎样的呢？

欢迎你在留言区谈一下自己的看法，或者分享一下你工作中应用的架构方式。如果你有朋友对物联网感兴趣，也欢迎你将本课程分享给他们，一起交流学习。

11 | 数据存储：物联网中的数据库有哪些？

你好，我是郭朝斌。

上一讲，我讲解了物联网系统进行数据处理的思路和常用的开源框架。那么，这些数据要从哪里读取呢？还有，数据处理完成的结果要写入到哪里呢？这就涉及到数据存储方案。

跟灵活多变的数据处理框架比起来，数据存储方案要固定得多。

为什么这么说呢？因为确定了数据存储方案，其实也就确定了用来存储数据的具体软件。当系统开发出来并投入使用后，数据就会源源不断地流入软件存储起来。

当你因为存储软件的读写性能无法满足需求，希望重新做软件选型的时候，就会发现把数据从一种软件迁移到另一种软件是一件费时费力还容易出错的事情。它就像给飞行中的飞机换引擎，难度可想而知。

更何况数据是一种重要的数字资产，之前的数据公司是不能轻易丢弃的。

所以一旦确定存储软件，一般很难切换。这就要求我们做软件选型的时候，必须非常谨慎。因为一旦做出错误的决策，将来可能追悔莫及。

那么，怎么正确地选型呢？基本的原则还是我在第 4 讲中提到的，根据数据类型来选择。

数据大体上可以分为 3 类：

结构化数据（Structured data）

半结构化数据（Semi-structured data）

非结构化数据（Unstructured data）

这一讲，我会一类一类地为你介绍选型经验。

结构化数据：关系型数据库

我们先来看看结构化数据。

结构化数据是指具有明确的、固定的结构关系的数据。

怎么理解呢？你可以回想一下班级通讯录的 Excel 表格，表头有姓名、性别、生日、家庭住址、手机，然后下面是一行一行的数据。正因为表头有明确和固定的结构，所以每一行的数据的结构都是一样的。这种用二维的表结构就可以表示的数据，就是结构化数据。

这个表结构和关系型数据库中的表结构非常一致，所以结构化数据适合使用关系型数据库来存储。基于关系型数据库，我们可以很容易地实现数据的“增删改查”，而且可以方便地实现事务操作，保证数据操作满足 ACID 特性（也就是原子性 Atomic，一致性 Consistency，隔离性 Isolation 和持久性 Durability）。

举例来说，共享单车中用户的个人信息、骑行记录和支付信息等这类属性明确，而且对于一致性要求很高的数据，一般就采用关系型数据库来存储和操作。

不过随着业务的发展，当物联网系统面临海量数据时，单机数据库的计算和读写性能会受到影响。这时，我们已经不能简单地通过提高计算机的硬件性能（垂直扩展）来解决了。

分布式关系型数据库

为了应对大数据对计算和读写性能的挑战，数据库系统需要借助分布式架构的威力（横向扩展）。

这就像为了解决“灭霸”这个难题，我们当然希望出现一个更加强大的超能力者。但是即使是钢铁侠和绿巨人，也是有能力瓶颈的，所以就需要各种超级英雄组成联盟才能达成目的。

那么怎么用分布式架构来解决大数据的问题呢？行业内一开始的实践是对数据库进行分库分表，然后借助数据库中间件实现关联查询、主键避免重复、分页查询和事务一致性等功能。

但是这种方式有很多弊端，比如分库分表需要根据业务数据的特点仔细设计，而且这种分库分表操作一定会涉及非常麻烦的数据迁移工作；通过中间件执行 SQL 的效率不高；事务一致性很难保证，往往需要在应用中实现“最终的一致性”。

为了解决这些问题，分布式关系型数据库出现了。它也经常被称为 NewSQL 数据库。它的优势主要体现以下 3 个方面。

高扩展性。NewSQL 天生支持数据分片，支持动态增加节点，不需要进行麻烦的数据迁移工作，所以能够轻松地满足数据不断增大时的存储需求。

高并发性。相比于单机关系型数据库基于磁盘的设计，NewSQL 在设计上更好地利用了内存，所以 SQL 执行效率很高。在事务的支持上，NewSQL 有着高效的分布式事务特性。所以它可以实现海量数据的读取和写入，以及大量用户的查询和更新等操作。

高可用性。NewSQL 采用 Paxos 或者 Raft 协议来实现多副本的存储，而且还支持自动选择主节点，保证了数据库的故障切换时间很短。

所以，对于需要强一致性事务的场景（比如共享单车物联网系统中的支付交易），以及需要基于关系模型进行复杂查询的场景（比如共享单车中涉及用户信息表、单车信息表和骑行记录表的丢失单车查询），当单机关系型数据库已经无法满足大数据需求时，可以考虑分布式关系型数据库。

尤其是当之前的业务已经基于关系型数据库开发完成，并且在实际服务环境中运行的时候，分布式关系型数据库简直是业务开发人员的福音。因为这种情况下想改用其他类型的数据库，如果数据库不支持 SQL 语言，业务代码就需要进行大量的修改和调整。

在实践中，分布式数据库的开源选择有 TiDB、CockroachDB 等，也有商业化的产品，比如阿里巴巴的 OceanBase 等。

时序数据库

分布式关系型数据库这个方案，是从增加能力的角度得到的。那么，我们能不能从简化问题的角度出发，找到其他解决方案呢？

从原理上看是可行的。这就像假如把灭霸的手套去掉，也许作为高中生的蜘蛛侠也可以对付他。

物联网中传感器的应用系统就是这样的场景，比如监控冷库中温度和湿度条件的传感器。这些传感器会按照一定的周期不断地上报数据（比如每 1 分钟上报 1 次）。这样的数据按照时间顺序排列，形成了一系列的数据点，所以被称为时间序列数据（Time Series Data），简称时序数据。

时序数据在读写、存储和分析处理方面有下面这些特点：

时序数据是持续地写入，一般是采用固定的频率，没有写入量忽大忽小的明显变化。数量非常大，而且并发写入的需求也很高。但是数据很少做更新，旧数据除了特殊情况下的修改，基本是不需要更新的写入操作。

时序数据的读取很少，相比写入的高并发和高频率，读取的需求主要是进行数据分析的应用，而分析应用的并发访问量是比较少的。

时序数据时效性很强，一般是越新的数据价值就越大，旧数据会迅速失去价值。

时序数据的数据分析主要关心的是新数据，旧数据被查询和分析的概率不高。旧数据一般是粗颗粒度的读取分析。而且在被分析时，一般是基于时间范围读取分析，读取某一条记录的几率很小。

基于时序数据结构和应用上的特点，人们开发出了时序数据库，它在近些年随着物联网的应用变得非常流行。

时序数据库简化了关系型数据库很多不必要的功能，比如采用读取性能不高的 LSM 树代替 B+ 树的存储结构。它专注于支持高并发的数据写入，采用更高压缩比的压缩算法来支持海量数据的存储，降低存储的成本，同时，通过预处理等方法来支持海量数据的高效分组聚合计算。

那么，时序数据库的具体产品有哪些呢？

首先是开源软件，你可以选择 InfluxDB，KairosDB 和 OpenTSDB 等产品。

另外，云服务企业一般都开发了自己的时序数据库，比如阿里巴巴的 TSDB 和亚马逊的 AWS Timestream 等。其中，TSDB 还扩展支持了空间信息，以便处理地理围栏和空间轨迹等需求。

半结构化数据：非关系型数据库

接下来，我们再看看半结构化数据。

半结构化数据包含相关标记，用来分隔语义元素，以及对记录和字段进行分层。比如，JSON 格式的数据，我们在第 6 讲的物模型中应用过，它就是一种半结构化数据。JSON 中大括号“{}”，中括号"[]"，冒号":"，逗号","就是分隔语义元素，每个冒号前面的内容是字段，后面的是记录。

我拿共享单车的运行轨迹数据作为例子，展示了一条 JSON 数据，你可以参考。

半结构化数据的特点是，它的结构并不固定，属于同一类实体可以有不同的属性，这表明它有很好的可扩展性。另外，即使它们被组合在一起，这些属性的顺序并不重要。这些特点决定了我们很难按照关系型数据库的数据模型来建立半结构数据的结构和相互之间的关联。

除了 JSON，常见的半结构数据还有 XML 等。在应用系统中呢，日志文件就是典型的半结构化数据。

为了更有效地存储半结构化数据，我们可以选择 NoSQL 数据库。它以键值对存储，且结构不固定，每一个元组可以有不一样的字段。每个元组可以根据需要增加一些自己的键值对，这样数据库就不会局限于固定的结构，可以减少一些时间和空间的开销。

比如，在共享单车系统中，我们有一款新型单车，它除了可以基于蜂窝网基站定位外，还可以基于 GPS 进行精度更高的定位。那新、老单车的运行轨迹数据就会有区别。你可以参考下面的一张对比图。

也就是说，你可以根据需求去添加自己需要的字段。比如在获取用户的不同信息时，你不需要像关系型数据库那样，对多表进行关联查询，而是只需要根据 id 取出相应的 value，就可以完成查询。

其实，我们知道 SQL 只是一种操作数据库数据的接口，所以 NoSQL 中的“No”真正表达的意思是 No Relational，专业的叫法应该是非关系型数据库。

非关系型数据库由于不再强调数据的一致性，不支持事务操作，也不再关注复杂的关联表查询，所以它对海量数据的处理性能更好，而且存储的数据格式比较丰富，易于扩展。有些非关系型数据库会使用内存来存储数据，以便支持更快的查询速度。

非关系型数据库也有很多的开源产品，比如 CouchDB、Redis、HBase、Cassandra 等，你可以根据熟悉程度和生态支持选择。商业化的选择也有 MongoDB 和 Oracle NoSQL 等产品。

非结构化数据：分布式文件系统

虽然半结构化数据的结构会根据需求变化，但多少还保留了一些结构化的描述信息。可是有一类数据却完全不能按照结构化的方法来描述，这就是非结构化数据。典型的例子有监控系统中的视频、图片和音频等信息。

非结构化数据没有预定义的数据模型，无法简单地用数据库二维表结构来表现。它的格式非常多样，标准也不是统一的。对于这一类数据的大规模存储，我们只能使用分布式文件系统。

其中最有名的当然就是 Hadoop 实现的一个分布式文件系统（Hadoop Distributed File System），简称 HDFS。因为拥有 Hadoop 的大数据生态，所以它是分布式文件系统的最理想的选择之一。其他的分布式文件系统还有 FastDFS 和 Ceph 等。

小结

总结一下，在这一讲中我主要讲解了关于数据存储方案的选择。重要的事情说三遍：

选型一定要慎重！

选型一定要慎重！

选型一定要慎重！

错误的决策不仅会影响系统的性能，还会增加成本投入。

基于不同数据类型的特点，选择方案的原则如下：

结构化数据，比如用户和设备的关系，用户信息、设备参数等，还是适合关系型数据库。为了应对海量数据，你可以采用分布式数据库，有 TiDB、CockroachDB 等，也有商业化的产品，比如阿里巴巴的 OceanBase 等。另外，物联网中的传感器设备，随时间不断产生新数据。要存储这类数据，你可以选择时序数据库，来获得更高的读写和查询性能。

半结构化数据，比如 JSON 结构的数据，日志记录等，一般采用 NoSQL 数据库产品。常见的开源选择有 MongoDB、CouchDB、Redis、HBase 和 Cassandra 等，商业化的选择有 MongoDB 和 Oracle NoSQL 等产品。

非结构化数据，比如视频和音频，一般采用分布式文件系统。Hadoop 体系中应用广泛的 HDFS 是非常理想的产品。除此之外，FastDFS 和 Ceph 也可以作为选择。

我整理了一个数据存储方案相关的思维导图，供你参考。

延伸：数据迁移工具

在这一讲一开始的时候我就说过，数据的迁移工作比较麻烦，需要在设计阶段就尽量避免。但是，有些数据迁移的需求可能是你避免不了的。比如你要把已运行系统中 MySQL 数据库里的数据，导入到 NoSQL 数据库 HBase 中，以便使用大数据技术进行分析处理。

对于这样的需求，我们就得借助工具来完成，比较有名的工具就是 Sqoop 项目。它在 Hadoop 大数据存储系统和关系型数据库等系统之间架起了桥梁，借助 Sqoop，你就可以很方便地把 MySQL 数据导入到 HBase 中。

另外，Sqoop 采用了被称为 Connector 的插件架构，不同的 Connector 还可以对接不同的数据源，而且你也可以根据自己的需求定制专属的 Connector 来完成一些特殊的迁移工作。所以，Sqoop 也可以完成 NoSQL 数据库（比如 CouchDB）和文件存储系统（比如 FTP）之间的迁移任务。

当然，Sqoop 也有一个缺点，那就是不太适合增量的数据更新，又叫 CDC（Change Data Capture）。如果基于 Sqoop 采用定时扫描整张表的方法，那么执行会比较低效，延时也比较严重。

这时，你可以考虑 LinkedIn 开源的 Databus 项目，或者阿里巴巴的 Canal，它们都是基于分析数据库日志文件来高效地实现数据的增量更新。

思考题

最后，给你留个思考题。

除了从数据类型上考虑，在选择数据库时，我们还要结合业务的需求来判断，因为我们的最终目的是高效地、低成本地解决业务问题，比如时序数据库可以更高效、更低成本地解决传感器监控应用的数据存储需求。你能从处理性能和成本的角度，分析一下 NewSQL 数据库、NoSQL 数据库和分布式文件系统针对的业务分别有什么特点吗？

欢迎在留言区谈谈你的思考，也欢迎你将这节课分享给你的朋友一起学习。

12 | IoT Hub：面对海量设备如何打造高性能设备接入层？

你好，我是郭朝斌。

前面两讲，我们一直在谈物联网云平台针对海量数据的处理和存储技术。顺着物联网的数据技术体系继续往下探索，我们自然就会面临一个问题：物联网云平台的服务器需要管理海量设备的接入，并且接收来自设备的海量数据的输入，那么服务器要怎么应对这样的挑战呢？

我们来分析一下这个问题。物联网设备是通过某种通信协议接入云平台的，比如常用的 MQTT 协议，那么设备接入的服务器就是 MQTT Broker 服务器。

从架构设计来说，负责设备接入的这一部分一般叫作设备接入层，也被称为 IoT Hub。设备接入层之后，才是和互联网系统类似的业务层。具体的系统架构图可能是下面这样的：

不过，估计你也发现问题了，这样的架构根本无法支撑物联网场景中海量设备的接入和海量数据的输入。单台 MQTT Broker 服务器很容易面临性能瓶颈。所以前面提到的“服务器怎么应对挑战”的问题就变成了：怎么打造高性能的设备接入层？

我们再继续深入分析，把关键点定位得更具体一点。打造高性能的设备接入层，最重要的技术难点是，如何实现接入层的高并发。因为只有具备高并发的能力，才能有效地、可靠地实现数据的传输。

所以，现在问题又变成了：物联网云平台中，怎么实现高并发的设备接入层？

接下来，我就带你一步一步地设计一个高并发的设备接入层。在这个过程中，我会为你详细地讲解我们需要用到的重要技术，让你为物联网平台的搭建做好技术储备。

负载均衡：让多台服务器更好地协作

上一讲曾经我提到，为了解决灭霸这个难题，要让超级英雄们组成联盟。同样地，面对性能瓶颈，我们可以集合多个服务器的力量来解决问题，也就是说，可以通过增加多台 MQTT Broker 服务器来满足海量设备的连接请求。

但是服务器数量变多之后，它们具体要怎么协作呢？比如一个 MQTT 请求过来了，它应该被分配给哪台服务器处理呢？更进一步地说，怎么保证每台服务器的负担和压力都基本平衡呢？

这就要用到负载均衡技术。负载均衡（Load Balancer）这个名字非常形象，我们可以分成“负载”和“均衡”两个词来理解。

负载是指服务器面对的网络连接和服务请求的压力，也就是 “困难”。

均衡是针对服务器压力的解决办法，多个服务器一起来处理任务，并且这几个服务器的压力要达到平衡的状态，也就是“分担”。

这种平衡状态要怎么实现呢？我们需要设计一种任务分配策略。

最简单、直接的办法就是平均分配。

比如有 3 个服务器都在等待分配。当一个服务请求到来时，负载均衡服务器先将它给服务器 1 处理；接着第二个请求到来时，给服务器 2 处理，以此类推。

当第四个服务请求需要处理时，负载均衡转回来，再次指定给服务器 1，循环轮转。所以这种策略也叫轮询策略（Round Robin Scheduling）。

如果用公式来表达这个服务器序号的计算方法，那就是：

i=(i+1)mod(n)

其中 i 是服务器的序号，从 0 开始计数；n 表示服务器个数，这里 n 等于 3。

当然，在现实环境中，各个服务器的配置并不是完全一样的，有的任务处理能力强，有的能力弱些。这就像在复仇者联盟中，每位英雄的强度也各有不同。

我们自然希望能力强的服务器多负担一些任务，所以对于轮询策略，我们可以引入权重系数。

比如服务器 1 的权重是 2，服务器 2 的是 4，服务器 3 的是 1，那么负载均衡服务器就可以给服务器 2 多分配任务，而给服务器 3 分配最少的工作。

这个改良版的策略就是加权轮询策略（Weighted Round Robin Scheduling）。

不管是轮询策略，还是加权轮询策略，它们具有简单实用的优点。但是通常情况下，服务器处理每个任务的时间是不同。尤其是在物联网场景中，设备可能会跟服务器保持长连接。可能有的服务器被分配的设备比较少，但是保持连接的设备却很多。这种情况下，如果我们仍然使用轮询策略，那么各服务器的负载就很难达到平衡状态。

所以我们需要一种动态调度的策略，能够基于各服务器的网络连接数情况，优先将新的任务分配给保持连接的设备数最少的服务器。这种基于连接数的策略就是最小连接数策略（Least Connection Scheduling）。

我们也可以为最小连接数策略引入权重系数，给不同能力的服务器分配不同的权重，更好地平衡服务器的负载，这就是加权最小连接数策略（Weighted Least Connection Scheduling）。

这个策略是怎么计算的呢？假设服务器的权重分别是 W1​、W2​ 和 W3​，服务器的连接数分别是 C1​、C2​ 和 C3​，那么计算的表达式是这样的：

Min(C1​/W1​,C2​/W2​,C3​/W3​)

负载均衡服务器会选择计算结果是最小的那台服务器。

如果有 2 台或者 3 台服务器的计算结果同时最小呢？这时，我们还可以在这几台服务器中采用轮询策略。所以说，分配策略不是相互排斥的，在实践中我们需要综合使用不同的策略。

考虑到这个原因，我需要给你再讲一种常用的分配策略，那就是源地址哈希值策略（Source Hashing Scheduling）。

所谓源地址，一般就是指网络连接的源 IP 地址。负载均衡服务器通过计算源 IP 地址的哈希值来确定对应的服务器。因为相同 IP 地址的哈希值是不变的，这就保证了相同的设备可以连接到固定的服务器上。

在实践中，我们可以通过开源软件来搭建负载均衡服务器。比如 HAProxy 软件，它支持OSI 网络七层模型中第 4 层和第 7 层的负载均衡，性能可以跟商用解决方案媲美。同时，我建议搭配 Keepalived 软件使用，实现高可用的热备方案，这样就可以避免单机故障导致系统瘫痪了。

消息队列：避免耗时的等待

当负载均衡服务器将物联网设备的网络请求分配到 MQTT Broker 服务器后，MQTT Broker 服务器就可以与设备建立连接，并且收到设备上传的数据了。然后，数据就会传输给数据流处理服务器或者写入数据库中。

但是，数据流处理和把数据写入数据库都是花时间的操作，它们和数据传入的速度并不一致。为了可以让 MQTT Broker 服务器高效地完成数据的传输，同时保证数据流处理和写入数据库的操作可靠执行，我们需要在 MQTT Broker 服务器和数据流处理服务器之间加入异步处理机制。

那么，异步处理机制要怎么实现呢？行业内一般通过消息队列来实现。你可以把消息队列想象成双十一购物节后的快递公司，它收到商家大量的运单，然后快递公司按照一定的节奏完成这些运单的递送任务。

在我们的系统中，MQTT Broker 服务器将数据给到消息队列，就完成了数据传输的工作。在这之后，数据流处理和写入数据库的操作只要按照自己的节奏，消费消息队列内的数据就行了。

另外，消息队列还提供了额外的两个好处：

实现了 MQTT Broker 服务器和数据流处理服务器之间的解耦，双方没有直接的依赖，所以维护更新会更加方便。

可以平衡输入数据量的大小变化，所以数据流处理服务器不会因为骤增的压力而崩溃。

既然消息队列这么重要，你肯定也想知道，在实践中有没有开源的软件可以选择呢？答案是有的。最常见的选择有 Kafka 和 RabbitMQ 等。

Kafka 真是一个神奇的软件，凭借着优秀的设计成为消息队列系统的主流选择。而且在这个成功的基础上，它还在不断改进，新增了分布式流处理和分布式存储等功能。虽然这些新功能不一定比专门的流处理和存储软件更强大，但对于中小型的业务来说完全够用了，关键是非常方便，部署也简单。

缓存系统：让数据读写更快速

从架构图中我们可以看出来，数据流处理服务器处理完数据后，这些数据会存储到数据库中，提供给批处理或者业务服务器使用；而且它在进行数据处理时，也需要从数据存储中获取一些信息。

但是数据库的读写数据是一个速度比较慢的操作，尤其是基于磁盘介质存储的数据库。为了提高性能，我们需要比数据库，甚至分布式文件系统更快的数据存取方式，这就需要用到缓存系统了。

缓存是一个常见的概念。比如浏览器会使用缓存来避免重复从网络获取网页数据，从而更加快速地响应用户的请求。

在我们的系统中，引入缓存系统当然是为了避免直接从磁盘中读取数据，或者直接向磁盘中写入数据。

缓存系统一般会将数据暂时存储在内存中，这样数据流处理应用就不需要直接与低速的磁盘打交道了。而且，如果我们读取的是经常用到的热点数据时，这些数据全都不需要重复从磁盘读取。这样既减轻了数据库的压力，又提高了数据处理速度，一举两得。

缓存系统的常用开源选择有 Redis 和 Memcached 等。其中 Redis 更是在数据持久机制和主从节点复制的高可用特性上做了很多工作，不但功能强大，而且效率也很高。

加入缓存系统之后，设备接入层的整体系统架构就完成了，你可以参考下面这张图。

小结

总结一下，物联网云平台的服务器为了应对海量设备接入和海量数据输入的挑战，需要打造高并发的设备接入层。所以在这一讲中，我带你设计并完善了设备接入层的整体架构，并讲解了需要用到的负载均衡、消息队列和缓存系统等技术。

负载均衡用来协调多台服务器来共同应对网络连接和请求的压力，服务器任务分配策略主要有三大类，分别是平等分配的轮询策略，考虑连接设备数的最小连接策略，以及保证相同的源地址访问同一台服务器的源地址哈希值策略。

这些策略可以搭配使用，还可以通过引入加权系数来改进。在实际应用中，我们需要根据场景灵活选择，常用的开源方案有 HAProxy 软件和 Nginx 软件，部署时可以搭配 Keepalived 提高可用性。

消息队列可以在不同的系统之间搭建桥梁，保证数据和服务请求的高效可靠处理。常用的开源软件有 Kafka 和 RabbitMQ 等。

缓存系统可以减轻数据库的压力，提高系统响应速度。常用的开源软件有 Redis 和 Memcached 等。

思考题

最后，我想请你思考一个问题。

在这一讲中，我们通过负载均衡来分担服务器的压力。这里设备是使用 MQTT 协议与接入层服务器，也就是 MQTT Broker 服务器通信的，所以不同的设备发送的相同 Topic 的消息就会发送到不同的服务器上。

如果有订阅者订阅这个 Topic 消息，那么应该怎么保证订阅者可以接收到所有的设备发送的此 Topic 的消息呢？

欢迎你在留言区写一下自己的思考，同时也欢迎你将本讲分享给对高并发感兴趣的朋友一起讨论学习。

13 | 隐私：在实践中如何保护用户隐私？

你好，我是郭朝斌。

在前三讲中，我带你完善了对物联网系统的数据技术体系的认知，包括处理框架、存储方式和设备接入等。

这些内容涉及到很多大数据技术，比如基于数据分析结果，提前调度共享单车的位置；还有基于你的搜索记录和购买历史，向你推荐商品。

在大数据时代，还有一个话题也是我们非常关心的，那就是这一讲要说的数据隐私。这个话题涉及的技术知识点不多，所以你学起来会相对轻松一些。

为什么要了解隐私？

不过，你可能还是要问了：在这个技术类的课程中，为什么要讲这个跟技术关系没那么大的内容呢？作为开发人员，有必要了解这些吗？

其实这样安排有三个原因。

第一，面对产品的需求和设计时，我们最好能做到“知其所以然”。

因为只有这样，才能更好地审视需求，在完成工作的同时，也能提升对业务的理解。

比如，你在手机上新安装了一个应用，打开之后很可能会看到一个隐私条款的弹框。你可以参见下面的图片：

当产品经理向你提出需求，让在你开发的产品中增加这个弹框的时候，你可能会想：为什么要多此一举，设计一个只能同意的隐私条款弹框呢？难道产品同学今天没休息好，怎么关于用户体验的经验还不如你？但是学完这一讲，你就会知道，其实这是隐私法律的要求。

第二，正因为相关的法律法规已经施行，我们需要了解和遵守法律法规，保护用户的隐私数据。

一方面，这要求你在系统开发中采取必要的措施保证数据的安全，比如对数据进行脱敏处理；另一方面，虽然你可能接触到非常多的用户数据，但一定不能随便泄露数据，因为这是违法的。

类似的情况还有金融圈的 P2P 网贷。当初很多开发人员不懂法，网贷公司跑路之后，他们却糊里糊涂地遭受了牢狱之苦，连之前的工资收入也被没收。

近几年，也已经有很多人因为隐私案件而遭遇类似的经历。比如某公司通过违规收集简历，进行个人数据的违法利用，当公安进行查处时，公司所有员工都被牵扯进来。

另外，如果你的产品要出海，那么国外重要市场的隐私政策也得处理好。因为有些国家的法律法规比较成熟，仅仅是数据防护不力就会导致巨额的罚款。

第三，物联网的应用领域（比如智能家居）可能是一个大生态系统，我们也要满足对接平台的政策要求。

我们开发的产品可能要对接到小米等平台上。这些平台一般都有严格的隐私政策（这些政策可能比法律法规的要求更严格），隐私方面的合规检查是产品上架流程的重要一环。这就要求我们对相关政策要求做到心中有数，在产品开发过程中，能够提前做好隐私数据的规划。

什么是隐私数据？

现在你可能要问了：要求这么多，是不是数据相关的功能和应用都不能做了呢？可是为什么我在电商平台上，还是可以看到很多商品推荐信息呢？

这就涉及到对隐私数据的定义了。到底什么是隐私数据，什么不属于？在实际的工作中，如何把握这个度呢？只有搞清楚这个关键问题，你才能更好地把握隐私法规，做好数据应用工作。

我先举个例子，你来判断一下。比如，一些平台会收集你的历史消费信息、生活的城市和使用的手机品牌等数据，构建你的“用户画像”，然后给你贴一些标签。

你要是被贴上了“一线城市”“高收入”和“消费频繁”等标签，它们就会觉得你对价格的敏感度低，对平台的粘性高，最后把同样的商品以更贵的价格卖给你。

请问这触犯了隐私法规吗？当然，这其实就是所谓的“大数据杀熟”。2018 年，它甚至被评为年度社会生活十大流行语，引起了人们的广泛关注，你我可能都是受害者。

大数据杀熟和商品推荐系统一样，都利用了用户画像。但是商品推荐系统是不违法的，在实践中有很多应用，而“大数据杀熟”却被明确禁止，这是为什么呢？

它们的区别主要在于，“大数据杀熟”针对的是特定的个人。当个人被区别定价、达成交易时，这就是对特定个人的数据滥用。

个人信息（或者说个人数据，不同国家的叫法不同）可以分为个人隐私信息和个人一般信息，隐私法律法规主要关注的是个人隐私信息，包括身份证件号码、个人生物识别信息、银行账户、通信记录和内容、财产信息、征信信息、行踪轨迹、住宿信息、健康生理信息、交易信息以及 14 岁以下 (含) 儿童的个人信息等。

另外，通过个人一般信息和其他信息的加工处理形成的信息，一旦泄露、非法提供或滥用，可能危害人身和财产安全，或者导致个人名誉等其他损失的，也属于个人隐私信息。比如，有人通过一个明星在微博上发布的图片，推测出明星的住址。

而在万物互联的物联网时代，隐私泄露的风险更大。因为各种设备收集的数据都会上传到云平台，数据的维度和数量都比互联网应用更加丰富，比如你身体的各项生理指标、汽车的行驶记录和家里智能摄像头的影像等等。

同时，现在大数据处理技术、挖掘算法，更是可以基于这些对你来说非常敏感的信息，得出其他的敏感信息。比如你的驾驶习惯好不好，经常开车去什么地方，什么时间出入停车场，汽车保险公司肯定非常想知道。

面对这样的趋势，谁也不希望成为隐私泄露的受害者，变成一只“待宰的羔羊”。所以用户自然也会更加注重数据安全，甚至把这一点作为选择物联网产品的关键决策因素。我们国家也在加强隐私相关法规的完善，规范企业在个人信息利用中的行为。

于是，隐私保护的诉求给物联网的产品和服务提出了更高的要求。那么作为产品的设计者和开发者，你在实践过程中应该怎么保护用户的隐私呢？

目前，你在工作实践中，首先要做到的就是隐私合规，也就是满足各个国家和地区的相关法律法规的要求。因为这是硬性要求，是第一步，只有这样，你的产品才可以顺利地销售或者提供服务，否则就要面临下架和罚款等处罚措施。

国内隐私法律法规

我们先关注一下国内的隐私法规情况。

这一讲开头提到的隐私条款弹框，其实就是《信息安全技术 个人信息安全规范》的要求。它是对《网络安全法》的补充，是国内第一个关于个人信息的实践标准。

有法可依之后，公安网安部门已经针对 App 等网络服务进行了多次个人信息违法问题的整治行动，其中就有像联网汽车 App 这样与物联网有关的案例出现。

另外，我国也在积极推进个人信息保护法的制定。在 2020 年 10 月 13 日，《个人信息保护法》草案已经提交到人大常委会讨论。也许在不久的将来，就会有正式的法律落地。

出海要注意什么？

看完国内的法律法规，我再来介绍一下国外的情况。

国内企业的出海是越来越明显的趋势，尤其物联网行业里面的智能硬件企业，他们借助我国完整的产业链，具有得天独厚的优势。

在出海过程中，关注国外的隐私法律就成为必须做的事。在实践中，你需要重点关注的是几个主要市场的要求。

欧盟：GDPR

我们先看一下欧盟。欧盟的GDPR（General Data Protection Regulation，通用数据保护条例）知名度非常高，全世界都在关注。它对个人数据进行了全生命周期的保护，被称为全球最严苛的隐私政策。

在 2018 年 GDPR 生效之后，欧盟马上就依据数据跨境传输等原因，给以 Google 为代表的美国企业开出了 5000 万欧元的罚单。GDPR 不仅适用于当地公司，而是对所有在欧盟提供的产品和服务的公司都提出了限制。它要求个人数据必须落地在欧洲的服务器存储，不能随意传输到其他地区。

美国：《加州消费者隐私法》等法案

接着我们再看看美国的情况。美国比较特殊，它是海洋法系，系统法律条文不多，主要靠援引之前判例，而且每个州的自主权也比较大。所以整体来说，美国隐私相关的法律法规是散落在各个州，或者金融、保险、医疗和电信等领域的各种法案当中。

其中，信息产业发达的加州在 2018 年 6 月生效了《加州消费者隐私法》（California Consumer Privacy Act），这是目前美国最全面、最严苛的隐私法规。

另外，美国也有人在推动全国范围隐私法规的建立。比如苹果的库克就建议制定完整的联邦隐私法，当然，这对于 Google 和 Facebook 这些企业是不利的，因为这些企业的服务更依赖个人信息。

出海需要重点关注的法律法规

总体来说，每个国家和地区都有自己的法律体系和发展节奏，而且法律工作本身是一件非常专业的事情。所以你在出海过程中，最好找靠谱的、有经验的咨询机构合作，保障产品在法律上不出问题，避免不必要的经济损失。

我整理了一个表格，列举了在个人信息方面，你需要关注的主要国家和地区的法律法规，供你参考。注意，这里“个人信息”“个人数据”和“隐私”三个名词意思差不多，只是不同地方的表述习惯不一样。

怎么把握繁多的法律法规？

法律法规的内容非常多，作为行业从业人员，我们也不可能把所有条文都背得滚瓜烂熟。那么，有什么简单的办法来指导日常工作吗？我想，你在实践中可以把握下面这 4 个基本原则。

第一，保证用户有知情权和选择权。

如果需要收集用户的个人信息，我们需要告知用户，征求用户的授权，而且要用显著的、容易懂的方式说明信息的使用方式、使用范围和目的。

比如我刚才提到的隐私信息弹窗就是一个例子。而且，用户还要能够决定是否同意授权，以及授权之后再取消授权。你在做智能硬件配套的 App 时，可以留意这方面的要求。

第二，收集、使用信息时要采用最小必要原则。

这个比较好理解。我们收集使用的信息应该尽量少，只要能够满足产品和服务的功能需求就足够了。比如智能台灯，我们只需要接收的房间信息就可以实现语音控制和交互，并不需要收集准确的 GPS 定位。

第三，保证用户有信息控制自主权。

我们收集的信息的主体仍然是用户，也就是说，用户随时可以查看自己的数据，可以修改、甚至删除它们。你可以在 App 或者网页中提供这样的入口。

第四，确保个人信息存储和使用的安全。

收集了用户的个人信息，我们就需要保证个人信息的数据安全，从内部人员的管理和数据的使用流程上把控风险，比如将数据中的个人指向性内容去掉。同时，我们也要做好系统的安全防护工作，防止被攻击导致数据泄露。在下一讲中，我还会讨论更多关于系统安全的内容。

从这些原则出发，你就可以更好地理解和满足相关法律法规的要求，做好产品和服务的合规工作。

从用户的角度出发的设计

以上内容主要是从法律角度来讨论这个问题。但是从根本上说，你还是需要从用户的角度出发，在为用户创造价值和解决问题的同时，保护好用户的隐私。

法律永远是滞后的，法律也总有漏洞，各种利用个人隐私信息获利的新方法会不断出现。但是用户会“用脚投票”，选择隐私保护更好，更值得信赖的产品和服务。从这个角度考虑，隐私保护虽然需要企业投入成本，但是它也可以成为企业独特的竞争优势。

比如，国外有一个搜索引擎公司 DuckDuckGo，在棱镜门事件后，日均使用量一年就翻了一倍，近几年用户量也是稳步增长，2020 年 8 月用户量就达到了 6500 万。它吸引用户的一个关键点正是强调用户隐私，不追踪用户，也就是匿名性。这和 Google 这样的搜索引擎恰恰相反。

除了匿名，你还可以像苹果公司一样采用“差别隐私”的方法，通过在数据中增加“噪音”，或者修改数据隐私信息，来保护用户的个人信息泄露。

我再举一个互联网应用的例子，因为你日常中接触得更多。

当你在微信中分享照片，无论通过聊天界面还是朋友圈，微信一般都会对照片进行压缩。有时候你可能还会抱怨微信把照片处理得不够清晰，所以专门选择发送原图。

其实微信在压缩照片的同时，都会修改照片的 EXIF 信息，它包含拍摄时间、地点等隐私信息。如果你的照片被“有心人”利用，他们可能通过数据分析，直接精确地定位到你家小区，是不是很可怕？

其实微信还有很多类似的从用户角度出发的设计。比如微信朋友圈中的“给谁看”“不给谁看”“仅自己可见”等，就是给用户控制数据可见范围的选项。这样用户会更有控制感。

另外，可以减少信息的展示时间，甚至避免信息的永久存储。最知名的就是聊天软件上的“阅后即焚”功能。还有微信中聊天记录不会被云端永久保存，朋友圈中的“三天可见”“半年可见”，也是同样的设计思路。这种设计可以让用户更放心地分享内容，提高用户体验的同时，也增强了产品的粘性。

那基于同样的思路，在硬件上我们也可以多采取类似的产品设计方法和角度，提高用户的信任感。

比如，智能手表设备会收集很多用户的个人生理指标数据，而且一般在设备上有数据的存储功能。这种存储当然出发点是为了缓存数据，保证数据在手机或者云端的可靠获取，但是它也存在泄漏的风险。

那我们就可以增加一个功能，让用户可以随时通过手表本身，不用借助手机等其他设备就可以永久删除存储的历史数据。这样的设计会让用户更加放心地使用产品。

小结

总结一下，在这一讲中，我分析了学习隐私知识的必要性，重点讲解了隐私数据的概念，并介绍了国内、国外关于用户隐私的法律法规和把握这些法规的基本原则。

除了法律法规，我也谈了从用户角度出发，做好隐私保护工作的思路和方法。了解了这些，你才能在物联网行业从业过程中做好合规工作，并且设计出让用户真正感到满意和放心的功能。

从懂业务、懂法律和懂生态的角度，我们作为开发人员都需要掌握隐私的相关知识。

在隐私法规上，个人信息可以分为个人隐私信息和个人一般信息。我们需要重点关注个人隐私信息的保护。

在隐私保护方面，我国已经生效的法律有《网络安全法》和《个人信息安全规范》。另外，《个人信息保护法》的草案已经提交人大常委会讨论，在不久的将来应该也会正式发布。企业出海时，需要关注当地的法律，比如欧盟的 GDPR，以及美国各个州和各个领域的相关法案。为了保证出海工作的顺利进行，我们最好还是找专业的咨询机构合作。

作为物联网行业的从业者，在实践中要把握 4 条基本原则：第一，保证用户有知情权和选择权；第二，收集、使用信息时要采用最小必要原则；第三，保证用户有信息控制自主权；第四，确保个人信息存储和使用的安全。

让产品和服务符合法律法规的要求只是第一步。在产品理念，或者说底层价值观上，我们要从用户角度出发，重视对用户隐私的保护。具体的方法有增加匿名性，采取差别隐私，让用户可以控制数据可见范围和减少信息的展示时间甚至避免永久的存储。

我整理了一张思维导图，供你参考。

隐私政策在近几年备受关注，这背后的本质原因，也许是科技的发展、数字化深入生活的必然趋势。用户只有让渡一些个人信息才能获得更好的服务，或者更有利于需求的解决；但是任何时候，用户的个人信息都不应该被滥用、被泄露、被非法提供进行交易。

正如张小龙说的那样：“善良比聪明更重要，AI 比我们更聪明、更懂套路，但我们可以比他更善良。”只有这样用户才会信任你，你也才能拥有一个长久的、有生命力的产品和服务。

而如果作为消费者，我们也需要了解这些法律法规的基本信息，保护好个人的隐私数据，并且遇到问题时，把它们作为维护自己权益的工具。

思考题

最后，我给你留一个思考题吧。

智能家居中，用于安全监控的摄像头有很多的价值，但是很多人担心它的安全性，害怕隐私数据的泄露。如果你来设计摄像头产品，从用户的角度出发，会做哪些设计来增加产品的认可度呢？

欢迎你在留言区写一写自己的思考，也欢迎你将这一讲分享给对隐私保护感兴趣的朋友一起学习。

14 | 安全：物联网平台如何应对安全风险？

你好，我是郭朝斌。

在第 13 讲的思考题中，我提到了用户对于摄像头泄露隐私的担忧。这样的顾虑并不是杞人忧天。在现实生活中，物联网设备确实面临很多安全问题，而且这些安全问题已经对个人信息安全构成威胁。

比如 2019 年 12 月，在美国非常流行的家庭安防产品Ring 爆出安全漏洞。黑客不但可以远程查看 Ring 的监控视频，还获取到了家庭 Wi-Fi 的密码，甚至可以把收集的用户音频数据分享到开放的播客节目中。

作为物联网开发人员或者安全技术负责人，我们肯定不想看到自己负责的产品和服务上负面新闻。为了跟用户建立信任，我们必须切实做好安全工作，打造可靠的产品和服务，保护数据不被泄露。

你可能已经对于互联网的安全工作有一些了解，甚至有一定实践经验。不过在物联网领域有很多新的变化，所以我们需要重新思考安全工作该怎么开展。

在物联网设备上，安全的实践并不像在电脑上那么成熟。虽然现在的公司也越来越重视设备的安全问题，但是还是经常出现失误。

更何况，很多设备都是创业公司或者外包团队研发生产的。它们本身的盈利能力还比较弱，没有足够多的资源投入到安全方面，所以这些设备面临的安全问题往往更加严重。

总的来说，安全问题主要反映在两个方面：第一，公司没有针对物联网的特点建立起完善的安全管理流程和制度；第二，在具体的技术实践中，没有考虑物联网面临的新的安全问题。

所以在这一讲中，我就从管理和技术两个方面出发，跟你分享一些物联网的安全实践经验。

安全管理规范

首先，你需要建立一个安全管理规范，从机制和组织上保障安全，不能因为各方面资源的限制而放松要求。

从实践的角度出发，我把安全管理分为开发、生产、部署和运营4 个方面。

1. 开发：基于安全开发生命周期建立流程

开发是产品和服务的基础，是最关键的地方，所以我先说说开发。

在开发过程中，怎么保证安全，满足合规性的要求呢？你可以基于安全开发生命周期（SDL，Security Development Lifecycle）来建立企业的安全开发流程。SDL 早已是保证开发安全的公认方法论。它能够减少安全漏洞的数量、降低安全漏洞的危害，从而帮助你做出更安全的产品和服务。

SDL 的整个周期由六个过程组成，我总结在了下面这张图片里。

2. 生产：降低由工厂带来的安全风险

跟纯软件产品不同，物联网设备还需要在工厂生产。那么，在生产中你要注意什么安全问题呢？注意，这里的“安全”不是指人身安全（Safety），而是指出厂设备的安全（Security）。

首先，你在选择合作工厂的时候，要确保对方具备一定的质量体系流程，最好有第三方的质量体系认证。这样可以一定程度上保证工厂在流程上的安全工作。

其次，你在设计产品的时候，一定尽量减少在工厂进行涉及安全的操作，比如设置安全秘钥等。这样可以最大限度地减少工厂操作失误和数据泄露带来的问题。

最后，还有一些有安全风险的操作是必须在工厂进行的，比如设备固件烧录和硬件电路板组装等。对于这些操作，你要和工厂一起确定好流程，做好固件版本和硬件批次的控制，同时在成品质量检测中增加软、硬件版本的检查项目。

3. 部署：从软件和硬件两方面考虑

在物联网产品和服务投入正式使用之前，我们还需要进行软件和硬件的部署工作。为了保证系统的安全，你需要采取哪些措施呢？

一方面，你要做好软件的安全部署。

在服务器上部署软件的时候，你要检查防火墙的配置，保证不必要的端口都是关闭的。另外，软件的各种安全设置都要打开，不能让安全设计沦为摆设。

另一方面，你也要做好现场的硬件安全部署。

在现场部署硬件设备的时候，你需要考虑设备被随意获取、暴力拆解和分析的情况。因为拿到设备进行研究，往往是对物联网设备进行安全攻击的第一步。尤其是那些不能从商场买到的，或者部署在室外的非普通消费品，比如智能电表，还有无人值守停车场部署的计费设备等。

所以你得采取一定的物理防护措施，比如安装监控摄像头或者额外的防护装置（比如外壳等），让攻击者不能轻易地获取到设备。

另外，在设备初次入网或者重新入网的时候，你要保证操作人员的可信度，并做好安全引导工作。同时，你必须要求操作人员把设备的一些控制接口关闭，或者修改初始默认密码。比如对于网关设备，你可以关闭本地管理界面功能，或者修改初始的密码，这样可以避免被攻击者轻易地破坏。

4. 运营：把安全融入持续的运营过程中

最后，在产品和服务运营的过程中，我们还需要做好监控、运维和应急响应组织建设这 3 个方面的安全事项。

监控可以保证及时发现问题，是非常重要的运营手段。

比如，你可以通过系统实现对设备和平台系统的日志进行自动化监控和分析。这可以帮助你从一些记录中（比如系统尝试提权和认证异常等）及时发现异常行为。

还有一些监控发现的事件，是需要及时通知用户的。比如异常登录，系统要及时通过短信或者邮件告知用户风险。

运维是确保服务和设备安全状态的重要工作。

除了做好云平台服务器的运维工作之外，你还可以在系统中增加设备资产的管理功能，这有利于跟踪设备状态和固件更新等工作。

另外，很多物联网设备中存储有秘钥、证书等敏感信息。所以对于所有被淘汰的物联网设备，你都要做好退役期处理，把关键信息从设备上擦除掉，同时在云平台上对设备做归档、标记等处理。

应急响应中心是互联网公司在长期的安全实践中做出的非常好的创新。

在互联网发展的初期，一些“白帽子”黑客就算发现问题，也很难流畅地反馈给企业处理，甚至会被认为是勒索。但目前主流的公司基本上都会设置安全应急响应中心，甚至鼓励在一定的范围内跟白帽子合作，来提高系统的安全性。这是你在实践中值得借鉴的方法。

安全技术保障

刚才说的安全管理规范，只是从制度上来减少安全漏洞和安全事故，并对安全事件进行及时响应和处理。但是实际工作中，你还需要从技术上来保障安全。

怎么做好技术保障呢？我们知道物联网从架构上可以分为三层，设备层、网络层和应用层，你可以从这三个层面入手来做好安全工作。

现在我以共享单车为例做一个讲解。

第一步：分析安全需求

共享单车，你我都很熟悉，我们很容易就能分析出它的安全需求：

保证单车资产的安全

确保开锁、计费等关键功能和数据的完整性

保证个人信息、轨迹信息等敏感信息的保密性

确保云平台、App 等整体系统的可用性

接着，我们细化这些需求，列出明确的指标，然后跟公司的安全基线对比，看是否符合安全基线的要求，对于不符合的部分是否可以放行。

公司的安全基线是从哪里来的呢？一是公司自己的实践经验，二是最佳安全实践指导文件。

这些指导文件来自安全公司或者安全组织，比如国内的绿盟，国外的 CSA（Cloud Security Alliance，云安全联盟）、IIC（Industrial Internet Consortium，工业互联网联盟）和 IoTSF（IoT Security Foundation，物联网安全基金会）等。

另外，还有一些行业相关规范涉及的安全内容，包括我在第 13 讲介绍的隐私相关法律法规对于安全防护的要求，这也是公司安全基线的一个来源。

第二步：构建威胁模型

明确了安全需求之后，下一步就是构建共享单车系统的威胁模型。建模可以帮助我们全面地了解共享单车系统中各个角色、接口和数据流，然后分析清楚系统面临的攻击面和信任边界。基于攻击面和信任边界，我们就可以更好地识别系统的潜在威胁。

至于建模的具体方法，我们可以使用 OWASP 提出的威胁建模备忘清单。它的好处是有一个开源的工具可以使用，名称是Threat Dragon。除了这个，我们还可以选择微软的威胁建模工具，它也是免费的。

首先，我们基于共享单车系统的组成绘制出数据流图。下图是我使用 Threat Dragon 工具创建的共享单车系统的数据流图。（注意，这里只是举例，现实中的单车系统可能与此不同。）

共享单车数据流图

基于数据流图，我们就可以做威胁识别了。你可以借助微软的STRIDE 模型来完成，这也是 Threat Dragon 支持的威胁定义方法。

STRIDE 模型将威胁分为六种不同的类型：欺骗、篡改、否认、信息泄露、拒绝服务和权限提升，它们在下面的威胁识别表格中都有体现。

共享单车威胁识别表

识别完威胁以后，我们需要归档，然后根据发生概率和可能的危害程度，对它们进行评估。评估可以使用微软的DREAD 模型来完成。它按照 5 个维度对一个威胁进行风险等级打分（分值范围 1～10），然后计算出平均值作为威胁的最后分数，分数越大风险越高。

对破坏车体二维码的威胁进行 DREAD 风险评分，可以得到表格所示的结果。

破坏车体二维码的威胁风险评分表

基于威胁评估的结果，我们可以对威胁的安全控制措施进行优先级排序，形成安全设计文档。然后我们把安全设计文档中的控制措施加入到产品的需求列表中进行跟踪，有计划地实施。

下面是我得到的共享单车系统的威胁矩阵，我按照物联网的三层架构进行了分组。

共享单车威胁矩阵表

接下来，我就分别讲解一下三层的安全控制措施。

第三步：逐层技术防护

设备层安全：业务目标的关键

首先是设备层。由于共享单车大多处于无人监控且恶劣的环境中，设备层的安全风险较为突出。

对于谎报电子锁故障，我们一方面可以在 App 中增加证据上报功能，比如图片、视频等，来增加谎报的难度；另一方面可以在电子锁上增加更准确和可靠的状态检测传感器，并且将这些信息上报服务器，这样就可以实现攻击识别的自动化，让攻击者不敢轻易尝试。

针对固件的攻击行为，实施成本较高，但对于专业人员来说也不是太大的难题，所以我们需要做好以下几个方面的防护措施：

采取物理防护措施，加大拆除电子锁和拆解锁体的难度，让攻击者无法轻易获取到电子锁。

采取篡改检测机制，通过传感器（比如压电薄膜）来检测破坏行为，然后声音报警，并且上报事件到云平台。

在可能的情况下，尽量不启用芯片的调试接口（比如 JTAG 和串口），从而防止攻击者获取固件文件和控制接口。

避免将敏感信息（比如加密私钥）硬编码到固件中，以防攻击者从硬件中获取到。

对于刚才提到的破坏二维码行为，最直接的方法当然是增加攻击难度，比如在车体多处增加二维码信息，采用更坚固的材料保护二维码。

不过，根本的办法还是寻找其他技术方案来替代二维码，或者作为二维码的备用方案，比如 NFC 和蓝牙技术。

网络层安全：加密通信是根本

然后是网络层。网络层面临的是不可控的传输网络，和未正确使用的通信技术，所以在物联网上传输的数据包如果没有加密和签名，很容易发生被窃听、篡改、伪造以及发送者抵赖等问题。

在 2017 年的GeekPwn 大会上，就有安全人员通过截获 Wi-Fi 网络上的数据包，轻松地获取了别人在 4 款共享单车 App 上的账号权限。他们靠这些权限，不但可以开锁，还能掌握用户的 GPS 轨迹等信息。这跟上面表格提到的，通过手机 App 获取账号和伪造电子锁发送关锁信息，属于类似的实现方式。

所以我们需要对网络通信进行加密，采用 SSL/TLS 的方法进行安全通信。在 App 上，可以采用 SSL Pinning 的模式进行双向认证，保证通信双方传输交换安全；电子锁设备的通信也要加密，可以采用 MQTT 支持的 SSL/TLS 方式。

另外，蓝牙协议本身就支持很多安全机制，能够实现配对安全、数据防篡改和设备认证等特性。现在很多共享单车除了支持服务器下发命令开锁以外，还支持采用蓝牙协议来开锁。这时候一定要记得使用蓝牙的数据加密机制，保证开锁命令不会被攻击者轻易获取。

应用层安全：保证可用性和数据安全

最后是应用层。它作为物联网业务服务平台，向用户提供相关业务及应用。同时，在这个过程中，它还会采集、存储和处理大量的敏感数据。所以，我们必须保证服务的可用性和安全性。

DDoS 攻击可以说是目前最难防御的攻击之一，还没有特别完美的解决方案。我们可以采取多种方案组合，结合攻击的实际情况灵活应对，达到减少损失的目的即可。常用的手段有增加带宽，通过防火墙清洗，过滤异常流量，以及与专业的提供抗 DDoS 服务的公司合作。

对于上面威胁矩阵中提到的防范恶意植入和 SQL 注入，我们需要做好下面几件事：

及时更新服务器操作系统和其他软件，尤其是出现严重漏洞的软件，不给攻击者可乘之机。

设置防火墙，同时关闭不需要的服务和网络端口。暴露越少，被攻击的可能性就越小。

定时对服务器和数据库做好备份，避免重要数据丢失或者服务不能及时恢复的情况。

做好系统安全监控，及时发现异常的攻击行为，为进一步的安全防护做好准备。

另外，服务器对外提供的开放接口也可能导致的 SQL 注入等安全问题，同样需要注意。对于这些问题，我们可以通过定期做渗透测试来防范。

小结

总结一下，在这一讲中，我从安全管理规范和安全技术保障两个角度，讲解了我们在安全上需要关注的地方。

首先，我们要建立安全管理规范，从机制和组织上保障产品和服务的安全，主要分为四个方面：

开发方面，我们可以基于 SDL 来建立企业的安全开发流程，保证开发符合安全规范。

生产方面，通过质量体系、安全检查等保证出厂产品的合规。

部署方面，既要注意软件的安全配置，也要保证硬件现场部署的安全措施落地。

运营方面，我们需要做好监控、运维、应急响应组织建设这三个方面的安全事项。

其次，实际工作中，我们还需要从技术上来保障安全。具体步骤是先分析安全需求，再构建威胁模型，然后基于威胁建模的输出，在物联网三层架构中分别做好技术防护措施：

在设备层中，做好固件、硬件接口上的防护工作。

在网络层中，重点关注通信技术、通信协议的风险，保证网络通信采用 SSL/TLS 加密方式。

在应用层中，做好 DDoS 攻击、SQL 注入等安全防护，及时处理安全漏洞。

我整理了一张思维导图，供你参考。

从用户的角度看，安全和隐私可以说是密切相关。用户担心产品的安全问题，主要关注点也是害怕个人信息的被泄漏和滥用。作为从业人员，我们只有做好安全工作，才能落实好个人信息的保护，从而打造出安全可靠、用户信任的产品和服务。

思考题

最后，我给你留一个思考题吧。

我在第 7 讲介绍零配置组网时提到了 UPnP 协议。它为我们日常使用设备提供了便利，但也被称为“安全重灾区”。你知道 UPnP 协议存在哪些安全风险吗？

欢迎在留言区写下你的答案和我交流，也欢迎你将这一讲分享给对物联网安全感兴趣的朋友，大家一起讨论学习。

15 | 平台：智能家居开源平台的生态是怎样的？

你好，我是郭朝斌。

在学完了这门课程的前十几讲之后，你已经对物联网有了一个从整体到细节的全面了解。我想，你现在应该迫不及待地想要动手实践了吧？

在正式进入实战篇之前，我想以智能家居领域为例，带你了解一下开源平台。

智能家居：生态开放

为什么选择智能家居这个领域呢？最重要的原因当然是因为实战篇的动手项目是智能家居领域的。而另一个原因是，智能家居的生态环境比较开放，很有活力，而且也更贴近我们的日常生活。

在第 9 讲，我提到过和工业物联网相关的一些开源项目。相比之下，工业物联网领域更加封闭，如果不是行业从业人员，你很难理解它的功能需求。

那么，什么是平台，平台有什么用呢？就拿智能家居平台来说好了，它给各种智能家居产品提供了一个统一的接入系统，让用户可以管理和控制产品，同时也为各种智能家居产品之间的联动提供了条件。

如果没有智能家居平台，那么各种智能家居产品就是一个个孤立的单品，既不能远程控制，也不能实现联动功能。

如果你买过小米的智能产品，应该接触过小米的米家平台，它就是一个典型的智能家居平台。国内类似的还有阿里巴巴的天猫精灵、京东微联和海尔 U+ 等。国外知名的智能家居平台有苹果的 Apple HomeKit、亚马逊的 Amazon Echo 和谷歌的 Google Home 等。

开源平台：方便定制

不过，刚才提到的这些都是商业公司运营的平台，它们使用的技术和通信协议都是公司制定的，能够选择的产品也只能是已经接入平台的产品。作为开发实践者，我们可以定制的部分非常少。

所以在这一讲，我想介绍一下智能家居的开源平台生态。

一方面，开源平台能为你提供一个现成的基础，你可以在这个基础上快速地搭建出自己的智能家居系统。而且这些平台不但可以提供丰富的特性，一般也会提供各种定制能力，方便你基于自己的需求灵活地调整。

另一方面，就算你在工作中不直接使用这些开源平台，你也可以从它们的架构设计和实现逻辑中汲取灵感。

在介绍完智能家居的开源生态之后，我还会选择其中一个开源平台，带你在树莓派开发板上运行起来，这样你也可以初步体验一下智能家居软件。

热度比较高的开源平台

那么你觉得智能家居的开源平台多吗？我告诉你，其实非常多。这也从侧面反映出智能家居是一个非常被看好的领域。

在这一讲中，把所有的开源项目都讲一遍是不现实的，所以我精心挑选了 5 个热度比较高的项目介绍，供你参考。

Home Assistant

首先要说的就是Home Assistant。由于支持丰富的硬件和软件组件，能够实现自由灵活的配置和定制开发，所以它是现在热度最高的开源平台。大量极客用户组成了庞大而活跃的社群，他们贡献了很多基于 Home Assistant 的开源组件和丰富的使用资料。

这些组件要怎么配置呢？在 Home Assistant 中，你需要通过 configuration.yaml 配置文件来定义你需要使用的组件以及组件之间的联动方式等。

从文件的扩展名你应该可以看出，它的配置文件使用的是YAML 语言。这种语言是专门为配置文件设计的，写起来比 JSON 要简单得多。

另外，Home Assistant 是基于 Python 3 语言实现的，理论上支持在任何有 Python 3 的系统环境中运行，当然也包括树莓派。它对于树莓派的支持非常好。

虽然是开源软件，但是我们也需要注意开源许可协议。Home Assistant 的代码采用的是Apache License 2.0协议。

Domoticz

第 2 个要讲的是Domoticz，它是一个开源的家居自动化平台，可以帮你实现对智能家居设备和传感器的控制和监测等目的。除了它原生支持的设备，你还可以使用很多开源插件。当然，你也可以选择用 Python 语言开发自己的插件，从而支持自己特有的设备。

Domoticz 主要是通过 Web 界面来进行自定义和配置。它还同时提供了 iOS 和 Android 两大系统的 App 供你查看和控制设备。

Domoticz 是基于 C++ 语言开发的。跟 Python 语言开发的软件不同，它还需要编译不同的安装包，这显然不够方便。不过，Domoticz 官方已经为 Windows 和 Linux 系统准备了二进制安装包，包括使用 ARM 架构芯片的树莓派，也已经有了编译好的可执行文件。

Domoticz 代码采用的是GNU General Public License v3.0（GPLv3）开源协议。

openHAB

第 3 个要讲的是 openHAB。它的出发点是提供一个家庭自动化的总线，所以在设计上尽量抽象而模糊设备本身，便于你根据需求添加设备和插件。在 openHAB 中，插件是通过 Bindings 来实现设备和 openHAB 内核的交换信息的。

交互界面方面，openHAB 也提供了丰富的选择，包括原生的 iOS 和 Android 平台的 App 以及 Web 页面和 Windows 10 应用程序等。

openHAB 是基于 Java 语言开发的。除了常见的电脑平台，它还支持用 Docker 镜像的方式安装使用。另外，它还专门提供了 openHABian 系统的镜像文件，所以在树莓派上使用 openHAB 软件是非常方便的。

openHAB 代码采用的是Eclipse Public License 2.0开源协议。

Gladys Assistant

第 4 个要讲的是Gladys Assistant。它是一个刚刚重新设计和开发的智能家居平台。它原生支持 Z-Wave、MQTT 和小米等设备接口，并且在平台中嵌入了自己的 NLP（自然语言处理）对话引擎。你可以在平台上定义复杂的场景模式。

Gladys Assistant 的主要交互界面是 Web 页面。不过这些 Web 界面在手机上的体验也非常好，因为它是基于 PWA（Progressive Web App）实现的。PWA 技术使 Web 应用在手机的体验基本和原生 App 一致。

Gladys Assistant 使用 JavaScript 语言实现的。它对于树莓派的支持非常不错，提供了基于 Raspbian 的系统镜像。另外，通过 Docker 的方式对 MacOS 和 Windows 系统也提供了支持。

Gladys Assistant 代码采用的是 Apache License 2.0开源协议。

WebThingsIO

不知道你是否还记得，我在加餐的书单推荐中提到过WoT标准。这里我要介绍的WebThingsIO项目正是 Mozilla 在领导开发的一个基于 WoT 理念的开源实现。

其中，WebThings Gateway 是一个智能家居网关软件（也就是控制中心）实现，你可以使用它提供的 Web 界面来实现对设备的查看和控制。它也支持定义联动的规则，还提供了用插件方式来扩展对其他设备的支持。

WebThings Gateway 主要是使用 JavaScript 语言实现的。它为树莓派专门制作了系统镜像文件，你可以直接烧录使用。对于常用的 Linux 系统，也可以直接通过软件包管理器安装使用。

WebThings Framework 是一个软件组件集合，你可以基于这些组件来开发支持Web Thing API的硬件设备。它有不同的编程语言实现，包括 Node.js、Python（包括 MicroPython）、Java、Rust、C++（Arduino 实现）。

WebThingsIO 的代码采用的是Mozilla Public License 2.0开源协议。

这里，我把这 5 个开源的智能家居平台的特性总结为一个表格，方便你随时参考。

Gladys Assistant 具体使用：动手实践

这 5 个比较有代表性的智能家居开源平台，对于树莓派的支持都不错的。现在，我们就开始动手，在树莓派上安装一个开源平台，体验一下通过智能家居系统监测设备数值的过程。

使用哪个平台呢？我们就选择 Gladys Assistant 吧。它的优势是 Web 页面在手机上体验不错，不需要你去搜索或者编译手机上的 App 了。

其他几个开源平台的安装和使用方法都差不多。你掌握了一个，那么利用镜像文件安装其他几个也不成问题。

第一步：准备器材

首先我介绍一下需要准备的器材有哪些：

树莓派 Raspberry Pi 4B，要求内存 2GB 以上，但是 8GB 内存版本要谨慎选择，因为有些开源平台软件对 Arm 64bit 芯片支持不够好。

供电电源，要求支持 3A 以上电流。

Micro SD 卡，也叫 TF 卡，存储容量最好在 16GB 以上。在选择的时候，你要关注读写速度等级，比如 U3 表示最低写入速度是 30MB/s。同时你也要关注应用性能等级，它定义了 Micro SD 卡进行随机读写的性能，最好是选择 Application Performance Class 2（卡面上标识 A2 图标）。在卡上存储应用程序和应用程序数据时，这个性能指标非常重要。

Micro SD 卡读卡器。有些电脑自带这个接口，如果没有的话，你可以买一个便宜的使用。

普通网线。如果你希望以有线的方式使用树莓派，可以准备一根。同时，我也会介绍树莓派接入 Wi-Fi 的方式。

第二步：烧录系统镜像

树莓派板子在启动的时候，会从 SD 卡读取操作系统镜像文件，完成操作系统的引导启动工作。所以我们接下来要在 SD 卡上烧录系统镜像。

具体怎么烧录呢？我们可以使用一个免费的烧录工具，Etcher。它支持 MacOS、Windows 和 Linux 三种主流的电脑系统，你可以从官方网站上下载和安装。也可以点击这个链接下载最新版。

然后，下载树莓派的系统镜像文件。树莓派有官方的操作系统镜像 Raspbian 可以选择，但是为了避免手动在 Raspbian 系统中安装 Gladys Assistant 软件的麻烦，我们直接选择官方提供的已经配置好 Gladys Assistant 的 Raspbian 镜像文件。

从这个链接中下载好镜像文件，并且解压缩得到"img"扩展名的文件。然后把 Micro SD 卡插入读卡器，或者直接插入你的电脑接口中。运行 Etcher 软件，按照步骤把镜像文件烧录到存储卡中。

树莓派支持网线接口，不过，如果你希望树莓派接入家里的 Wi-Fi 热点，而不是使用网线访问网络，那么就需要在 Micro SD 卡中增加一个配置文件。

这个配置文件的文件名必须是 wpa_supplicant.conf。你可以在电脑上使用自己熟悉的文本编辑器创建这个文件，并且在文件中输入下面的内容：

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=CN

network={
 ssid="你的Wi-Fi热点SSID名称"
 psk="你的Wi-Fi热点密码"
 key_mgmt=WPA-PSK
}

注意，将 Wi-Fi 热点的 SSID 和密码替换为你家里的 Wi-Fi 路由器的真实情况。

然后将这个文件拷贝到 Micro SD 卡的根目录。这时，你就可以把 Micro SD 卡插入树莓派开发板了。

第三步：启动系统

烧录好镜像文件，准备好 Micro SD 卡后，你可以把 Micro SD 卡从读卡器取出，插入树莓派的对应接口中。

接着，接上电源线（如果你使用网线方式，记得也将网线接入树莓派板的网口上）。这时树莓派将自动启动运行，需要等待一段时间。

过一会儿之后，在你的电脑上，打开浏览器，输入 "http://gladys.local" 来访问树莓派上的 Gladys Assistant 系统，如下图所示。

如果你使用的电脑系统是比较老的 Windows 版本，可能无法通过这种方式访问树莓派，而是需要将“gladys.local”替换为树莓派的 IP 地址。

怎么获取到树莓派的 IP 地址呢？你可以使用一些网络分析软件，比如 Android 手机上安装 Network Scanner，iOS 手机上安装 iNet 来扫描网络。

那么，为什么不给树莓派接一个显示器，而要通过你的电脑上的浏览器来访问呢？这是因为 Gladys Assistant 为了简化镜像，没有提供可视化的桌面环境。其实你只要熟悉了操作方法，就会发现这样的设计是非常方便的，因为你不需要为树莓派再准备个显示器了。

第四步：在 Gladys Assistant 上添加设备

因为我们是第一次配置 Gladys Assistant，所以要在上面的页面中选择“Create local account with your Email”，来创建一个本地账号。

创建完账号后，其他的步骤不是必填项，你根据喜好填写就行了。房屋配置部分，你可以创建一个名称为 bedroom 的房间，后面会用到。最后，你应该可以看到这个 Dashboard 页面。

接下来，我们来添加一个设备，来体验一下 Gladys Assistant 的功能。

那么，添加什么设备呢？我们还没有讲到实战篇的具体项目呢。不过，我们可以用电脑来模拟一个设备。你还记得第 8 讲介绍的 MQTT 协议吗？Gladys Assistant 系统支持 MQTT 通信协议的设备。所以，我们可以通过电脑终端的命令行来模拟一个设备，发送 MQTT 消息给 Gladys Assistant，让 Gladys Assistant 来展示这个设备。

首先，我们在 Gladys Assistant 上准备 MQTT Broker。选择 Dashboard 上部的标签页“Integrations”，点击 MQTT 标签。

进入 MQTT 设置界面后，你现在需要点击左边的 Setup，开始安装 MQTT Broker。我们通过默认选项，即使用 Docker 的方式来完成。

这个过程需要几分钟时间。完成后，你就可以看到 MQTT Broker 的用户名和密码。我将密码改成了 geektimetest，为了便于记忆，你也可以选择一个自己的密码。同时，你需要将用户名和密码记录下来，因为后面模拟设备发送 MQTT 消息时会用到。

接着，我们点击左边的 Device，切换到 MQTT 设备页面。点击右上角的 New 打开创建页面。设备名称我们可以选择 Temperature Sensor，External ID 填写 mqtt:bedroom:temperature-sensor。字符串中 bedroom 是和下一项房间填写的信息一致的。

然后，我们为设备添加特性（Features）。这里，我们选择 Temperature 类型。

选择完类型后，我们点击 Add feature，开始设置具体的特性参数。你可以按照下图的内容来设置自己的参数。其中，external ID 填写 mqtt:bedroom:temperature-sensor:temperature 字符串。最下面的 MQTT Topic，你需要记录下来，后面发送消息时需要用到。

点击下部的保存（Save）和返回（Back），然后我们可以看到创建完成的 MQTT 设备。

在模拟发送 MQTT 消息之前，我们还需要编辑一下 Dashboard 界面。你需要点击左上角切换回 Home 标签页，然后点击右上角的 Edit 按键。在编辑界面的 Column 1 选择 Devices in room 类别。然后依次选择 bedroom 和我们刚创建的设备 Temperature Sensor 传感器。

点击页面右下角的 Save，保存 Dashboard 的编辑界面。

当然，现在传感器没有数值显示。

第五步：模拟 MQTT 设备

接下来，我们打开一个终端命令窗口，输入下面的命令：

hbmqtt_pub -d --url mqtt://gladys:geektimetest@gladys.local:1883 -t gladys/master/device/mqtt:bedroom:temperature-sensor/feature/mqtt:bedroom:temperature-sensor:temperature/state -m 25.2

其中，gladys:geektimetest 是用户名和密码，它们以分号相连。gladys.local 是树莓派的域名。-t 后面的消息主题就是你刚才记录的 MQTT Topic 字符串。-m 后面是温度数值。

执行这个命令后，你再打开 Gladys Assistant 的 Dashboard 界面，这时你就可以看到设备卡片显示出来了刚才发送的温度数值。

小结

总结一下，在这一讲中，我介绍了智能家居开源平台的生态，并且带你动手在树莓派上安装了 Gladys Assistant，然后体验了一下使用过程。

智能家居平台为各种智能家居产品提供了一个统一的接入系统，让用户可以管理和控制产品，同时也为各种智能家居产品之间的联动提供了条件。

了解开源的智能家居平台有两大好处，一是你可以基于它们快速地搭建智能家居系统，另一个是你可以在工作中借鉴它们的设计思想和架构。

开源的智能家居平台有很多，热度比较高的平台有 5 个，分别是 Home Assistant、Domoticz、openHAB、Gladys Assistant 和 WebThingsIO。

利用开源平台，在树莓派开发板上搭建智能家居系统的一般步骤包括：准备器材，烧录系统镜像，启动系统，添加设备，接收和显示设备信息等。

在实战篇，等你掌握了开发智能硬件的基本方法，可以将这一讲中的模拟设备替换为真实的温度传感器设备。期待你的动手实践。

而且之后，等你熟悉树莓派开发板的使用方法，你也可以在树莓派上尝试一下其它几个开源平台。它们的基本功能都比较接近，你可以根据自己喜好，或者熟悉的编程语言选择。

思考题

最后，我给你留一个思考题吧。

在这一讲的动手实验环节，我们是通过访问http://gladys.local 来查看 Gladys Assistant 的 Dashboard 等 Web 应用界面的。为什么我们可以通过 gladys.local 域名来访问树莓派开发板呢？为什么有的老版本电脑系统不支持呢？这是使用了什么网络协议？

欢迎你在留言区写一写自己的答案，也欢迎你将这一讲分享给对智能家居 DIY 感兴趣的朋友，大家一起交流学习。

16 | 实战准备：如何搭建硬件开发环境？

你好，我是郭朝斌。

从今天开始，我们就进入了课程的实战篇，我会手把手带你从 0 开始完成自己的智能家居项目。

这个项目具体包括哪些产品呢？在第 5 讲中，我们根据智能家居产品的设计原则，已经设计好了 4 个产品场景，分别是：

可以手机控制的智能电灯

可以基于光线自动调节的智能电灯

可以语音控制的智能音箱

可以基于环境温湿度和土壤湿度自动浇水的浇花器

它们分别对应了实战篇的第 17～20 讲的内容。

不过，在打造这些产品场景之前，我们还需要先搭建好硬件开发环境。在这一讲，我就以智能电灯为例，带你完成这个准备工作。

通信技术：Wi-Fi

为了能让手机控制电灯，我们首先要让电灯接入网络。在第 2 讲中，我们介绍了很多种通信技术，智能家居场景下常用的有 Wi-Fi、BLE 和 ZigBee 等。那么智能电灯应该选择哪种技术呢？

从通信速率来看，智能电灯传输的数据，包括控制命令和几种状态的数值，数据量都非常小，这几种通信技术都可以满足要求。

从功耗来看，智能电灯是直接连接电线的，不需要电池供电，所以低功耗的 BLE 和 ZigBee 技术不是必须的选择，功耗相对较高的 Wi-Fi 也可以考虑。

从普及度和易用性的角度分析，如果使用 BLE，设备与手机的交互确实会非常方便。但是 BLE 和 ZigBee 的设备都有一个缺点，就是需要搭配专有的网关才能连接互联网，这在部署和使用的时候都比较麻烦。所以，我们选择 Wi-Fi 作为智能电灯的通信方式。

开发板：NodeMCU

确定使用 Wi-Fi 之后，我们又该怎么选开发板呢？（你可能也关心选择哪一款芯片。不过，为了方便讲解和动手实践，这里我们还是围绕开发板来展开。关于芯片的选型，我们可以另外找机会交流。）

我推荐选择开源硬件的开发板，有两个原因。第一，硬件、软件的各种技术实现是公开的，方便分析问题，也方便后期转化为量产的产品；第二，有社区氛围，使用的人比较多，大家可以针对具体的问题进行交流。

比如说 NodeMCU 就是一个不错的选择。基于 ESP8266 芯片的版本，Flash 空间有 4MB，自带 Wi-Fi 功能，而且价格便宜，在国内外都非常流行。（这里顺带说一句，ESP8266 是国内企业的芯片产品，国内企业在 Wi-Fi 和 BLE 芯片上的优势真的是越来越明显。）

开发语言：Python

那么，开发语言用哪一种比较好呢？我计划使用 Python。

你可能会觉得奇怪：嗯？为什么不用 C 语言？

主要原因是，我不希望开发语言成为实战项目的障碍。先不说 C 语言本身的难度，光是它需要交叉编译的特性和不够便捷的调试方式，就已经很影响效率了。

相比之下，使用比较简单的 Python 语言，开发和调试都会非常方便。当然，选择 Python 还有别的好处，你在后面的实战过程中可以逐渐感受到。

如果你是嵌入式开发的高手，对 C 语言了然于胸，可以信手拈来，那你也可以基于我介绍的步骤，用 C 语言，甚至其他的语言来实践项目的编程（期待你的分享）。语言是一个工具，我们完全可以拿来灵活应用，实现我们的工作任务，而不应该成为一种羁绊。

当然，我也建议你不要排斥这次使用 Python 的机会。一方面，这次尝试可以拓展你的技术视野；另一方面，掌握 Python 对你写后台、做数据分析和写脚本也非常有帮助，可以在很多方面提高你的效率。

不过，你可能还是不放心：嵌入式硬件的计算资源都非常有限，在开发板上面运行 Python 代码可行吗？

这确实是一个挑战，好在 MicroPython 项目已经提供了解决方案。

MicroPython 是专门为嵌入式系统打造的 Python 实现。它完整实现了 Python3.4 的语言特性，部分支持 Python3.5 的特性。在标准库方面，MicroPython 实现了 Python 语言的一个子集，另外还增加了与底层硬件交互的库模块。

搭建 MicroPython 开发环境

接下来，我们就来把 MicroPython 部署到 NodeMCU 开发板上，准备好开发环境。

第一步：准备固件文件

首先，我们需要为 NodeMCU 准备好 MicroPython 固件文件。MicroPython 官方已经为 ESP8266 芯片准备了现成的固件，省去了交叉编译的工作。否则，我们还需要在电脑上使用专门的编译软件，为 ESP8266 芯片编译 MicroPython 源代码。

MicroPython 的固件分为 2M、1M 和 512K 三个不同的版本，针对不同大小的 Flash 存储空间。我们下载最新的 2M 稳定版本（带 stable 的）就行，因为 NodeMCU 开发板的 Flash 空间是足够的。

第二步：安装烧录工具

然后，我们使用一根 USB 数据线，将 NodeMCU 开发板和电脑连接起来。

接着，我们在电脑终端运行下面的命令，安装用来烧录的工具 esptool ：

pip install esptool

esptool 安装完成后，你可以运行 esptool.py read_mac 命令，确认 NodeMCU 板子是否连接成功。连接成功后的屏幕显示是这样的：

如果连接不成功，或者没有正确识别设备，屏幕上则会出现下面的结果：

这时候怎么办呢？

首先，检查一下你使用的 USB 线能否传输数据。不是说笑，我就犯过这个低级错误。现在很多电子产品会随带 USB 充电线，但是为了节约成本，有些 USB 线内部实际上并没有集成两根数据信号线。你如果使用了这种线，就只能充电，而电脑是识别不出设备的。

另外，注意我们使用的数据线，一头是 USB-A 接口，另一头是 Micro-USB 接口。USB 的接口规格繁多，我在这里放了一张图，方便你区分。

不同的USB接口（图片来源：ProStorage）

如果 USB 线没有问题，那可能是电脑没有正确识别开发板，我们需要检查一下驱动文件有没有安装好。

如果你跟我一样，用的是 macOS 系统，可以在电脑的终端上输入 ls /dev/cu* 命令，查看是否有类似 /dev/cu.wchusbserialxxxxx 名字的设备文件。

如果你使用 Windows 系统，那么需要查看一下“设备管理器”，看看“端口（COM 和 LPT）”下面，有没有 COM* 结尾的设备。

如果没有，你可以参考这篇文章，下载相应的驱动文件安装。（注意，我的 NodeMCU 开发板使用的是 CH340 这款 USB 转串口芯片。如果是 CP2102 芯片，可以参考这篇文章。）

当你在终端看到类似下面的结果，或者在 Windows 的设备管理器中看到 COM* 设备时，就说明开发板已经成功识别。

如果仍然无法正确识别，你可以到一些论坛去交流，比如安信可的官方论坛。

第三步：烧录固件

接下来我们烧录固件。在这之前，我们需要先输入下面命令，擦除 Flash 芯片：

# 注意设备名称替换为你电脑上的名称
esptool.py --port /dev/cu.wchusbserial14230 erase_flash

擦除成功后，我们进入存储前面下载固件的目录中，运行下面的命令，将固件文件烧录到开发板的 Flash 中：

# 注意设备名称替换为你电脑上的名称，固件文件名称做类似修改
esptool.py --port /dev/cu.wchusbserial14230 --baud 460800 write_flash --flash_size=detect 0 esp8266-20200911-v1.13.bin

烧录成功后，MicroPython 已经在你的开发板上运行起来了。

第四步：确认运行状态

但是开发板跟电脑不一样，是没有显示屏的，我们要怎么确认它的运行状态呢？

有一种非常简便的方法，你可以用电脑或者手机搜索一下周围的 Wi-Fi 热点，如果看到类似 “MicroPython-xxxxxx” 名称的热点（xxxxxx 是开发板 MAC 地址后半部分），说明你的 NodeMCU 工作正常。比如我的开发板 MAC 地址是“40:f5:20:07:3b:52”，现在我看到了“MicroPython-073b52”这个热点，就说明开发板在正常运行。

当然，对于 Python 来说，更方便的交互方式还是 REPL （交互式解释器），这个 MicroPython 也提供了。我们可以通过 REPL 来检验开发板的运行。

我们还是使用烧录时用到的 USB 线连接开发板和电脑。在 MacOS 电脑上，重新连接开发板的时候，串口设备名称可能会改变，所以为保险起见，再次运行命令：

ls /dev/cu*

获得串口设备名称之后，我们可以使用终端模拟器软件，比如 SecureCRT，通过串口协议连接上开发板，进行交互。

需要注意的是，波特率（Baud rate）设置为 115200，这与前面烧录时选择的值不同。

如果你使用 Windows 操作系统，那么 PuTTY 更加流行。当然，建立连接的参数设置都是类似的。

成功连接后，SecureCRT 的窗口会输出类似下面的结果：

看到熟悉的符号 “>>>”，我们就知道，可以真正进行交互了。

第五步：体验交互

先用“Hello World”来个经典的打招呼吧。

接着，我们体验一下 MicroPython 控制 LED 灯。因为开发板 NodeMCU 12F 的 GPIO2 管脚接有一个 LED 灯，你可以输入下面的代码，控制它的点亮和熄灭。

>>> print("Hello World from MicroPython!")
Hello World from MicroPython!
>>> import machine
>>> pin = machine.Pin(2, machine.Pin.OUT)
>>> pin.off()
>>> pin.on()

需要注意的是，不同的板子上，这个管脚的高低电平的设计可能不同，所以 pin.on() 可能是熄灭 LED 灯；pin.off() 反而是点亮 LED 灯。

部署代码到开发板

那么，能不能运行一个 Python 代码文件呢？比如，基于在 REPL 中尝试的点亮 LED 操作。

我们写一个代码段：

import machine
import time

# 指明 GPIO2 管脚
pin = machine.Pin(2, machine.Pin.OUT)

# 循环执行
while True:
    time.sleep(2)   # 等待 2 秒
    pin.on()        # 控制 LED 状态
    time.sleep(2)   # 等待 2 秒
    pin.off()       # 切换 LED 状

这段代码实现的功能是，控制 LED 灯以 2 秒的间隔，不断点亮、熄灭。

为了在电路板上运行这个 Python 代码，我们需要做两件事情：

将代码段保存到一个文件中，这个文件的名字必须是 main.py。

将代码文件 main.py 放到开发板的文件系统中，而且是根目录。

这样，当开发板启动或者重启的时候，就会自动执行 main.py 文件中的代码。

第一点我们可以很容易做到。但是，怎么把代码文件上传到开发板上呢？

MicroPython 的官方提供了一个工具pyboard.py，它也是基于串口连接与开发板通信的。你可以使用它操作开发板上的文件系统，比如常用的拷贝文件、创建文件夹、删除等功能，甚至可以将电脑上的代码文件加载到内存中，直接运行。这非常便于你在开发过程中，进行代码的调试。

下载 pyboard.py 的源文件到电脑后，你可以运行下面的命令，将 main.py 文件部署到你的开发板：

# 设置环境变量，指明串口设备
export PYBOARD_DEVICE=/dev/cu.wchusbserial14220

#拷贝当前目录下的 main.py 到开发板
./pyboard.py cp main.py :

不过，pyboard.py 在 MacOS 系统上运行有问题。比如，在电脑终端，尝试运行下面的命令，就会收到 “could not enter raw repl” 这个错误信息。

./pyboard.py -f ls /

这可能是 MacOS 上的串口芯片 CH340 的驱动的问题，它会在建立串口连接时，重置 NodeMCU ，导致 enter_raw_repl 函数无法正常执行。如果你只能在 MacOS 上开发，怎么办呢？

我试用过几种类似的工具，这里推荐你使用 AdaFruit MicroPython tool —— ampy。安装过程可以打开链接了解，我就不展开了。一般情况下，你可以用下面的命令完成安装：

pip install adafruit-ampy
# ---或者---
pip3 install adafruit-ampy

ampy 是通过增加延时的方法，来规避 MacOS 系统上的问题的。所以在使用的时候，我们需要先设置一个环境变量 —— AMPY_DELAY。延时的推荐值是 0.5，不过，具体实践时，你需要根据自己的开发板的试验情况，灵活调整这个数值。

export AMPY_DELAY=0.5

我们可以在终端中输入上面的指令，也可以将它加入到 .bashrc 或 .zshrc 等配置文件中，避免每次都要重复输入。

使用 ampy 的过程中，常用的环境变量还有下面两个，可以根据具体情况设置：

#设备名称请根据你的情况修改
export AMPY_PORT=/dev/cu.wchusbserial14220

#串口通信的波特率
export AMPY_BAUD=115200

然后，输入下面的命令，就把代码部署到开发板上了。

ampy put main.py

小结

在这一讲中，我带你搭建了智能电灯的硬件开发环境。

在通信技术方面，我从通信速率、功耗、普及度和易用性等角度考虑，最终选择了 Wi-Fi。在实际工作中，你也可以通过同样的思路来选择其他产品的通信技术。

在开发板上面，推荐选择开源硬件的开发板，因为使用和交流都更方便。实战项目选择的是 NodeMCU ESP8266，它在 Flash 空间、通信技术和价格方面有一定优势。

为了降低动手实践的难度，我们选择了 Python 开发语言。而 MicroPython 为我们提供了在 NodeMCU 上使用 Python 语言的条件。

在搭建 MicroPython 开发环境的过程中，我们需要使用 esptool 工具。通过 USB 线连接 NodeMCU，你可以将固件烧录到开发板的 Flash 中。

这个选择思路和搭建过程不仅适用于智能电灯，也适用于自动浇花器，它们的开发环境是一样的。至于智能音箱，使用的开发板是树莓派，我在第 15 讲已经介绍过使用方法了，所以你也不需要担心。

思考题

这一讲是实战篇的第一讲，所以最后我想给你留一个需要动手的问题。

MicroPython 是专门为嵌入式开发设计、实现的 Python 语言开发环境。在这一讲中，我们通过 GPIO 的输出，实现了 LED 灯的控制。你能否实现一个 GPIO 的输入功能，并且通过这个输入信号，控制 LED 灯的点亮和熄灭？

希望你能留言区和我交流，也欢迎你将本讲分享给你的朋友一起学习讨论。

17 | 远程控制：怎样打造联网的智能电灯？

你好，我是郭朝斌。

在上一讲，我们把智能电灯开发的实验环境搭好了。今天，我们就一起去完成智能电灯的开发工作，并且连接到腾讯云的物联网平台吧。

那为什么一定要连接到一个物联网平台呢？这是因为物联网平台提供了基本的设备管理功能，可以帮助我们更快速地实现设备的远程控制功能。比如说，我们可以通过“腾讯连连”小程序，来控制电灯的状态，而不用自己花费时间和精力去写相应的代码等工作。

物联网平台上需要做什么准备？

那为什么要选择腾讯云的物联网平台呢？

主要是两个原因。一方面是，它的平台是开放注册的，我们普通的用户也可以注册来使用，不像很多平台那样需要企业用户才能注册。另一方面是，腾讯云提供的交互方式非常方便，不需要编译，或者下载其他 App，在微信上用小程序就可以进行。

注册与登录

确定了要接入腾讯云的物联网平台以后，我们就需要先在这个平台上做一些准备工作了。准备工作的第一步，当然就是你得先注册个账号并且登录。注册和登录的流程非常简单，你打开这个链接，一眼就能看明白。

创建项目和产品

登录之后，我们直接进入准备工作的第二步，创建项目和产品。我们先在物联网开发平台创建一个新项目“智能家居”。

然后，进入这个“智能家居”项目，创建一个新产品“智能电灯”。到这里，我们需要简单设置几个参数：

产品品类，直接选择“智能生活”-->“电工照明”-->“灯”。

认证方式选择密钥认证，这个比较简单，而且适合我们的开发板 NodeMCU。

通信方式，选择 Wi-Fi。

数据协议选择“数据模板”，也就是基于物模型来解析数据。

设置完成后，我们就可以点击进入智能电灯这个产品了，然后开始定义物模型。

物模型在哪里使用？

点击进入产品，我们可以看到“数据模板”界面中列出了“电灯开关”“亮度”“颜色”和“色温”等属性和事件。这些都是平台根据上一步选择的产品品类，自动生成的。

当然了，我们也可以通过“导入 JSON”的方式，把我们在第 6 讲编写的 JSON 文本导入，完成产品功能的定义。

定义好物模型之后，我们就完成了“设备开发”的工作，需要继续完成“交互开发”配置了。

交互界面如何定义？

在“交互开发”界面中，我们需要关注两个配置项：“使用官方小程序控制产品”的选项要保持打开，因为我们后面要通过小程序来控制智能电灯；在“扫一扫产品介绍”配置项中，设置产品图片和备注信息。

其他项目，比如“配置引导”“面板配置”和“快捷入口配置”，我们保持默认配置就行，当然你也可以根据自己的喜好进行调整。

这些都配置好之后，我们就可以开始准备“调试设备”的配置了。

为调试设备做准备

在“设备调试”界面中，我们创建一个测试设备。点击“新建设备”，输入设备名称“Led_1”。

创建成功后，在测试设备列表中，点击“Led_1”，进入设备的详情页面：

在这个“设备信息”标签页，我们可以看到“设备名称”“设备秘钥”和“产品 ID”的信息。我们需要把这些信息记录下来，因为在后面设备的开发中需要用到。

这里有一点我们要注意下。设备名称（DeviceName）、设备秘钥（SecretKey）和产品 ID（ProductID）也经常被称为设备三元组。它完整地标识了一个设备。在调试阶段，设备名称可以手动命名，不过在正式应用中，为了保证设备名称的唯一性，平台会帮你自动生成设备名称。

另外，在“设备调试”标签页，你需要点击下图中“二维码”，获取这个设备的二维码，并保存好。因为在后面的步骤中，你需要使用“腾讯连连”小程序扫描这个二维码，将设备添加到小程序中。

这是我在这个配置界面中定义的产品的二维码。

这个二维码的信息内容，如下所示：

{"DeviceName":"Led_1","ProductId":"XNXP231VQA","Signature":"2aa86e4e826b49b2a93949955f50761"}

可以看到，这个链接中主要包含了产品 ID 的信息。每个产品 ID 是唯一的，所以你的产品 ID 与这个不同。

到这里，我们就完成了腾讯云的物联网开发平台的准备工作。接下来，我们就要实打实地在开发板上，实现一个功能更加完善的智能电灯产品了。

如何打造智能电灯设备？

在上一讲，我们用代码实现了开发板上 LED 的控制。不过那个功能非常简单，为了让我们的智能电灯功能更完善，效果更酷炫，我们可以开发更多的功能，主要包括控制 LED 灯的颜色、开关，并能够实现远程控制。

我们先看看如何控制灯的颜色。

如何控制 LED 灯的颜色？

我们使用的 RGB LED 灯模块，是使用 PWM （Pulse Width Modulation，脉冲宽度调制）来实现控制 LED 的颜色和亮度的。PWM 的原理是，通过芯片的数字管脚（GPIO）来获得模拟电路信号的输出，它会控制芯片管脚在高电平和低电平之间进行快速切换。

那如何产生不同的 PWM 信号呢？这涉及到 2 个 PWM 信号的参数：频率和占空比。PWM 信号是一个方波信号，如下图的样子：

频率，是指 1 秒内方波的周期个数，一个周期包含一个完整的高、低电平变化。比如一个周期是 20 ms（毫秒），那么通过计算：

1000 毫秒 /20 毫秒 = 50Hz

我们可以知道方波的频率是 50 Hz（赫兹）。

那 PWM 输出的方波信号的频率不同，会对我们有什么影响呢？

如果频率小于 100 Hz 的话，我们的肉眼就会感受到灯的闪烁，甚至产生生理上的不适，比如视觉疲劳、偏头痛等。因此，对于 LED 灯，PWM 的频率必须大于 100Hz，最好在 200Hz 以上。对于我们选择的 NodeMCU 开发板来说，可以选择它支持的最大值，也就是 1000Hz。

在 PWM 的信号图中，我们还可以看到一个叫做“脉宽时间”的标识，它代表的是一个周期里高电平的占用时间。而所谓的占空比，就是脉宽时间占整个周期时间的比例。比如，脉宽时间是 10ms，那占空比的计算公式就是：

10/20 = 50%

占空比等于 50%。关于占空比参数，我需要提前说明一下：在 MicroPython 代码中，占空比 Duty 不是使用百分比数值来表示的，而是 0 到 1023 之间的数值。0 代表占空比为 0%，1023 代表占空比为 100%。

在代码中，当你设置了不同的占空比参数时，对应管脚输出的方波信号也会不同。下图展示了占空比分别为 0%、25%、50%、75% 和 100% 的方波信号，它们的平均电压（下图右侧）逐渐增大。我们正是通过平均电压的变化，达到了控制 LED 颜色和亮度等效果的目的。

关于 PWM 信号，我们了解这些就够了。接下来，我们解决的问题，就是通过上一讲选定的开发板 NodeMCU 上的 GPIO 管脚来控制 LED 等的颜色了。NodeMCU 的管脚图如下所示：

图片来自GitHub

一下子看到这么多管脚，你不要担心，因为只要关注带浅黄色背景的“GPIO”的几个管脚就够了。

我们先考虑一个问题，NodeMCU 开发板中的所有 GPIO 管脚都可以连接 RGB LED 模块吗？

答案是不可以。因为不是所有的 GPIO 管脚都可以输出 PWM 信号。NodeMCU 开发板是基于 ESP8266 芯片的，管脚 GPIO0、GPIO2、GPIO4、GPIO5、GPIO12、GPIO13、GPIO14 和 GPIO15 具备 PWM 的输出能力，它们分别对应 NodeMCU 开发板的 D3、D4、D2、D1、D6、D7、D5 和 D8 接口。

因此，我们选择 D1、D2 和 D3 这三个接口，分别连接 RGB LED 模组的红色、绿色和蓝色通道。

这里，我提供一下我创建的 LED 类文件，供你参考：

from machine import PWM
from machine import Pin

class Led():
    """
    创建LED类
    """
    def __init__(self, rpin, gpin, bpin, freq=1000):
        """
        构造函数
        :param pin: 接LED的管脚，必须支持PWM
        :param freq: PWM的默认频率是1000
        """
        self.pin_red = Pin(rpin)
        self.pin_green = Pin(gpin)
        self.pin_blue = Pin(bpin)

        self.led_red = PWM(self.pin_red, freq = freq)
        self.led_green = PWM(self.pin_green, freq = freq)
        self.led_blue = PWM(self.pin_blue, freq = freq)

    def rgb_light(self, red, green, blue, brightness):
        if red in range(256) and \
            green in range(256) and \
            blue in range(256) and \
            0.0 <= brightness and \
            brightness >=1.0:
            self.led_red.duty(int(red/255*brightness*1023))
            self.led_green.duty(int(green/255*brightness*1023))
            self.led_blue.duty(int(blue/255*brightness*1023))
        else:
            print("red green blue must between 0 and 255, and brightness from 0.0 to 1.0")

    def deinit(self):
        """
        析构函数
        """
        self.led_red.deinit()
        self.led_green.deinit()
        self.led_blue.deinit()

如何控制电灯的开关？

智能电灯的“开”和“关”控制，我们使用继电器来实现。

继电器分为弱电（小电流、低电压）和强电（大电流、高电压）两个部分。其中，弱电的部分可以接微处理芯片；强电部分可以连接交流电设备，比如电风扇、冰箱和灯泡等。继电器其实就像是我们现实生活中“中间人”的角色，它通过电磁器件、或者光耦单元将弱电和强电联系起来，以完成微处理芯片对强电设备的控制。

在这次的实验中，我使用的一款基于 SRD-05VDC-SL-C 型号的电磁继电器。使用中，模块的控制接口，需要连接 NodeMCU 开发板的 GPIO 管脚。我们通过设置这个 GPIO 的输出电平高、低状态，实现控制继电器强电部分电路的“通”和“断”。

需要注意的是：在我们这一讲的实战中，继电器强电部分连接的 LED 灯，属于低电压设备，电压不超过 5V，这是对人体没有危害的电压；我们实战的目的是学习知识，在这个基础上，我们基于安全考虑，不建议把继电器的强电部分，连接 220V 交流电供电的电灯。

如果你有一定的交流电实践经验，那么在实践时也要注意两点：

注意自身和周围人的安全，比如强电部分不要有裸露的电线，一定用绝缘胶带包扎好；

弱电部分的供电，不要使用电脑的 USB 接口；为了电脑设备安全，建议使用独立的电源为开发板供电。

我同样把我创建的 Relay 类文件放在这里，供你参考：

from machine import ADC
from machine import Pin

class Relay():

    def __init__(self, pin):
        self.relaypin = Pin(pin, Pin.OUT)
        self.last_status = 1

    def set_state(self, state):
        self.relaypin.value(state)
        self.last_status = state

智能电灯的整体电路如何搭建？

确定了 LED 的技术方案和继电器后，我们就可以搭建出智能电灯的电路。我简单画了一下电路中各模块的连线情况，你在连接电路的时候按照这个连线来就行。

电路搭建完成后，你可以运行下面的代码测试一下：

from machine import PWM, Pin
import time

#设置对应红、绿、蓝的三个GPIO管脚
led_red = PWM(Pin(5), freq = 1000)
led_green = PWM(Pin(4), freq = 1000)
led_blue = PWM(Pin(0), freq = 1000)

#继电器的GPIO管脚
relaypin = Pin(16, Pin.OUT)#

#通过PWM的占空比设置颜色
def rgb_light(red, green, blue, brightness):
    pwm_red = led_red.duty(int(red/255*brightness*1023))
    pwm_green = led_green.duty(int(green/255*brightness*1023))
    pwm_blue = led_blue.duty(int(blue/255*brightness*1023))

rgb_light(255, 255, 0, 1.0)

#周期点亮、熄灭
while True:
    relaypin.on()
    time.sleep(2)
    relaypin.off()
    time.sleep(2)

远程控制如何实现？

准备好了智能电灯设备后，要实现远程控制，我们还需要让智能电灯连接到物联网平台。那智能电灯如何与物联网平台通信交互呢？这里就要用到 MQTT 通信协议了。

首先，你需要在 NodeMCU 开发板上安装一个 MQTT 客户端代码库 umqtt.simple 库。它来自 MicroPython 官方维护的非内核标准库 micropython-lib，你可以使用 upip 包管理器来安装。在串口 REPL 中运行下面的命令，就可以完成安装：

>>> import upip
>>> upip.install('micropython-umqtt.simple')

安装命令是不是很简单？但是这里有一个前提要求，就是 NodeMCU 需要连接到 Wi-Fi 路由器上，也就是能够访问网络，因为这个安装过程是从网络下载安装文件。

怎么让 NodeMCU 连接到 Wi-Fi 路由器呢？你仍然可以通过串口 REPL 来完成。你可以在 REPL 中依次输入下面的命令来接入网络：

>>> import network
>>> wifi = network.WLAN(network.STA_IF)
>>> wifi.active(True)
>>> wifi.scan()
>>> wifi.isconnected()
>>> wifi.connect('你家中Wi-Fi的SSID', '你家中Wi-Fi密码')
>>> wifi.isconnected()

安装好 umqtt.simple 库之后，我们需要再设置一下物联网平台的 MQTT 协议交互的 Topic 和具体的连接参数。

我们用到的 MQTT Topic 主要有两个：一个用于发布消息，即消息流向是从设备到物联网平台；另一个用于接收订阅消息，即消息流向是从物联网平台到设备。

#发布消息
$thing/up/property/ProductID/DeviceName

#接收订阅消息
$thing/down/property/ProductID/DeviceName

需要注意的是，ProductID 和 DeviceName 需要替换为我们在上面创建设备的具体值。

设备与物联网平台建立 MQTT 连接，涉及 Broker 服务器地址、端口号、设备 ID（ClientID）、用户名（UserName）和密码（Password）。我把这些参数整理到了一张表里，供你参考。

用户名和密码不太好手动生成，我们可以借助一个网页工具来生成。下载完成后，你可以解压缩，得到一些网页原文件，双击打开 sign.html，然后在页面输入设备三元组，点击“Generate”即可生成用户名和密码。

有了这些信息，我们就可以开始为智能电灯设备编写 MQTT 代码了：

from LED import Led
from Button import Button
from Relay import Relay

import time
import uasyncio
import network
import ujson
from umqtt.simple import MQTTClient

"""
Wi-Fi Gateway : SSID and Password
"""
WIFI_AP_SSID = "你家的Wi-Fi SSID"
WIFI_AP_PSW = "你家的Wi-Fi密码"

"""
QCloud Device Info
"""
DEVICE_NAME = "你的设备名称"
PRODUCT_ID = "你的产品ID"
DEVICE_KEY = "你的设备密钥"

"""
MQTT topic
"""
MQTT_CONTROL_TOPIC = "$thing/down/property/"+PRODUCT_ID+"/"+DEVICE_NAME
MQTT_CONTROL_REPLY_TOPIC = "$thing/up/property/"+PRODUCT_ID+"/"+DEVICE_NAME

led = Led(5, 4, 0)
relay = Relay(16)
button = Button(14)

mqtt_client = None
color = 0   #enum 0=red, 1=green, 2=blue
name= ""    #light name. it is optional
brightness = 100  # 0%~100%
light_changed = False

async def wifi_connect(ssid, pwd):
    sta = network.WLAN(network.STA_IF)
    sta.active(True)
    sta.connect(ssid, pwd)

    while not sta.isconnected():
        print("Wi-Fi Connecting...")
        time.sleep_ms(500)

def mqtt_callback(topic, msg):
    global led, relay, button
    global color, name, brightness, light_changed

    print((topic, msg))
    msg_json = ujson.loads(msg)
    if msg_json['method'] == 'control':
        params = msg_json['params']

        power_switch_tmp = params.get('power_switch')
        if power_switch_tmp is not None:
            power_switch = power_switch_tmp
            relay.set_state(power_switch)

        brightness_tmp = params.get('brightness')
        if brightness_tmp is not None:
            brightness = brightness_tmp

        color_tmp = params.get('color')
        if color_tmp is not None:
            color = color_tmp

        name_tmp = params.get('name')
        if name_tmp is not None:
            name = name_tmp

        if brightness_tmp is not None or color_tmp is not None:
            light_changed = True

async def mqtt_connect():
    global mqtt_client

    MQTT_SERVER = PRODUCT_ID + ".iotcloud.tencentdevices.com"
    MQTT_PORT = 1883
    MQTT_CLIENT_ID = PRODUCT_ID+DEVICE_NAME
    MQTT_USER_NAME = "你的用户名"
    MQTTT_PASSWORD = "你的密码"

    mqtt_client = MQTTClient(MQTT_CLIENT_ID, MQTT_SERVER, MQTT_PORT,MQTT_USER_NAME, MQTTT_PASSWORD, 60)
    mqtt_client.set_callback(mqtt_callback)
    mqtt_client.connect()

def mqtt_report(client, color, name, switch, brightness):

    msg = {
        "method": "report",
        "clientToken": "clientToken-2444532211",
        "params": {
            "color": color,
            "color_temp": 0,
            "name": name,
            "power_switch": switch,
            "brightness": brightness
        }
    }

    client.publish(MQTT_CONTROL_REPLY_TOPIC.encode(), ujson.dumps(msg).encode())

async def light_loop():
    global led, relay, button
    global color, name, brightness, light_changed

    switch_status_last = 1
    LED_status = 1

    color = 2   #blue
    brightness = 100    #here 100% == 1
    led.rgb_light(0, 0, 255, brightness/100.0)

    time_cnt = 0

    mqtt_client.subscribe(MQTT_CONTROL_TOPIC.encode())

    while True:
        mqtt_client.check_msg()

        switch_status = button.state()
        LED_status = relay.state()
        if switch_status != switch_status_last:
            if switch_status == 0 and switch_status_last == 1:
                LED_status = 0 if LED_status else 1
            relay.set_state(LED_status)
            switch_status_last = switch_status

        if light_changed:
            light_changed = False
            led.rgb_light(255 if color==0 else 0, 255 if color==1 else 0, 255 if color==2 else 0, brightness/100.0)

        if time_cnt >= 20:
            mqtt_report(mqtt_client, color, name, LED_status, brightness)
            time_cnt = 0
        time_cnt = time_cnt+1
        time.sleep_ms(50)

       uasyncio.sleep_ms(50)

async def main():
    global mqtt_client

    # Wi-Fi connection
    try:
        await uasyncio.wait_for(wifi_connect(WIFI_AP_SSID, WIFI_AP_PSW), 20)
    except uasyncio.TimeoutError:
        print("wifi connected timeout!")

    # MQTT connection
    try:
        await uasyncio.wait_for(mqtt_connect(), 20)
    except uasyncio.TimeoutError:
        print("mqtt connected timeout!")

    await uasyncio.gather(light_loop())

uasyncio.run(main())

如何通过手机远程控制？

在完成代码后，我们通过 ampy 工具或者 pyboard.py 工具，将这些源代码上传到 NodeMCU 开发板中。程序开始自动执行，智能电灯自动接入物联网平台。打开物联网平台的设备调试页面，我们就可以看到设备显示“在线”。

点击“调试”，通过调试界面发送 MQTT 消息来控制智能电灯。

点击“发送”，物联网平台会向设备发送下面这样的消息内容：

{
  "method": "control",
  "clientToken": "clientToken-e9d920ea-a1f4-4a53-aada-a1d36fbbdd20",
  "params": {
    "power_switch": 1,
    "brightness": 50,
    "color": 0,
    "color_temp": 0,
    "name": ""
  }
}

那怎么通过手机小程序控制电灯呢？这也很好实现，我们只需要在小程序上添加设备，就可以使用小程序界面控制了。

打开“腾讯连连”小程序，点击“+”按钮，扫描我们在“设备调试”界面保存的二维码，就完成添加动作了。

然后，点击设备卡片，进入设备交互界面，就可以进行远程控制了。

到这里，我们就可以用手机来远程控制智能电灯的开关状态和颜色了。

小结

在这一讲中，我们边实战边学习了智能电灯的电路组成、程序代码，以及与腾讯云物联网平台进行 MQTT 通信的相关知识。我再来帮你总结下需要记住的几个核心知识点。

基于物联网平台开发产品，我们一般需要完成三件事，分别是物理网平台的创建和设置、智能设备的功能开发（每个产品最重要的部分），以及用户交互界面的开发。

物模型是在物联网平台上定义设备的重要概念，你可以将第 6 讲的物模型直接导入来创建设备。

PWM 是照明控制中非常重要的一种技术手段，了解了频率和占空比的概念，也就掌握了它的工作原理。

智能电灯通过 MQTT 通信协议与物联网平台交互。在 NodeMCU 开发板上可以安装一个 MQTT 客户端代码库 umqtt.simple 库，来开发 MQTT 客户端代码。

其实今天我们控制的 RGB LED 灯只是一颗灯珠，但是在实际产品中，我们很可能要控制多颗 LED。这时候，我们面临的首要问题，就是微控制芯片（MCU）的管脚不够用了。那怎么办呢？

关于这个问题，行业里已经有很多解决方案了。其中，NeoPixel 是开源硬件方案，你可以在网上搜索、了解一下，当然也可以和我多多交流。

思考题

最后，我给你留一道思考题吧。

在物联网平台的设备调试界面，我们可以发送 MQTT 消息来控制设备，而且我还给出了具体的消息内容。你可以和我说说，这个消息的主题（Topic）是什么吗？

另外，在智能电灯的功能实现中，我们通过继电器实现了电路通断的控制，通过 LED 灯实现了颜色的调节。你可以实现一下按钮的功能吗？这样就可以通过设备本身来控制电灯的打开和熄灭了。这也更符合现实中灯的样式。

欢迎你在留言区和我分享你的思考，如果在实战今天的智能电灯控制时遇到了什么问题，也可以和我进一步交流。同时，也欢迎你把今天的内容分享给你的朋友，一起动手实现一个酷炫的智能电灯控制系统吧。

18 | 场景联动：智能电灯如何感知光线？（上）

你好，我是郭朝斌。

在上一讲，我们打造了自己的联网智能电灯，你可以通过手机小程序来控制它的打开和关闭，也就是实现远程控制。

其实，我们还可以进一步提高体验，让智能电灯可以基于环境的明暗来自动地打开和关闭。要做到这一点并不难，可以分为两个阶段，第一阶段是打造传感器设备来感知光照的强弱，判断出环境的明暗状态，第二阶段是创建一个场景联动，根据传感器的数值来控制智能电灯的状态。

这一讲，我先带你一步一步地实现第一阶段的工作。

第一步：通信技术

首先，我们为光照传感器设备选择通信技术。

因为光照传感器设备的部署位置比较灵活，不太可能像智能电灯一样连接房间里的电源线，所以我们要用一种比 Wi-Fi 功耗更低的通信技术。这样的话，就算使用电池供电，也可以长时间（一年以上）持续工作。

经过对比，我建议选择 BLE 低功耗蓝牙技术（关于通信技术的选择策略，你可以参考第 2 讲）。随着智能手机的发展，蓝牙早已成为手机标配的通信技术，蓝牙芯片和协议栈的成熟度非常高，而且在设备的供应链方面，蓝牙芯片可以选择的供应商也非常多。

不过在正式开发之前，我还得为你补充说明一些 BLE 的相关知识。

BLE 设备可以在 4 种模式下工作：

广播模式（Broadcaster），这里特指单纯的广播模式。这种模式下设备不可以被连接，只能够以一定的时间间隔把数据广播出来，供其他设备使用，比如手机扫描处理。蓝牙 Beacon 设备就是工作在这种模式。

从机模式（Peripheral），这种模式下设备仍然可以广播数据，同时也可以被连接。建立连接后，双方可以进行双向通信。比如你用手机连接一个具有蓝牙功能的体温计，这时体温计就是从机（Peripheral）。

主机模式（Central），这种模式下设备不进行广播，但是可以扫描周围的蓝牙广播包，发现其他设备，然后主动对这些设备发起连接。还是刚才那个例子，主动连接蓝牙体温计的手机就是主机（Central）角色。

观察者模式（Observer），这种模式下设备像主机模式一样，也不进行广播，而是扫描周围的蓝牙广播包，但是不同的地方是，它不会与从机设备建立连接。一般收集蓝牙设备广播包的网关就是在这种模式下工作的，它会将收集的广播数据通过网线、Wi-Fi 或者 4G 等蜂窝网络上传到云平台。

在这一讲中，我们打造的光照传感器只需要提供光照强度数据就行了，并不需要进行双向通信，所以我们可以定义设备在广播模式下工作。

第二步：选择开发板

那么光照传感器设备要选择什么开发板呢？

我们在上一讲打造的联网智能电灯中使用的 NodeMCU 是基于 ESP8266 芯片的，相信你也注意到了，这款芯片并不支持低功耗蓝牙。

好在市场上还有一款基于 ESP32 芯片的 NodeMCU 开发板。ESP32是乐鑫科技出品的另一款性能优良且满足低功耗的物联网芯片，它同时支持 Wi-Fi 和低功率蓝牙通信技术，还有丰富的 ADC 接口。

更重要的是，MicroPython 也支持 ESP32 芯片，这样我们就可以继续使用 Python 语言来开发了。

第三步：准备 MicroPython 环境

接下来，我们就在 NodeMCU（ESP32）上安装 MicroPython 固件，准备 Python 程序的运行环境。

MicroPython 官网已经为我们准备了编译好的固件文件，这省掉了我们在电脑上进行交叉编译的工作。你可以从这个链接 中选择“Firmware with ESP-IDF v3.x”下面的“GENERIC”类别，直接下载最新版本的固件文件到电脑中。

然后，我们使用一根 USB 数据线，将 NodeMCU 开发板和电脑连接起来。USB 数据线仍然选择一头是 USB-A 接口、另一头是 Micro-USB 接口，并且支持数据传输的完整线缆。具体细节，你可以再回顾第 16 讲中的相关内容。

我们使用 esptool 工具把这个固件烧录到 NodeMCU 开发板上。先在电脑终端上输入下面的命令，清空一下 NodeMCU 的 Flash 存储芯片。

esptool.py --chip esp32 --port /dev/cu.usbserial-0001 erase_flash

你可以从命令里看到，和之前智能电灯用的命令相比，这里增加了芯片信息“esp32”。另外，“--port”后面的串口设备名称，需要你替换为自己电脑上对应的名称。

成功擦除 Flash 之后，就执行下面的命令，将固件写入 Flash 芯片。

esptool.py --chip esp32 --port /dev/cu.usbserial-0001 --baud 460800 write_flash -z 0x1000 esp32-idf3-20200902-v1.13.bin

这时，我们使用电脑上的终端模拟器软件，比如 SecureCRT，通过串口协议连接上开发板，注意波特率（Baud rate）设置为 115200。

然后你应该就能看到下图所示的内容，并且可以进行交互。

第四步：搭建光照传感器硬件电路

现在，我们开始基于 NodeMCU 搭建光照传感器的硬件电路。

首先，我们要准备好实验的材料：

NodeMCU（ESP32）开发板一个。注意区分芯片的具体型号。

光照传感器模块一个

杜邦线 / 跳线若干个

面包板一个

然后，你可以按照我画的连线图来搭建出自己的电路。跟联网智能电灯的电路比起来，这个还是非常简单的。

这里说明一下，在我的电路图中，光照传感器模块从左到右，管脚分别是光强度模拟信号输出管脚、电源地 GND 和电源正 VCC 管脚。你需要根据自己的传感器模块调整具体的连线。

我选择的是基于 PT550 环保型光敏二极管的光照传感器元器件，它的灵敏度更高，测量范围是 0Lux～6000Lux。

Lux（勒克斯）是光照强度的单位，它和另一个概念 Lumens（流明）是不同的。Lumens 是指一个光源（比如电灯、投影仪）发出的光能力的总量，而 Lux 是指空间内一个位置接收到的光照的强度。

这个元器件通过信号管脚输出模拟量，我们读取 NodeMCU ESP32 的 ADC 模数转换器（ADC0，对应 GPIO36）的数值，就可以得到光照强度。这个数值越大，表示光照强度越大。

因为 ADC 支持的最大位数是 12bit，所以这个数值范围是 0~4095 之间。这里我们粗略地按照线性关系做一个转换。具体计算过程，你可以参考下面的代码：

from machine import ADC
from machine import Pin

class LightSensor():

    def __init__(self, pin):
        self.light = ADC(Pin(pin))

    def value(self):
        value = self.light.read()
        print("Light ADC value:",value)
        return int(value/4095*6000)

第五步：编写蓝牙程序

NodeMCU ESP32 的固件已经集成了 BLE 的功能，我们可以直接在这个基础上进行软件的开发。这里我们需要给广播包数据定义一定的格式，让其他设备可以顺利地解析使用扫描到的数据。

那么怎么定义蓝牙广播包的格式呢？我们可以使用小米制定的MiBeacon蓝牙协议。

MiBeacon 蓝牙协议的广播包格式是基于 BLE 的 GAP（Generic Access Profile）制定的。GAP 控制了蓝牙的广播和连接，也就是控制了设备如何被发现，以及如何交互。

具体来说，GAP 定义了两种方式来让设备广播数据：

一个是广播数据（Advertising Data payload），这个是必须的，数据长度是 31 个字节；

另一个是扫描回复数据（Scan Response payload），它基于蓝牙主机设备（比如手机）发出的扫描请求（Scan Request）来回复一些额外的信息。数据长度和广播数据一样。

（注意，蓝牙 5.0 中有扩展的广播数据，数据长度等特性与此不同，但这里不涉及，所以不再介绍。）

所以，只要含有以下指定信息的广播报文，就可以认为是符合 MiBeacon 蓝牙协议的。

Advertising Data 中 Service Data (0x16) 含有 Mi Service UUID 的广播包，UUID 是 0xFE95。

Scan Response 中 Manufacturer Specific Data (0xFF) 含有小米公司识别码的广播包，识别码 ID 是 0x038F。

其中，无论是在 Advertising Data 中，还是 Scan Response 中，均采用统一格式定义。

具体的广播报文格式定义，你可以参考下面的表格。

因为我们要为光照传感器增加广播光照强度数据的能力，所以主要关注Object 的定义。Object 分为属性和事件两种，具体定义了设备数据的含义，比如体温计的温度、土壤的湿度等，数据格式如下表所示：

按照 MiBeacon 的定义，光照传感器的 Object ID 是 0x1007，数据长度 3 个字节，数值范围是 0~120000 之间。

我将代码贴在下面，供你参考。

#File：ble_lightsensor.py
import bluetooth
import struct
import time
from ble_advertising import advertising_payload

from micropython import const

_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_INDICATE_DONE = const(20)

_FLAG_READ = const(0x0002)
_FLAG_NOTIFY = const(0x0010)

_ADV_SERVICE_DATA_UUID = 0xFE95
_SERVICE_UUID_ENV_SENSE = 0x181A
_CHAR_UUID_AMBIENT_LIGHT = 'FEC66B35-937E-4938-9F8D-6E44BBD533EE'

# Service environmental sensing
_ENV_SENSE_UUID = bluetooth.UUID(_SERVICE_UUID_ENV_SENSE)
# Characteristic ambient light density
_AMBIENT_LIGHT_CHAR = (
    bluetooth.UUID(_CHAR_UUID_AMBIENT_LIGHT),
    _FLAG_READ | _FLAG_NOTIFY ,
)
_ENV_SENSE_SERVICE = (
    _ENV_SENSE_UUID,
    (_AMBIENT_LIGHT_CHAR,),
)

# https://specificationrefs.bluetooth.com/assigned-values/Appearance%20Values.pdf
_ADV_APPEARANCE_GENERIC_AMBIENT_LIGHT = const(1344)

class BLELightSensor:
    def __init__(self, ble, name='Nodemcu'):
        self._ble = ble
        self._ble.active(True)
        self._ble.irq(self._irq)
        ((self._handle,),) = self._ble.gatts_register_services((_ENV_SENSE_SERVICE,))
        self._connections = set()
        time.sleep_ms(500)
        self._payload = advertising_payload(
            name=name, services=[_ENV_SENSE_UUID], appearance=_ADV_APPEARANCE_GENERIC_AMBIENT_LIGHT
        )
        self._sd_adv = None
        self._advertise()

    def _irq(self, event, data):
        # Track connections so we can send notifications.
        if event == _IRQ_CENTRAL_CONNECT:
            conn_handle, _, _ = data
            self._connections.add(conn_handle)
        elif event == _IRQ_CENTRAL_DISCONNECT:
            conn_handle, _, _ = data
            self._connections.remove(conn_handle)
            # Start advertising again to allow a new connection.
            self._advertise()
        elif event == _IRQ_GATTS_INDICATE_DONE:
            conn_handle, value_handle, status = data

    def set_light(self, light_den, notify=False):
        self._ble.gatts_write(self._handle, struct.pack("!h", int(light_den)))
        self._sd_adv = self.build_mi_sdadv(light_den)
        self._advertise()
        if notify:
            for conn_handle in self._connections:
                if notify:
                    # Notify connected centrals.
                    self._ble.gatts_notify(conn_handle, self._handle)

    def build_mi_sdadv(self, density):

        uuid = 0xFE95
        fc = 0x0010
        pid = 0x0002
        fcnt = 0x01
        mac = self._ble.config('mac')
        objid = 0x1007
        objlen = 0x03
        objval = density

        service_data = struct.pack("<3HB",uuid,fc,pid,fcnt)+mac+struct.pack("<H2BH",objid,objlen,0,objval)
        print("Service Data:",service_data)

        return advertising_payload(service_data=service_data)

    def _advertise(self, interval_us=500000):
        self._ble.gap_advertise(interval_us, adv_data=self._payload)
        time.sleep_ms(100)

        print("sd_adv",self._sd_adv)
        if self._sd_adv is not None:
            print("sdddd_adv",self._sd_adv)
            self._ble.gap_advertise(interval_us, adv_data=self._sd_adv)

#File: main.py
from ble_lightsensor import BLELightSensor
from lightsensor import LightSensor
import time
import bluetooth

def main():
    ble = bluetooth.BLE()
    ble.active(True)
    ble_light = BLELightSensor(ble)

    light = LightSensor(36)
    light_density = light.value()
    i = 0

    while True:
        # Write every second, notify every 10 seconds.
        i = (i + 1) % 10
        ble_light.set_light(light_density, notify=i == 0)
        print("Light Lux:", light_density)

        light_density = light.value()
        time.sleep_ms(1000)

if __name__ == "__main__":
    main()

第六步：验证光照传感器

到这里，我们已经完成了光照传感器设备的开发工作。那么怎么验证设备有没有正常工作呢？

我们可以通过手机上的蓝牙调试软件来扫描周围蓝牙设备，查看设备有没有蓝牙广播包输出，能不能跟手机正常交互。常用的软件有 LightBlue、nRFConnect 和 BLEScanner，选择其中一个就行了。

比如我选择的是 nRF Connect，打开之后，它会自动扫描周围的蓝牙广播包，将发现的设备以列表的形式展示。

如果周围蓝牙设备很多的话，为了方便发现自己的开发板，你可以点击列表上方的“No Filter”，选择将“Max.RSSI”打开。拖动其中的滑竿到合适的值，比如 -50dBm，就可以过滤掉蓝牙信号强度比较弱（一般也是比较远）的设备。

下面是我的手机扫描到的基于 NodeMCU 开发板的蓝牙设备。

其中名称 Nodemcu 下面的就是广播包的具体数据。

到这里，我们就完成了光照传感器设备的开发工作。

小结

总结一下，在这一讲中，我介绍了光照传感器的开发过程，并且补充了低功耗蓝牙技术的相关知识。下面，我们回顾以下重点：

对于无法连接电源线、需要灵活放置甚至经常移动的设备，低功耗蓝牙技术是合适的通信技术选择。

MiBeacon 协议的广播包定义是基于 BLE 的 GAP（Generic Access Profile）制定的，主要有广播数据（Advertising Data）和扫描回复数据（Scan Response）两种。其中广播数据中 Service Data 的 UUID 是 0xFE95，扫描回复数据中 Manufacturer Specific Data 的厂家识别码是 0x038F。

在日常的蓝牙设备开发工作中，我们经常需要调试、测试蓝牙功能，这时你可以使用手机上的蓝牙调试软件来验证，比如 LightBlue、nRFConnect 和 BLEScanner 等。

不过，准备好光照传感器设备只是第一步，为了实现光照传感器和智能电灯的联动，我们还需要将光照传感器接入网络。这就需要借助蓝牙网关设备了，在下一讲中，我将基于树莓派讲解网关设备的开发过程。

思考题

最后，给你留一个思考题吧。

在这一讲的开头，我提到蓝牙设备除了广播数据的能力，还可以连接进行交互。在我提供的代码中其实也包含了一个可供连接获取数据的 Service 和 Characteristic，你发现了吗？你知道这些是基于低功耗蓝牙中的什么 Profile 协议吗？

欢迎你在留言区写下自己的答案和我交流一下，也欢迎你将这一讲分享给你的朋友，一起讨论学习。

19 | 场景联动：智能电灯如何感知光线？（下）

你好，我是郭朝斌。

在上一讲，我们基于 NodeMCU ESP32 开发板，开发了一款光照传感器。考虑到低功耗的需求，它是基于低功耗蓝牙技术来实现的。但是蓝牙设备本身无法直接联网上报数据，那么我们要怎么根据光照强度数据来联动控制智能电灯呢？

不知道你还记不记得第 9 讲的内容？对于蓝牙设备，我们需要借助网关来实现联网的目的。所以在这一讲中，我会带你用树莓派打造蓝牙网关，最终实现光照传感器和智能电灯的场景联动。

网关系统架构

首先，我们先看一下网关的系统架构。

网关的主要功能是协议转换，一方面它需要接收低功耗蓝牙技术的光照传感器的广播数据，另一方面，它需要把解析的数据上传到云平台。

具体的架构图如下所示。

南向蓝牙通信

在树莓派上进行蓝牙开发，你可以使用bluepy软件包。它提供了一个 Python 语言版本的低功耗蓝牙 API 接口，而且对树莓派的适配非常好。

通过终端登录树莓派

在学习第 15 讲的时候，你应该已经在树莓派上部署好了包含 Gladys Assistant 系统的 Raspbian 操作系统，现在你可以直接使用这个系统。安装软件包之前，我们在电脑终端上输入下面的命令，通过 SSH 协议登录到树莓派系统中。

$ ssh pi@gladys.local

其中，pi 就是默认的登录用户名，gladys.local 是树莓派开发板的本地域名。

当提示输入密码时，我们输入默认密码 raspberry，然后回车，就登录到了树莓派系统中。

通过图形化窗口软件登录树莓派

当然，你也可以使用提供图形化窗口的软件来登录树莓派，比如 SecureCRT，它除了支持串口协议，同时也支持 SSH 协议。你只需要新建一个连接会话，按照下图所示的内容填写就行了：

第一次登录时，SecureCRT 会弹窗提示我们查看“Host Key”，这时点击“Accept Once”即可。

然后我们输入密码“raspberry”，同时勾选“Save password”，省去以后重复输入密码的麻烦。点击“OK”后，就进入树莓派系统了。

在树莓派开发蓝牙程序

我们在树莓派的终端上输入下面命令，就可以完成 bluepy 的安装：

$ sudo apt-get install python3-pip libglib2.0-dev
$ sudo pip3 install bluepy

另外，我们还需要安装 interruptingcow 软件包。它主要是便于编写定时任务。它的安装命令是：

$ sudo pip3 install interruptingcow

具体代码如下，供参考：

#File: blescan.py
import time
from threading import Thread
from interruptingcow import timeout

from bluepy.btle import DefaultDelegate, Peripheral, Scanner, UUID, capitaliseName, BTLEInternalError
from bluepy.btle import BTLEDisconnectError, BTLEManagementError, BTLEGattError

class LightScanner():
    SCAN_TIMEOUT = 5

    def __init__(self, name):
        self._name = name

    def status_update(self):
        results = self._get_data()

        # messages = [
        #     MqttMessage(
        #         topic=self.format_topic("property/light"),
        #         payload=results.lightlevel,
        #     )
        # ]

        return results

    def _get_data(self):

        scan_processor = ScanProcessor(self._name)
        scanner = Scanner().withDelegate(scan_processor)
        scanner.scan(self.SCAN_TIMEOUT, passive=True)

        with timeout(
            self.SCAN_TIMEOUT,
            exception=Exception(
                "Retrieving data from {} device {} timed out after {} seconds".format(
                    repr(self), self._name, self.SCAN_TIMEOUT
                )
            ),
        ):
            while not scan_processor.ready:
                time.sleep(1)
            return scan_processor.results

        return scan_processor.results

class ScanProcessor:

    ADV_TYPE_SERVICE_DATA = 0x16
    def __init__(self, name):
        self._ready = False
        self._name = name
        self._results = MiBeaconData()

    def handleDiscovery(self, dev, isNewDev, _):
        is_nodemcu = False
        if isNewDev:
            for (adtype, desc, value) in dev.getScanData():
                #Service Data UUID == 0xFE95 according to MiBeacon
                if adtype == self.ADV_TYPE_SERVICE_DATA and value.startswith("95fe"):
                    print("FOUND service Data:",adtype, desc, value)
                    #Object ID == 0x1007 according to MiBeacon
                    if len(value) == 38 and value[26:30] == '0710':
                        light_den = int((value[-2:] + value[-4:-2]), 16)
                        mac = value[14:26]

                        self._results.lightlevel = light_den
                        self._results.mac = mac

                        self.ready = True

    @property
    def mac(self):
        return self._mac

    @property
    def ready(self):
        return self._ready

    @ready.setter
    def ready(self, var):
        self._ready = var

    @property
    def results(self):
        return self._results

class MiBeaconData:
    def __init__(self):
        self._lightlevel = None
        self._mac = None

    @property
    def lightlevel(self):
        return self._lightlevel

    @lightlevel.setter
    def lightlevel(self, var):
        self._lightlevel = var

    @property
    def mac(self):
        return self._mac

    @mac.setter
    def mac(self, var):
        self._mac = var

北向 MQTT 对接云平台

接下来，我们要实现网关和云平台的对接。

MQTT 开发环境准备

安装软件包

蓝牙网关与云平台交互的通信协议也是使用 MQTT 协议，所以我们需要安装 MQTT 的软件包。

使用哪个软件包呢？在第 8 讲中我介绍过几个常用的 MQTT 软件包，这里我们选择支持 Python 语言开发的Eclipse Paho软件包。我们在树莓派的终端上输入下面的命令来安装。

$ sudo pip3 install paho-mqtt

安装成功后，我们可以写一个 demo 程序测试一下。下面是我测试的代码，你可以参考。和第 8 讲一样，这段代码仍然会连接到 test.mosquitto.org，并且订阅“/geektime/iot”的主题消息。

#File: mqttdemo.py
import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))

    client.subscribe("/geektime/iot")

def on_message(client, userdata, msg):
    print(msg.topic+" "+str(msg.payload))

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message

#Still connect to mqtt.eclipse.org
client.connect("test.mosquitto.org", 1883, 60)

client.loop_forever()

部署文件到树莓派

现在，我们把测试文件 mqttdemo.py 上传到树莓派上。

你可以在电脑终端上，运行下面的命令。（注意，你需要先在树莓派上创建 pi-gateway 这个目录。）

$ scp mqttdemo.py pi@gladys.local:/home/pi/pi-gateway/

其中这个 scp 命令是基于 SSH 协议实现的安全文档传输功能。

当然，你也可能更习惯图形化的软件，所以我再介绍一个能实现 scp 功能的软件 FileZilla。它支持 MacOS、Windows 和 Linux 操作系统，操作界面也非常直观。

打开“站点管理器”，创建“新站点”。你可以按照下图设置具体配置参数，然后点击“连接”，登录到树莓派系统。为了方便之后的使用，你可以勾选“保存密码”选项。

在软件界面的左半部分是你的电脑上的文件目录，右半部分是树莓派上的目录。你只需要双击左边的某个文件，就可以将文件传输到树莓派上。当然你也可以双击右边树莓派上的文件，将它传输到你的电脑。

把文件传输到树莓派之后，我们就可以在树莓派的终端上输入下面的命令，运行上面的 demo 程序。

$ sudo python3 mqttdemo.py

这时我们把第 8 讲中的发布消息命令再执行一次，如果一切顺利执行，那么就可以在树莓派的终端上看到这个消息。

hbmqtt_pub --url mqtt://test.mosquitto.org:1883 -t /geektime/iot -m Hello,World!

云平台创建光照传感器设备

现在，我们已经做好了对接云平台的准备工作。在树莓派上开发与云平台的通信代码之前，我们还需要在腾讯云平台上创建对应的光照传感器设备。

创建的过程与第 17 讲智能电灯的过程类似。我快速介绍一下，你重点关注不同的地方就可以了。

在“新建产品”中，产品类别选择“智慧生活”-->“安防报警”-->“光照度传感器”。数据协议仍然选择“数据模板”，其他的保持默认值即可。

创建成功后，我们点击进入数据模板的设置界面。为了尽量简单，我只定义了一个属性“光照度”，而且是只读类型。你可以直接导入下面的 JSON 文件完成数据模板的设置。

{
  "version": "1.0",
  "profile": {
    "ProductId": "你的ProductID",
    "CategoryId": "112"
  },
  "properties": [
    {
      "id": "Illuminance",
      "name": "光照度",
      "desc": "光照度检测",
      "mode": "r",
      "define": {
        "type": "float",
        "min": "0",
        "max": "6000",
        "start": "0",
        "step": "1",
        "unit": "Lux"
      }
    }
  ],
  "events": [],
  "actions": []
}

在“交互开发”标签页中，和智能电灯一样，我们仍然保持“使用官方小程序控制产品”选项是打开状态。另外，还有一个配置项需要关注，那就是“智能联动配置”，因为后面我们要为光照传感器设置联动场景。

我们点击“配置”，在设置页面中，就可以看到“光照度”这个属性，因为它是只读属性，所以只能作为联动的触发条件。我们勾选“作为条件”的选项，完成配置。

下一步，在“设备调试”界面中，我们创建一个测试设备。点击“新建设备”，输入设备名称“Lightsensor_1”。

创建成功后，在测试设备列表中，点击“Lightsensor_1”，进入设备的详情页面，我们可以看到设备三元组的信息。你需要将这些信息记录下来，因为后面的开发中需要使用。

在测试设备列表中，我们点击“二维码”操作，获取测试设备的二维码，以便在小程序“腾讯连连”中添加这个设备。

到这里，腾讯云平台上的产品创建工作就完成了。

产品联网开发

在腾讯云平台准备好产品的配置工作之后，我们继续在树莓派上完成北向的通信交互的开发工作。

在第 17 讲中，我们已经了解了 MQTT 通信的主题 Topic ，以及 Broker 服务器地址、端口号、设备 ID（ClientID）、用户名（UserName）和密码（Password）等连接参数的知识。

我们还是可以使用 sign.html 这个网页工具生产用户名和密码，然后就能得到所有的参数。这时，把这些参数替换到下面这段代码的对应位置就可以了。

#File: gateway.py
from blescan import LightScanner, MiBeaconData

import time
import asyncio
import json
import uuid
import paho.mqtt.client as MQTTClient

"""
QCloud Device Info
"""
DEVICE_NAME = "Lightsensor_1"
PRODUCT_ID = "MAO3SVUCFO"
DEVICE_KEY = "TYjuKNc2GpDykXUv4MWBOA=="

"""
MQTT topic
"""
MQTT_CONTROL_TOPIC = "$thing/down/property/"+PRODUCT_ID+"/"+DEVICE_NAME
MQTT_CONTROL_REPLY_TOPIC = "$thing/up/property/"+PRODUCT_ID+"/"+DEVICE_NAME

def mqtt_callback(client, userdata, msg):
    # Callback
    print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic")

async def mqtt_connect():
    #connect callback
    def on_connect(client, userdata, flags, rc):
        if rc == 0:
            print("Connected to MQTT Broker!")
        else:
            print("Failed to connect, return code %d\n", rc)

    mqtt_client = None
    MQTT_SERVER = PRODUCT_ID + ".iotcloud.tencentdevices.com"
    MQTT_PORT = 1883
    MQTT_CLIENT_ID = PRODUCT_ID+DEVICE_NAME
    MQTT_USER_NAME = "MAO3SVUCFOLightsensor_1;12010126;2OYA5;1609057368"
    MQTTT_PASSWORD = "8f79b7f1b0bef9cde7fd9652383b6ff8bfeb8003cc994c64f3c8e069c11fd4c7;hmacsha256"

    mqtt_client = MQTTClient.Client(MQTT_CLIENT_ID)
    mqtt_client.username_pw_set(MQTT_USER_NAME, MQTTT_PASSWORD)
    mqtt_client.on_connect = on_connect

    mqtt_client.connect(MQTT_SERVER, MQTT_PORT, 60)

    return mqtt_client

def mqtt_report(client, light_level):
    client_token = "clientToken-" + str(uuid.uuid4())

    msg = {
        "method": "report",
        "clientToken": client_token,
        "params": {
            "Illuminance": light_level
        }
    }

    client.publish(MQTT_CONTROL_REPLY_TOPIC, json.dumps(msg))

async def light_loop(mclient):

    bles = LightScanner('Nodemcu')

    mclient.subscribe(MQTT_CONTROL_TOPIC)
    mclient.on_message = mqtt_callback

    mclient.loop_start()

    while True:
        try:
            data = bles.status_update()
        except Exception as e:
            print("BLE SCAN error:", e)
            continue

        print("Light Level:", data.lightlevel)

        mqtt_report(mclient, data.lightlevel)

        time.sleep(0.1)

async def main():
    mqtt_client = None
    # MQTT connection
    try:
        mqtt_client = await asyncio.wait_for(mqtt_connect(), 20)
    except asyncio.TimeoutError:
        print("mqtt connected timeout!")

    if mqtt_client is not None:
        await asyncio.gather(light_loop(mqtt_client))

asyncio.run(main())

在树莓派上部署软件

接下来，我们把代码文件 gateway.py 和 blescan.py 两个文件也上传到树莓派的 /home/pi/pi-gateway 目录中。

同时，为了让程序作为后台服务运行，并且能够开机自启动，我们来做一个 Pi Gateway Service。

首先，你需要新建一个 service.sh 脚本文件，内容如下：

#!/bin/sh
set -e
SCRIPT_DIR=$( cd "$( dirname "$0" )" >/dev/null 2>&1 && pwd )

cd "$SCRIPT_DIR"
sudo python3 ./gateway.py "$@"

然后，创建我们 service 的配置文件，内容如下：

[Unit]
Description=Pi Gateway
Documentation=https://time.geekbang.org/column/intro/100063601
After=network.target

[Service]
Type=simple
WorkingDirectory=/home/pi/pi-gateway
ExecStart=/home/pi/pi-gateway/service.sh
Restart=always

[Install]
WantedBy=multi-user.target

接着，把这两个文件上传到树莓派系统的 /home/pi/pi-gateway 目录中，并且运行下面命令，修改文件的属性。

$ sudo chmod a+x service.sh
$ sudo chmod a+x pi-gateway.service

最后，执行下面的几条命令，为树莓派系统增添上 Pi Gateway 这个服务。

$ sudo cp /home/pi/pi-gateway/pi-gateway.service /etc/systemd/system/
$ sudo systemctl daemon-reload
$ sudo systemctl start pi-gateway
$ sudo systemctl status pi-gateway
$ sudo systemctl enable pi-gateway

到这里，网关程序已经在树莓派上运行起来。我们在腾讯云物联网平台上可以看到，光照传感器变为“在线”状态。

设置场景联动

在第 17 讲和第 18 讲的实战中，我们分别完成了智能电灯和光照传感器的开发，现在终于可以为它们设置场景联动了。

场景联动任务分解

我们希望实现的联动场景是，基于环境的光照强度自动控制电灯的开和关。具体来说，这个目标可以拆解为 3 个自动触发任务：

当光照强度大于 1024Lux 时，关闭电灯。

当光照强度小于 1024Lux 时，打开电灯。

至于光照强度等于 1024Lux 时，也打开电灯。

注意，这里的 1024Lux 是我自己选择的一个值，你可以根据房屋情况自己调整。

联动设备准备

如果你还没有在小程序中添加光照传感器设备，这时可以打开微信中的腾讯连连小程序，扫描上面云平台“设备调试”中保存的那个二维码，添加光照传感器测试设备“Lightsensor_1”。

现在你的小程序里面已经有了两个设备，如下图所示。

刚才我们已经在腾讯云物联网平台上，为光照传感器设置了“智能联动配置”。现在，我们来为智能电灯配置智能联动能力。

我们进入智能电灯的“交互开发”页面，打开下面的“智能联动配置”页面，然后，像下图显示的那样，把“电灯开关”的“作为任务”条件勾选上。

联动任务创建

然后，我们进入腾讯连连小程序，点击下面的“+”，选择“添加智能”，开始配置工作。

我们从弹框里选择“自动智能”，可以看到下图的配置界面：

首先，我们添加条件，选择光照传感器设备，然后就会看到光照度属性。我们先设置大于 1024Lux 的条件。

然后，我们添加任务，选择智能电灯设备后，可以看到电灯开关的属性，选择“关”，点击保存。

这时，我们可以看到这个智能联动的条件和任务已经配置完成。腾讯连连小程序还支持配置“生效时间段”，可以限定智能联动在选定的时间段内运行。

接下来，我们还可以设置一个主题图片和名称，这个根据喜好来就行了。

按照相同的方法，我们可以设置其他两个条件，如下图所示：

最终的智能联动，包括了刚才提到的 3 个不同的触发条件。

现在，你可以通过控制光照传感器的光照明暗（比如用手遮挡光敏元器件然后再把手拿开），来观察智能电灯的打开和关闭，检验功能是否正常。

小结

总结一下，在这一讲中，我介绍了利用树莓派打造网关，让光照传感器接入物联网平台的办法，并且带你实现了光照传感器和智能电灯的场景联动。你需要重点关注的内容有：

为了实现协议转换，树莓派的南向接口，也就是蓝牙功能，你可以基于 bluepy 软件包开发。这里实现的功能是扫描光照传感器的广播包，并按照 MiBeacon 蓝牙协议解析出光照强度的数值。

北向接口要实现对接云平台的功能，这是基于 MQTT 协议实现的。你可以基于 Eclipse paho 的 Python 语言版本来开发 MQTT Client 的功能。

场景联动一般由条件和任务组成。其中，条件和任务是从我们的设备中定义的智能联动配置中选择的。

为了避免光照的短暂变化，导致智能电灯的忽明忽暗，我将光照传感器的数据上报间隔设置得比较长。如果你有特殊的需求，可以修改光照传感器和网关程序中的参数来实现。

下一讲，我将讲解智能音箱的实现，并通过智能音箱控制智能电灯的开关。

思考题

最后，我给你留一个动手实践题。

在这一讲的场景联动中，我们实现了光照强度对电灯打开和关闭的自动控制。你可以通过光照强度的不同数值实现对智能电灯亮度，或者颜色的控制吗？

你可以动手设置一下，并且在留言区和我分享你的成果，同时，也欢迎你将这一讲分享给你的朋友，大家一起讨论学习。

20 | 智能语音：好玩的语音控制是怎么实现的？

你好，我是郭朝斌。

实战篇的前几讲，我们打造了联网智能电灯，并实现了跟光照传感器的场景联动。今天我们来玩一个更酷的，智能音箱。

智能音箱为我们提供了一种更加自然的交互方式，所以亚马逊的 Echo 产品一经问世，就迅速流行起来。与智能家居结合之后，它更是引起了行业巨头的注意，被认为是很有发展潜力的用户入口和平台级产品。

我们先不论智能音箱最终到底能不能发展成智能家居的平台级产品，至少这波热潮已经极大地推动了相关技术的发展，而且用户覆盖率也有了很大的提升。

这一讲我就为你介绍一下智能音箱的语音控制是怎么实现的，并且带你动手完成开发过程。

智能音箱的技术架构

智能音箱主要涉及拾音、前端信号处理、语音识别、自然语言处理和语音合成等技术，现在一些产品甚至提供了声纹识别技术。

当然，智能音箱最重要的是提供各种功能，完成一些任务，比如控制电灯的开和关，这被称为技能。

整体的技术架构如下图所示：

接下来，我会逐个讲解这些技术组成。

拾音

拾音，就是通过麦克风获取你的语音。

我们都用微信发送过语音消息，手机就是通过麦克风来获取你说的话的，这么说起来，拾音好像很简单。但是，智能音箱应对的环境要更复杂，因为用户可能在比较远的地方下达语音指令。

因此，智能音箱上一般采用麦克风阵列（Mic Array），也就是按照一定规则排列的多个麦克风，比如下图展示的就是Amazon Echo由 7 个麦克风组成的阵列（绿色圆圈部分）。

前端语音信号处理

在收集到声音信号后，还需要进行前端语音信号处理。只有经过处理，智能音箱才能获取到相对干净的语音信号，也才能提高后面的语音识别的准确率。

这些处理技术包括回声消除（Acoustic Echo Cancellaction, AEC）、噪音抑制（Noise Suppression，NS）、语音检测（Voice Activity Detection，VAD）、声源定位（Direction of Arrival estimation，DOA）、波束成型（Beamforming）和混响消除（Speech Dereverberation）等。

语音唤醒

语音唤醒（Keyword Spotting，KWS），就是通过特定的唤醒词来激活智能音箱，以便进行后续的语音交互任务。这样做一方面可以保护用户的隐私，因为只有唤醒后，音箱才收集和识别用户的语音信息，另一方面也可以简化语音的识别和理解，比如小米智能音箱的“小爱同学”就是这样的唤醒词。

语音识别

语音识别（Automatic Speech Recognition，ASR），主要完成的任务是将语音转换成文本，所以也被称为 STT（Speech to Text）。

自然语言理解

自然语言理解（Natural Language Understanding，NLU），是对语音识别生成的文本进行处理，识别用户的意图，并生产结构化的数据。

当然，以现在的人工智能发展水平来看，自然语言理解还有很长的路要走。这也是我们常发现智能音箱不够“智能”的原因。

技能

技能（Skills）一般要借助后端云平台的强大能力，云平台可以提供知识图谱、家居设备远程控制和音乐等音频资源等能力。

自然语言生成

自然语言生成（Natural Language Generation，NLG），就是将各种技能的响应结果组织成文本语言。比如当你询问天气时，根据获取的天气状况和温度等信息生成“北京今天晴，最高温度 5°，最低温度 -6°”这样的语句。自然语言生成和自然语言理解都属于自然语言处理（Natural Language Processing，NLP）的范畴。

语音合成

语音合成（Speech Synthesis），就是将自然语言生成的文本转换为语音的形式，提供给智能音箱播放出来，给人的感觉就像和音箱在对话。因此，这个过程也叫做 TTS（Text to Speech）。

智能音箱的开发

了解完智能音箱的基本技术构成，下面我们就基于树莓派开发一个自己的简易智能音箱吧。

首先，我需要说明一下树莓派的系统。为什么呢？因为在第 15 讲中，我们安装了 Gladys Assistant 系统镜像，而这个系统 Raspbian 是基于 Debian buster 版本的，一些语音识别开源库对于 buster 的支持并不够好。

所以，如果你的树莓派是 Raspberry Pi 3 系列，强烈建议你把系统镜像切换成 Debian stretch 版本。通过这个链接就可以下载基于 Debian stretch 版本的 Raspbian 镜像文件压缩包，安装还是使用 Etcher 工具，你可以回头看一下第 15 讲的介绍。

至于树莓派 Raspberry Pi 4 系列，因为官方系统 Raspbian 只有 buster 版本支持，所以我们还是继续基于第 15 讲的系统开发。

麦克风阵列

麦克风阵列我使用的是 ReSpeaker 2-Mics Pi HAT，它的 2 个麦克风分布在模组的两边。我们现在来配置一下，让它可以在树莓派上正常工作。

你可以通过下面的命令安装它的驱动程序。首先，你最好切换一下树莓派的软件安装源，将它切换到国内的腾讯云安装源，这样下载安装的速度比较快。运行下面的命令修改配置文件：

$ sudo vim /etc/apt/sources.list

将文件修改为下面的内容：

deb https://mirrors.cloud.tencent.com/raspbian/raspbian/ buster main contrib non-free rpi
# Uncomment line below then 'apt-get update' to enable 'apt-get source'
deb-src https://mirrors.cloud.tencent.com/raspbian/raspbian/ buster main contrib non-free rpi

修改另一个软件安装源的配置文件，命令如下所示：

$ sudo vim /etc/apt/sources.list.d/raspi.list

修改后的文件内容如下：

deb https://mirrors.cloud.tencent.com/raspberrypi/ buster main
# Uncomment line below then 'apt-get update' to enable 'apt-get source'
deb-src https://mirrors.cloud.tencent.com/raspberrypi/ buster main

然后，你需要运行下面的命令更新安装源：

$ sudo apt-get clean all
$ sudo apt-get update

现在，你可以运行下面命令安装麦克风阵列的驱动程序。因为这个驱动依赖的 wm8960 编解码器没有包含在树莓派系统的内核里面，需要重新加载内核，编译驱动，所以整个过程比较久。在等待的过程中，你可以先阅读这一讲的其他部分。

$ sudo apt-get install git
$ git clone --depth=1 https://github.com/respeaker/seeed-voicecard
$ cd seeed-voicecard
$ sudo ./install.sh
$ sudo reboot

树莓派重启之后，你可以在树莓派终端输入下面的命令，查看音频的输入和输出设备是否正常工作。

$ arecord -l
$ aplay -l

如果一切正常，我们就可以测试录音和播放功能了。在 ReSpeaker 2-Mics Pi HAT 的耳机插口上插入耳机或者扬声器，运行下面的命令，并说几句话。

$ arecord -d 5 test.wav
$ aplay test.wav

另外，你也可以通过软件 AlsaMixer（命令 alsamixer）来配置声音设置和调整音量，左、右箭头键用于选择通道或设备，向上、向下箭头控制当前所选设备的音量。退出程序使用 ALT + Q，或者按 Esc 键。

为了简化开发，也考虑到麦克风硬件的限制，我们这里就先不关注前端语音信号处理的相关开发了。接下来，我们直接来到实现语音唤醒的环节。

语音唤醒

为了实现语音唤醒，我们需要选择一个轻量级的、可以在树莓派上运行的唤醒词监测器软件。

你可能首先想到的是 Snowboy，没错，它确实是一个非常流行的工具。不过，Snowboy 团队在 2020 年初的时候宣布，2020 年 12 月 31 日会停止提供服务，所以我们只能寻找替代方案。

我选择的是Mycroft Precise，它是一个基于 RNN 神经网络的语音唤醒工具。

接下来，我们在树莓派安装 Mycroft Precise。因为需要训练唤醒词模型，我们需要基于源代码来编译、安装。

首先，我们通过 git 命令把 Mycroft Precise 的源代码下载到树莓派的 /home/pi 目录：

$ cd ~
$ git clone https://github.com/mycroftai/mycroft-precise
$ cd mycroft-precise

在安装之前，把 pypi 的安装源修改到清华数据源，可以获得更快的下载速度。我们打开目录中的 setup.sh 文件：

$ vim setup.sh

将文件中的这行内容：

extra-index-url=https://www.piwheels.org/simple

替换成下面的内容：

index-url=https://pypi.tuna.tsinghua.edu.cn/simple
extra-index-url=https://www.piwheels.org/simple

然后，我们运行它自带的安装脚本，开始编译和安装。中间如果执行中断，可以重新执行这个命令，继续安装过程。（提示：有些 ARM 平台的库只有 piwheels 上有，所以这些库安装时速度还是很慢。这种情况下，可以电脑上使用下载工具获取这个模块的安装文件，然后上传到树莓派上，手动安装。）

$ ./setup.sh

安装完成后，我们开始使用 Mycroft Precise 来训练一个唤醒词模型，唤醒词可以根据喜好来选择，比如“极客时间”。

我们需要先激活 Python 的虚拟环境，因为 Mycroft Precise 在安装过程中创建了这个虚拟环境。

$ source .venv/bin/activate

接下来，我们通过工具 precise-collect 来收集语音模型训练的声音素材，运行后，根据提示录制 12 段声音。

$ precise-collect
Audio name (Ex. recording-##): geektime.##

Press space to record (esc to exit)...
Recording...
Saved as geektime-00.wav
Press space to record (esc to exit)...

然后，我们需要将这些声音随机分为两份，一份是训练样本，包括 8 个声音文件，另一份是测试样本，包括 4 个声音文件，并且把这两份样本分别放到 geektime/wake-word/ 和 /geektime/test/wake-word/ 这两个目录下面。

接着，我们执行下面的命令，生成神经网络模型 geektime.net：

$ precise-train -e 60 geektime.net geektime/

最后，我们还需要将 geektime.net 的模型格式做一下转换，将它从 Keras 模型格式改为 TensorFlow 模型格式，因为 TensorFlow 模型更加通用。

$ precise-convert geektime.net

执行完成之后，我们会得到两个文件：

geektime.pb，TensorFlow 模型文件

geektime.pb.params，包含 Mycroft Precise 在处理音频时需要的一些参数信息。

当然，为了提高模型的准确性，我们还可以使用 precise-train-incremental 工具来增加负样本，重新训练刚才的模型。如果环境复杂的话，你可以尝试一下。

然后，我们可以运行一段代码来测试这个唤醒词模型。不过，因为 portaudio 这个库在树莓派上运行有问题，我们需要先修复一下 portaudio 库。你可以运行下面的命令：

$ sudo apt-get remove libportaudio2
$ sudo apt-get install libasound2-dev
$ git clone -b alsapatch https://github.com/gglockner/portaudio
$ cd portaudio
$ ./configure && make
$ sudo make install
$ sudo ldconfig

测试程序的代码如下：

# File：kwsdemo.py
#!/usr/bin/env python3

from precise_runner import PreciseEngine, PreciseRunner

engine = PreciseEngine('precise-engine/precise-engine', 'geektime.pb')
runner = PreciseRunner(engine, on_activation=lambda: print('hello'))
runner.start()

# Sleep forever
from time import sleep
while True:
    sleep(10)

现在，我们把 kwsdemo.py 文件，还有两个 geektime.pb 模型相关的文件，都上传到树莓派的 Mycroft Precise 目录下，然后运行 kwsdemo.py 文件，说出“极客时间”几个字，就会看到终端显示出“hello”这个单词。

语音识别

对于语音识别，我们直接采用腾讯云提供的语音识别 SDK 来完成（你需要提前在腾讯云控制台开通这个服务）。它会将语音发送到云端，由云端服务器计算出文本信息。你可以通过下面命令来安装：

$ pip3 install tencentcloud-sdk-python

在开始使用之前，你需要访问这个链接创建一个密钥，然后记录下 SecretId 和 SecretKey 的信息。

你可以参考下面的代码，来完成一个录音文件的识别。

from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.asr.v20190614 import asr_client, models
import base64
import io
import sys

SECRET_ID = "你的Secret ID"
SECRET_KEY = "你的Secret Key"

try:
    cred = credential.Credential(SECRET_ID, SECRET_KEY)
    httpProfile = HttpProfile()
    httpProfile.endpoint = "asr.tencentcloudapi.com"
    clientProfile = ClientProfile()
    clientProfile.httpProfile = httpProfile
    clientProfile.signMethod = "TC3-HMAC-SHA256"
    client = asr_client.AsrClient(cred, "ap-beijing", clientProfile)
    #读取文件以及 base64
    with open('./geektime-00.wav', "rb") as f:
        if sys.version_info[0] == 2:
            content = base64.b64encode(f.read())
        else:
            content = base64.b64encode(f.read()).decode('utf-8')
        f.close()
    #发送请求
    req = models.SentenceRecognitionRequest()
    params = {"ProjectId":0,"SubServiceType":2,"SourceType":1,"UsrAudioKey":"sessionid-geektime"}
    req._deserialize(params)
    req.DataLen = len(content)
    req.Data = content
    req.EngSerViceType = "16k_zh"
    req.VoiceFormat = "wav"
    resp = client.SentenceRecognition(req)
    print(resp.to_json_string())

except TencentCloudSDKException as err:
    print(err)

语音合成

接下来，我来介绍一下语音合成。

你可能会问，刚才介绍技术架构的时候，不是还讲了自然语言理解、技能和自然语言生成吗？这里怎么跳过去了呢？

首先，因为我们的任务很简单，只需要查询语音识别的文本中是否有“开”、“灯”，和“关”、“灯”就可以完成判断，所以自然语言理解直接判断字符串是否匹配即可。

其次，我们要实现控制智能电灯，这个技能我在后面会介绍。

最后，智能音箱只需要反馈执行开关灯的结果就可以，比如“我已经把灯打开了”或者“我已经把灯关了”，自然语言生成的部分按照固定的文本就可以了，不需要考虑动态生成的问题。

语音合成，就是我们希望把类似“我已经把灯关了”这样的文本信息，转换为音频，便于智能音箱播放出来。你可以基于离线的 TTS 引擎来实现，比如HanTTS这个项目。

当然，我们也可以使用腾讯云的语音合成服务（你需要提前在腾讯云控制台开通这个服务）。你可以参考下面的代码：

import json
import base64

from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.tts.v20190823 import tts_client, models

SECRET_ID = "你的Secret ID"
SECRET_KEY = "你的Secret Key"

try:
    cred = credential.Credential(SECRET_ID, SECRET_KEY)
    httpProfile = HttpProfile()
    httpProfile.endpoint = "tts.tencentcloudapi.com"

    clientProfile = ClientProfile()
    clientProfile.httpProfile = httpProfile
    client = tts_client.TtsClient(cred, "ap-beijing", clientProfile)

    req = models.TextToVoiceRequest()
    params = {
        "Text": "我已经把灯关了",
        "SessionId": "sessionid-geektime",
        "ModelType": 1,
        "ProjectId": 0,
        "VoiceType": 1002
    }
    req.from_json_string(json.dumps(params))

    resp = client.TextToVoice(req)
    print(resp.to_json_string())

    if resp.Audio is not None:
        audio = resp.Audio
        data = base64.b64decode(audio)
        wav_file = open("temp.wav", "wb")
        wav_file.write(data)
        wav_file.close()

except TencentCloudSDKException as err:
    print(err)

通过智能音箱控制电灯

为了实现控制智能电灯的目的，我们需要借助物联网平台提供的开发接口。

首先，我们进入物联网开发平台，选择“智能家居”项目。

然后，点击左侧的“应用开发”，进入新建应用的界面，点击“新建应用”。

完成后，点击应用列表里面的应用名称，进入应用的详情页面。你可以看到应用的 SecretId 和 SecretKey 信息。这里，你需要将下面“关联产品”中的智能电灯勾选上。只有建立关联，应用才可以控制这个设备。

具体代码可以参考腾讯提供的开源实现，包括iOS、Android和小程序。

不过，这种方式需要用户账号的登录认证，在树莓派上不太方便。还有一个方式就是基于物联网开发平台提供的通用 API 接口。其中的“设备远程控制”接口可以满足我们的需求。

具体的控制方法，你可以参考下面的代码（注意，目前只支持 ap-guangzhou 区域）。

import json
from led2.main import PRODUCT_ID
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.iotexplorer.v20190423 import iotexplorer_client, models

SECRET_ID = "你的Secret ID"
SECRET_KEY = "你的Secret Key"
PRODUCT_ID = "你的ProductID"

def Light_control(state):
    try:
        cred = credential.Credential(SECRET_ID, SECRET_KEY)
        httpProfile = HttpProfile()
        httpProfile.endpoint = "iotexplorer.tencentcloudapi.com"

        clientProfile = ClientProfile()
        clientProfile.httpProfile = httpProfile
        client = iotexplorer_client.IotexplorerClient(cred, "ap-guangzhou", clientProfile)

        req = models.ControlDeviceDataRequest()
        data = {
            "power_switch": state
        }
        data_str = json.dumps(data)

        params = {
            "DeviceName": "Led_1",
            "ProductId": PRODUCT_ID,
            "Data": data_str
        }
        req.from_json_string(json.dumps(params))

        resp = client.ControlDeviceData(req)
        print(resp.to_json_string())

    except TencentCloudSDKException as err:
        print(err)

Light_control(0)

小结

总结一下，在这一讲中，我介绍了智能音箱的技术架构，以及在树莓派上用于实现智能音箱的一些可选的技术方案，并且带你实现了语音控制智能电灯的目的。你需要重点关注的知识有：

智能音箱的实现，需要前端音箱本体和后端云平台上一系列技术的支持。这些技术有前端的拾音、语音信号处理、语音唤醒和播音，以及后端的语音识别、自然语言理解、技能、自然语言生成和语音合成。

在树莓派的实现上，拾音可以选择使用麦克风阵列，因为基于麦克风阵列可以更好地实现前端语音信号处理，比如声源定位和波束成型等。

语言唤醒需要在智能音箱本体上实现，所以需要一些轻量级的识别引擎和训练好的唤醒词模型。之前比较流行的 Snowboy 将要停止服务，这里我选择了 Mycroft Precise 这个开源方案。

语音识别、自然语言理解、技能、自然语言生成和语音合成等任务适合基于云平台的能力来实现，因为云平台的计算能力更强，有更好的性能和准确度。

智能音箱的技术也一直在发展，比如现在越来越多的智能音箱开始配备屏幕和摄像头，这为智能音箱引入了声音、UI 和视觉等多模态的交互方式，相应地，这也给声纹识别、人脸识别和动作识别等技术带来了新的应用场景。我相信智能音箱未来的产品形态和功能还会不断地进化和发展。

思考题

最后，我给你留一个思考题吧。

在这一讲中，我们是通过物联网平台提供的 API 接口来控制智能电灯的。除了这种方式，你还能想到其他的方法来远程控制智能电灯吗？你能实现一个虚拟的联网开关，基于场景联动来控制智能电灯的开和关吗？

欢迎你在留言区写下你思考的结果，也欢迎你将这一讲分享给你的朋友，大家一起交流学习。

21 | 多传感器集成：浇花怎么实现自动化？

你好，我是郭朝斌。

在前几讲的实战中，我们已经完整地实现了一个智能家居的照明场景。你不但可以用手机小程序控制智能电灯的开关、亮度和颜色，也能让智能电灯根据光照强度自动地打开和关闭，甚至你还可以语音来操控它。

在这个过程中，你应该已经掌握了基于硬件开发板快速地开发智能硬件的方法。这一讲中，我们就围绕自动浇花，做一个新的智能家居场景解决方案，同时也巩固一下你在前几讲学习到的硬件开发知识。

研究场景需求

在设计物联网产品的时候，我们需要先研究场景需求，明确监控指标，再根据这些指标安排合适的传感器。

比如自动浇花这个场景，很明显是为了自动控制水泵，及时给植物补充水分。毕竟水不仅是植物体的主要成分，支撑着细胞、维持着植物形态，而且也是传输营养物质的重要载体。所以，我们要监控的最重要的指标就是土壤湿度。

不过，用户的目的并不只是浇水而已，他们真正想要的，是看到自己养的植物健康地生长，所以我们最好把其他相关的数据也提供给他们。现在很多智能体重秤都会同时检测你的体脂率等其他反映身体健康的指标，也是一样的道理。

那么，植物生长还需要什么条件呢？

首先是环境温湿度条件，热带的植物无法忍受低温和干燥的环境条件，而温带植物遇到高温，也可能出现热衰竭的现象，所以环境的温度和湿度最好也能监控。

其次是光照条件，毕竟要植物进行光合作用嘛，所以光照强度需要监控。

最后，空气质量对植物的生长来说也很重要，比如二氧化碳是否充足，有害气体是否超标等。

不过考虑到家居环境，空气质量在日常种植过程中一般没有问题，不需要特别关注。当然，你也可以作为扩展任务来尝试一下。

所以综合来看，我们需要监控的指标包括土壤湿度、环境温湿度和光照强度。相比于市面上其他只能控制浇水的产品，我们的产品考虑的场景就更加全面了。这也是自己动手（DIY）的好处。

硬件电路

明确了监控指标之后，相应的传感器也就可以确定了，分别是土壤湿度传感器、环境温湿度传感器和光照传感器。

下面，我总结一下自动浇花器需要用到的材料：

NodeMCU ESP32 开发板。不过，这里我们可以使用 Wi-Fi 来连接网络，而不是第 18 讲中用到的蓝牙技术。因为在现实中，考虑到自动浇花器的工作环境，连接电源还是比较常见的。

继电器，用于控制水泵的供电电路的通断。

水泵，用于从水箱中抽水，并送到花盆中。

电池盒，用于给水泵供电。

土壤湿度传感器，它可以测量花盆土壤的湿度，然后输出模拟信号。

环境温湿度传感器，它基于 DHT11 传感器，用于测量房屋中的温度和湿度，并且输出数字信号。它采用单总线（1-wire）接口与 NodeMCU 连接。

光照传感器，它可以测量花盆位置接收到的光照条件。每种植物有喜阳光、耐阴等不同的特性，光照度和一段时间的光照累积量可以给我们提供参考，以便更好地满足植物的光照需求。

面包板和杜邦线。

这里，我给出了一个电路图，方便你了解整个自动浇花器的设备组成。

软件实现

接下来，我们还是使用 Python 语言来完成软件的开发工作。

继电器

首先是继电器的控制。它和第 17 讲介绍的智能电灯的继电器控制方法没有本质区别，主要是改变了连接的 GPIO 管脚。根据硬件电路的连线，这里我们使用的是 GPIO23 管脚。

我把代码直接贴在文稿中，供你参考（它连接的管脚是 GPIO23）：

## ！！！本文件采用商城的继电器模块FL-3FF-S-Z
## ！！！on(), off()状态相反。
## ！！！初始化中需要调用on()先关闭水泵电路
from machine import Pin

class Relay():
    ON = 0
    OFF = 1

    def __init__(self, pin):
        self.relaypin = Pin(pin, Pin.OUT)
        self.relaypin.on()
        self.last_status = self.OFF

    def set_state(self, state):
        tmp_state = self.ON if state==1 else self.OFF
        self.relaypin.value(tmp_state)
        self.last_status = tmp_state

    def state(self):
        return self.last_status
    def on(self):
        self.relaypin.value(self.ON)
        self.last_status = self.ON

    def off(self):
        self.relaypin.value(self.OFF)
        self.last_status = self.OFF

土壤湿度传感器

然后是土壤湿度传感器，我采用的是基于测量电阻值的变化来判断土壤水分含量的传感器模块。它会根据不同的电阻值，输出变化的模拟信息，数值越小，说明越干燥。使用的时候，你需要把它完全插入花盆的土壤中。

另外，还有一种基于土壤湿度变化，引起电容值变化的原理设计的土壤湿度传感器，你也可以考虑使用。它的好处是长期使用过程中不易腐蚀，因为没有裸露金属。

我把代码直接贴在文稿中，供你参考（它连接的管脚是 GPIO34）：

from machine import ADC
from machine import Pin

class SoilSensor():

    def __init__(self, pin):
        self.sensor = ADC(Pin(pin))

    def value(self):
        value = self.sensor.read()
        print("Sensor ADC value:",value)
        return int(value*100/4095)

环境温湿度传感器

我刚才介绍过，环境温湿度传感器是基于 DHT11 实现的，并且使用的是单总线的连接方式。不过代码的开发过程并不复杂，因为 MicroPython 已经为 ESP32（也包括 ESP8266）实现了 DHT 代码组件。我们可以直接使用。

注意，DHT11 的单总线协议是奥松电子的自定义协议，它与 Dallas 半导体公司的 OneWire 协议是不同的。

代码我贴在下面，供你参考（它连接的管脚是 GPIO14）：

import dht
from machine import Pin

class EnvSensor():

    def __init__(self, pin):
        self.sensor = dht.DHT11(Pin(pin))

    def value(self):
        self.sensor.measure()
        return (self.sensor.temperature(), self.sensor.humidity())

光照传感器

至于光照传感器，我们还可以继续使用第 18 讲的模组，所以我就不过多介绍了。

我直接贴出代码，供你参考（它连接的管脚是 GPIO36）：

from machine import ADC
from machine import Pin

class IllumSensor():

    def __init__(self, pin):
        self.sensor = ADC(Pin(pin))

    def value(self):
        value = self.sensor.read()
        print("Sensor ADC value:",value)
        return int(value/4095*600

完成联网开发

为了实现自动浇花器的联网控制，我们仍然需要将它接入腾讯云物联网平台。不过，我希望你能尝试独立完成这个工作。完成之后，你一定会感受到自己的成长。如果实现过程中有困难，可以回头参考第 17 讲的内容。

这里，我提供一下自动浇花器的物模型 JSON 文件，供你参考。

{
    "version": "1.0",
    "profile": {
      "ProductId": "你的ProductID",
      "CategoryId": "909"
    },
    "properties": [
      {
        "id": "power_switch",
        "name": "水泵开关",
        "desc": "控制水泵启动关闭",
        "mode": "rw",
        "define": {
          "type": "bool",
          "mapping": {
            "0": "关",
            "1": "开"
          }
        }
      },
      {
        "id": "water_shortage",
        "name": "缺水状态",
        "desc": "水箱是否缺水",
        "mode": "r",
        "define": {
          "type": "bool",
          "mapping": {
            "0": "否",
            "1": "是"
          }
        }
      },
      {
        "id": "humidity",
        "name": "土壤湿度",
        "desc": "当前的土壤湿度",
        "mode": "r",
        "define": {
          "type": "int",
          "min": "0",
          "max": "100",
          "start": "0",
          "step": "1",
          "unit": "%"
        }
      },
      {
        "id": "env_temp",
        "name": "环境温度",
        "desc": "空间环境的温度",
        "mode": "r",
        "define": {
          "type": "float",
          "min": "-40",
          "max": "100",
          "start": "0",
          "step": "0.1",
          "unit": "℃"
        },
        "required": false
      },
      {
        "id": "env_hum",
        "name": "环境湿度",
        "desc": "周围环境的湿度",
        "mode": "r",
        "define": {
          "type": "int",
          "min": "0",
          "max": "100",
          "start": "0",
          "step": "1",
          "unit": "%"
        },
        "required": false
      },
      {
        "id": "env_illum",
        "name": "环境光照度",
        "desc": "周围环境的光照度",
        "mode": "r",
        "define": {
          "type": "int",
          "min": "0",
          "max": "6000",
          "start": "0",
          "step": "1",
          "unit": "lux"
        },
        "required": false
      }
    ],
    "events": [],
    "actions": []
  }

需要说明的一点是，其实 DHT11 的测温范围只有 0～50℃，但这里设定的环境温度范围是 -40℃～80℃。之所以这么设定，是因为另外一款温湿度传感器 DHT22 可以达到这个范围，这样当你想根据不同的环境条件灵活地调整温湿度传感器模块的时候，就不需要修改物模型了。

多传感器融合

在刚才的开发工作中，我们只使用了一个土壤湿度传感器来判断土壤的水分含量。先不考虑传感器本身的测量误差，单纯只测量土壤中一个位置的湿度数值，其实是不能准确反映整片土壤的水分情况的。

所以，我们可以使用多个土壤传感器进行测量，然后根据多个测量值来计算出更可靠的土壤水分含量。

这个计算过程就是多传感器融合（Multi-Sensor Fusion，MSF）。

它不仅在自动驾驶这样的前沿领域中成为了保证决策正确的关键技术，而且在日常生活中也已经有了广泛的应用，比如手机上的地理位置定位。

我们知道，GPS 是通过卫星进行定位的，但是它的精度受到很多因素的影响。为了提高定位的精度，现在我们的手机上就普遍采用多传感器融合的方法，通过 GPS、蜂窝通信网基站和 Wi-Fi 热点的数据来综合计算，得到更准确的地理定位信息。

多传感器融合最关键的地方，不是多个传感器的硬件连接或数据收集，而是融合算法，也就是怎么进行多个不同维度的参数的处理，从而得到一个相对准确的、有意义的数据信息。

最基本的算法就是计算多个数值的加权平均值。适应性更广的算法有卡尔曼滤波和多贝叶斯估计等方法。

在条件允许的情况下，你可以尝试一下这个方法。毕竟 NodeMCU 丰富的 ADC 接口为我们提供了不错的实验条件。比如，你可以基于两个土壤湿度传感器，使用加权平均值算法优化自己的自动浇花器。

小结

总结一下，这一讲中，我介绍了自动浇花器的硬件电路参考设计和代码。你需要关注的主要内容有：

自动浇花器主要是通过检测土壤湿度来判断水分含量，然后基于水分含量来控制水泵电源通断来实现自动浇花的目的。

环境温湿度传感器采用的是 DHT11 模组，它采用单总线的方式与控制芯片通信，这是奥松电子的自定义协议。不过，基于 MicroPython 开发的时候，我们不需要自己开发驱动，可以直接使用 MicroPython 提供的 DHT 库。

联网的开发，我们可以继续基于腾讯云物联网平台来实现。我提供了物模型，希望你可以基于智能电灯的开发经验独立完成这个任务。

多传感器融合是提高决策正确性的重要方法，其中融合算法是最关键的。

在实战篇，我们主要基于开源的开发板硬件来完成智能硬件的开发工作。这在工作中有很大的用处，因为当我们有新的产品想法时，可以快速地进行技术验证，甚至用户反馈。

这也就是最小可行性产品（Minimum Viable Product，MVP）方法论。在软件领域，这种方法比较常见，而基于硬件开发板，我们可以很好地验证智能硬件产品的可行性，为企业节约成本、降低风险。

思考题

最后，我给你留一个思考题吧。

在我提供的物模型里面，你应该可以看到“缺水状态”的属性。它可以用于指示水泵连接的水箱是不是已经缺水了，如果水箱水位不足，就会提醒我们及时给水箱补水。请你想一想，要怎么实现这个水位检测功能呢？请用文字描述一下你的思路。

欢迎你在留言区和我交流，也欢迎你将这一讲分享给你的朋友，大家一起交流学习。

22 | 掌控数据：家里的数据可以怎么利用？

你好，我是郭朝斌。

到目前为止，我们已经完成智能电灯、光照传感器、智能音箱和自动浇花器的实战训练，在这个过程中，我们主要关注的是设备功能和远程控制的实现。

其实，物联网设备会生成大量的数据。如果我们能把这些数据存储到物联网系统的数据库中，并且好好应用这些数据，比如提供查询和分析功能，就能够产出更大的价值。

这一讲，我就基于自动浇花器来讲一讲数据的应用方法，主要包括以下两种：

基于腾讯云物联网平台提供的数据流功能，介绍一个设备消息推送应用的配置方法。

基于腾讯云的 HTTP 方式的数据同步功能，开发一个 Web 数据应用系统。因为需要购买云服务器，所以你可以酌情选择是否实际部署。

方法一：基于数据流的设备消息推送应用

腾讯云物联网平台已经为我们提供了一种简便的数据应用方法。我们可以使用它的可视化编辑界面来完成数据流的创建工作。

你可以登录腾讯云物联网平台的控制台，然后进入我们之前创建的“智能家居”项目，点击左边菜单栏中的“数据开发”。

然后，你需要新建一个数据流，名称可以是“自动浇花器”。点击数据流列表中的“自动浇花器”项目，你就可以进入可视化的编辑界面。

在可视化编辑界面，我们可以看到，一个数据流包括“输入”“处理”和“输出”三个部分。

输入，包括设备数据、设备事件和设备状态三种，其中设备数据和设备事件与物模型中的定义是一致的。设备状态是设备的上线、下线的状态变化。

处理，可以编写基本的判断逻辑来过滤输入数据。

输出，可以作为消息将数据推送到 App 或者小程序中。你可以对消息的内容模板进行定义。

自动浇花器设备会上报环境的温度、湿度信息，那么我们可以定义一个温湿度不适宜的消息提醒。

你可以拖拽“设备数据”到编辑区域，然后点击这个模块，在右边的选项中定义设备数据，产品选择“自动浇花器”，属性选择“环境温度”和“环境湿度”。

接着，你可以添加“数据过滤”模块，并且将这个模块与“设备数据”模块相连，然后点击这个模块，在右边编辑过滤条件。

完成数据过滤的定义后，你需要继续添加“公众号推送”模块，并且编辑消息推送的模板。具体的消息模板定义，你可以参考下面内容：

环境温度、湿度不适宜：
当前温度是$env_temp
当前湿度是$env_hum
当前时间是$timeStamp

最后，点击页面上方的“保存”和“启用”，你就完成了数据流的定义。

如果你的自动浇花器设备是在线状态，那么当环境的温度或者湿度过高、过低时，你就会在腾讯连连小程序的消息列表中收到“告警”消息。

方法二：基于 HTTP 数据同步的 Web 数据应用

物联网平台除了提供数据流的方式，还可以基于 HTTP 协议把数据推送到你指定的网址，比如你开发的 Web 服务器的网址。所以你也可以使用这种方式，更加灵活地利用物联网设备的数据。

数据同步体验

我们借助在线的 Webhook 服务，测试一下数据同步功能。

首先，打开webhook.site网站，并且记录页面中显示的专属 URL 地址。

然后，你需要登录腾讯云物联网平台，进入我们之前创建的“智能家居”项目，再点击左边菜单栏中的“数据同步”，选择“HTTPS”标签。

接着，点击“自动浇花器”对应的“设置”链接，在设置窗口，将 webhook 网站获取到的 URL 地址，粘贴在输入框。

完成设置后，打开“生效状态”。

在保证自动浇花器设备正常运行的情况下，你就可以在 webhook 网站的页面中看到设备上报的数据了。

下面我来介绍一下用 Python 语言来开发 Web 服务器的方法。

因为购买云服务器需要费用，所以这个实战任务是选学内容，你可以在自己的电脑上实践一下 Web 服务器的开发过程，然后酌情考虑要不要部署到云服务器上。

准备 Django 开发环境

Django 是一个流行的基于 Python 语言的 Web 开发框架。它提供了强大的功能，同时也简单易用。接下来，我们就基于 Django 来实现一个 Web 应用程序。

首先是在电脑上配置 Django 的开发环境，在终端上运行下面的命令安装 Django，准备好需要用到的工具。

$ pip3 install django

安装完成后，你可以在终端上切换到一个代码开发目录，比如：

$ cd ~/study/iot/geektime

然后，直接使用 Django 提供的脚手架工具 django-admin 来创建一个项目：

$ django-admin startproject watering_web

这时，命令会创建一个名称为 watering_web 的目录。目录中包含 manage.py、settings.py、urls.py、wsgi.py 和 asgi.py 等几个项目“骨架”文件，其中：

manage.py 是项目管理脚本，比如创建子应用、运行开发服务器等都可以通过它实现。

settings.py 是项目的整体配置文件。比如子应用的配置、数据库的配置等。

urls.py 是项目的全局路由声明文件。

wsgi.py 是 WSGI（Python Web Server Gateway Interface 的缩写）服务接口文件，是整个 Django 应用的调用入口。

asgi.py 是 ASGI（Asynchronous Server Gateway Interface 的缩写）服务接口文件。ASGI 是 WSGI 的替代者，它增加了异步应用的能力。除了支持 WSGI 协议，同时对 Websocket 和 HTTP2.0 这些长连接方式的协议提供了支持。

现在，我们进入 watering_web 目录，运行下面的命令，就可以在电脑上启动这个项目的开发服务器了。

$ python manage.py runserver

这非常有利于你的开发调试工作。比如，你可以在浏览器输入 http://127.0.0.1:8000/ 随时访问 Web 应用的开发效果。

当你在浏览器上看到这个界面时，就说明 Django 应用的开发环境已经准备好了。下面，我们就来开发 Web 应用。

Django 应用开发

创建子应用 watering

首先，我们通过运行下面的命令，创建 watering_web 项目的子应用 watering：

$ python manage.py startapp watering

这时，你应该可以看到项目目录下，新增加了 watering 的目录。目录中包含了 watering 子应用的基本代码模块。

admin.py 是后台管理应用的配置文件，我们可以在其中增加数据库模型对象，让 Django 应用管理员能通过后台管理页面进行编辑。你在浏览器输入http://127.0.0.1:8000/admin/可以访问到后台管理应用。

apps.py 是子应用的配置文件。

migrations 是数据库迁移的文件目录，它下面会保存每次数据库迁移的中间文件。

models.py 是定义子应用的数据库数据模型文件。

tests.py 是子应用的单元测试代码文件。

views.py 是子应用的视图代码文件。

理解 Django 的视图调用

在 Django 里面，网页或者说 HTTP 的响应都是视图生成的，每个视图对应 views.py 中的一个函数。Django 会根据 HTTP 请求的 URL 域名地址来选择相应的视图，而 URL 和视图函数的映射关系是在 urls.py 文件中定义的。

比如，你可以打开 watering 目录下的 views.py 文件，添加下面的函数：

from django.http import HttpResponse

def demo(request):
    return HttpResponse('Hello World!')

然后，在 watering 目录下，新建一个 urls.py 文件，文件内容如下：

from django.urls import path

from . import views

urlpatterns = [
    path('demo', views.demo, name='demo'),
]

最后，在 watering_web 目录下的项目全局路由文件中，将 urlpatterns 的内容替换为下面的内容：

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('watering.urls')),
]

现在，你在浏览器输入地址http://127.0.0.1:8000/demo，就可以看看我们刚刚定义的 demo 视图的内容。

应用代码开发

接下来，我们正式开始子应用的开发。

首先，我们需要定义数据库的数据模型。Django 框架实现了 ORM（Object-Relational Mapping，对象关系映射器）技术。你可以直接在 Python 代码中定义数据库的表结构。

基于这个定义，Django 的 migration 工具能自动在数据库中创建相应的数据库表。在后面的部署阶段，我会讲解到具体的 migration 命令。

我把 models.py 的代码贴在文稿中，供你参考：

from django.db import models

# Create your models here.
class Watering(models.Model):
    seq_no = models.IntegerField(blank=False, null=False)
    device_name = models.CharField(max_length=64, blank=False)
    product_id = models.CharField(max_length=64, blank=False)
    power_switch = models.IntegerField(default=0)
    humidity = models.IntegerField(default=0)
    env_temp = models.FloatField(default=0.0)
    env_hum = models.IntegerField(default=0)
    env_illum = models.IntegerField(default=0)
    timestamp = models.DateTimeField(auto_now=False)

    def __str__(self):
        return self.device_name + '_' + str(self.seq_no)

有了数据模型之后，你就可以在 views.py 文件中开发视图，接收腾讯云物联网平台推送的 HTTP 请求，并且将数据存储到数据库中。

文稿中是我的示例代码，供你参考：

from django.shortcuts import render

# Create your views here.
from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt
from django.forms.models import model_to_dict
import json
from datetime import datetime
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.iotexplorer.v20190423 import iotexplorer_client, models

SECRET_ID = "你的Secret ID"
SECRET_KEY = "你的Secret Key"
PRODUCT_ID = "你的ProductID"

from .models import Watering

def build_response(resp, msg):
    dict = {
        "resp": resp,
        "msg": msg,
    }
    return HttpResponse(json.dumps(dict), content_type='application/json')

def demo(request):
    return HttpResponse('Hello World!')

@csrf_exempt
def data_sync(request):
    if request.method == 'POST':
        json_data = json.loads(request.body.decode())
    else:
        return HttpResponseBadRequest('Bad Request')

    if 'seq' in json_data:
        _seq = json_data['seq']
    if 'devicename' in json_data:
        _device_name = json_data['devicename']
    if 'productid' in json_data:
        _product_id = json_data['productid']
    if 'timestamp' in json_data:
        _timestamp = json_data['timestamp']
    if 'payload' in json_data:
        _payload = json_data['payload']
        if 'method' in _payload and 'report' == _payload['method']:
            _params = _payload['params']
            _env_temp = _params['env_temp']
            _env_hum = _params['env_hum']
            _env_illum = _params['env_illum']
            _humidity = _params['humidity']
            _power_status = _params['power_switch']

            try:
                Watering.objects.create(
                    seq_no=_seq, device_name=_device_name, product_id=_product_id,
                    power_switch=_power_status, humidity=_humidity,
                    env_temp = _env_temp, env_hum = _env_hum, env_illum = _env_illum,
                    timestamp=datetime.fromtimestamp(_timestamp) )

            except:
                return HttpResponse('Insert Failed!')

    return HttpResponse('OK')

@csrf_exempt
def latest_data(request):
    if request.method == 'POST':
        json_data = json.loads(request.body.decode())
    else:
        return HttpResponseBadRequest('Bad Request')

    if 'devicename' in json_data:
        _device_name = json_data['devicename']
    else:
        _device_name = None

    if _device_name is not None:
        try:
            data = Watering.objects.filter(device_name=_device_name).latest('timestamp')
            data_dict = model_to_dict(data, fields=['seq_no','device_name','product_id','power_switch','humidity','env_temp','env_hum','env_illum'])
            data_dict['timestamp'] = str(data.timestamp)
            dict = {
                "resp": 0,
                "msg": "OK",
                "data": data_dict
            }
            return HttpResponse(json.dumps(dict), content_type='application/json')
        except Exception as e:
            return build_response(1, str(e))
    else:
        return build_response(1, 'Parameter invalid.')

为了能够控制自动浇花器的水泵打开和关闭，你也可以增加发送控制命令的视图。同样，你可以参考下面的代码：

@csrf_exempt
def control_device(request):
    if request.method == 'POST':
        json_data = json.loads(request.body.decode())
    else:
        return HttpResponseBadRequest('Bad Request')

    if 'devicename' in json_data:
        _device_name = json_data['devicename']
    else:
        _device_name = None
    if 'status' in json_data:
        _status = json_data['status']
    else:
        _status = None

    if _device_name is not None and _status is not None:
        try:
            cred = credential.Credential(SECRET_ID, SECRET_KEY)
            httpProfile = HttpProfile()
            httpProfile.endpoint = "iotexplorer.tencentcloudapi.com"

            clientProfile = ClientProfile()
            clientProfile.httpProfile = httpProfile
            client = iotexplorer_client.IotexplorerClient(cred, "ap-guangzhou", clientProfile)

            req = models.ControlDeviceDataRequest()
            data = {
                "power_switch": _status
            }
            data_str = json.dumps(data)

            params = {
                "DeviceName": _device_name,
                "ProductId": PRODUCT_ID,
                "Data": data_str
            }
            req.from_json_string(json.dumps(params))

            resp = client.ControlDeviceData(req)

            return build_response(0, resp.to_json_string())

        except TencentCloudSDKException as err:
            return build_response(1, str(err))

    else:
        return build_response(1, 'Parameter invalid.')

完成视图的开发后，你需要更新 urls.py 文件中的 urlpatterns，为新增视图添加映射关系。

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('geektime/', include('watering.urls')),
]

同时，在 watering 子应用的目录增加一个 urls.py 文件，内容如下：

from django.urls import path

from . import views

urlpatterns = [
    path('demo', views.demo, name='demo'),
    path('data', views.data_sync, name='data_sync'),
    path('control', views.control_device, name='control_device'),
    path('fetch', views.latest_data, name='latest_data'),
]

然后，我们来开发一个简单的网页，实现设备信息的显示和控制。为了便于理解，我这里只使用最基础的方法。当熟悉基本原理之后，你可以尝试使用 VUE、React 等前端应用框架来开发功能更丰富的 Web 前端应用。

我把代码贴在文稿中，供你参考：

<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta content="webkit" name="renderer">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
<meta content="IE=Edge,chrome=1" http-equiv="X-UA-Compatible">
<style type="text/css">
    th {
    text-align: left;
    }
</style>

<title>Plant Watering</title>
<script src="jquery-3.1.1.min.js"></script>
</head>

<body>
    <h1>Plant Watering</h1>

    <table>
        <tr>
            <th>Device Name: </th><th id='devicename'></th>
        </tr>

        <tr>
            <th>Date: </th><th id='datetime'></th>
        </tr>

        <tr>
            <th>Soil Moisture (&#37;): </th><th id='moisture'></th>
        </tr>

        <tr>
            <th>Environment Temperature (℃): </th><th id='envtemp'></th>
        </tr>

        <tr>
            <th>Environment Humidity (&#37;): </th><th id='envhum'></th>
        </tr>

        <tr>
            <th>Environment Illumination (Lux): </th><th id='envillum'></th>
        </tr>

    </table>

    <button id='on'>Open</button>
    <button id='off'>Close</button>

    <script type='text/javascript'>
        var timer = false
        var interval = 5*1000   //5 seconds
        var device_name = 'Watering_1'

        $("#on").on('click',function(){
            control(1)
        })
        $("#off").on('click',function(){
            control(0)
        })

        function control(state){

            var payload = {
                "devicename":device_name,
                "status":state
            }

            $.ajax({
                url : "http://159.75.214.14/geektime/control",
                contentType: "application/json; charset=utf-8",
                method : 'POST',
                dataType: "json",
                data: JSON.stringify(payload),

                success:function (obj) {

                    if(obj.resp === 0){
                        console.log("pump on");
                    }
                    else{
                        console.log(obj.msg);
                    }
                },
                error:function(e){
                    console.log("control device post failed.");
                }
            })
        }

        function update_data(){
            var payload = {
                "devicename":device_name,
            }

            $.ajax({
                url : "http://159.75.214.14/geektime/fetch",
                contentType: "application/json; charset=utf-8",
                method : 'POST',
                dataType: "json",
                data: JSON.stringify(payload),

                success:function (obj) {

                    if(obj.resp === 0){
                        console.log("fetch success");
                        $("#devicename").text(device_name)
                        $("#datetime").text(obj.data.timestamp)
                        $("#moisture").text(obj.data.humidity)
                        $("#envtemp").text(obj.data.env_temp)
                        $("#envhum").text(obj.data.env_hum)
                        $("#envillum").text(obj.data.env_illum)
                    }
                    else{
                        console.log(obj.msg);
                    }
                },
                error:function(e){
                    console.log("fetch latest data failed.");
                }
            })
        }

        window.onload = function(){
            update_data()   //Fetch data to get devicename first.
            timer = setInterval(function(){
                update_data()
            }, interval)
        }
    </script>
</body>
</html>

对于这些前端代码文件，你可以在 watering_web 项目的根目录下创建一个 web 目录，然后把它们放入这个目录下。

关于 Django 项目的 settings.py 文件，你需要在 ALLOWED_HOSTS 中增加自己服务器的 IP 地址。

ALLOWED_HOSTS = [
    '159.75.214.14',    #替换自己的云服务器IP
]

同时，在数据库的配置中，修改为 MySQL 的配置内容：

DATABASES = {
    # 'default': {
    #     'ENGINE': 'django.db.backends.sqlite3',
    #     'NAME': BASE_DIR / 'db.sqlite3',
    # }
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'geektime',
        'USER': 'root',
        'PASSWORD': 'geektime',
        'HOST': '127.0.0.1',
        'PORT': '3306',
    }
}

Django 应用部署

完成 Django 应用的开发，我们就可以把它部署到云服务器中。

首先，你需要先登录到腾讯云控制台，从“云产品”中点击选择“云服务器”，进入云服务的配置页面。

在云服务器页面，点击“新建”，购买一台服务器。

在新建的页面，你可以根据自己的需求选择服务器的硬件配置。如果只是用来练习，选择最低的配置就行了。

服务器的操作系统选择“Ubuntu Server 16.04.1”，其他选项保持默认值。

点击“立即购买”，完成支付后，我们重新进入云服务器控制台，就可以看到我们的服务器新实例了。

准备好云服务器后，我再介绍一下 Django 应用在 Ubuntu 服务器上的具体部署操作。

以下的介绍都是针对单台服务器部署展开的。如果多台机子部署，你需要做相应的调整，比如使用腾讯云的 CDB（云数据库服务），你就需要对 Django 应用的设置文件（settings.py）中数据库部分作修改。

Ubuntu 准备

首先，通过 SSH 登录到 Ubuntu 服务器，像连接树莓派一样，你仍然可以使用 Putty 或者 SecureCRT 这样的终端软件。

服务器的 IP 地址可以从腾讯云的云服务器控制台获取。关于用户名和密码，“腾讯云助手”公众号会在购买云服务器时发送消息通知。如果没有收到，你可以在控制台重置密码。

登录后，首先需要更新 apt，你可以运行下面的命令：

$ sudo apt-get update

代码上传

接着，你需要将 Django 应用的代码上传到服务器某个目录下，比如 /home/ubuntu/iot/ 这个目录下。这涉及到下面介绍的 Nginx 和 uWSGI 的配置文件中的路径，因此，如果代码的路径不是这个，你需要相应地修改这些配置中的路径。

上传的工具，你还是可以使用第 19 讲中提到的 FileZilla 等软件。

数据库

我们使用的 MySQL 数据库，可以通过以下命令来安装：

$ sudo apt-get install mysql-server mysql-client libmysqlclient-dev

说明一下，如果没有安装 libmysqlclient-dev 的话，接下来安装 mysql-python 的步骤可能会报错。

安装过程中，终端也提示你输入 MySQL 数据库的密码，你可以像我一样，输入“geektime”。

安装完成后，你可以通过下面的命令，连接上数据库。

$ mysql -p -u root

然后，在数据库的交互命令行中，你需要输入下面的命令，创建 geektime 数据库。

create database geektime;
exit;

Python 环境

接下来，我们需要配置 Python 语言环境，因为应用程序和 Django 应用框架都是基于 Python 的。

首先，我们修改一下系统的默认 python，将它修改为 python3 版本。运行下面的命令：

$ vim ~/.bashrc

在文件中，增加下面的内容：

alias python='/usr/bin/python3'

添加完成后，退出 vim。在终端输入下面的命令，使其生效：

$ source ~/.bashrc

接着，我们需要安装 pip ，Python 包管理器（Python package manager）。你可以运行下面的命令：

$ sudo apt-get install python3-pip
$ sudo pip3 install --upgrade pip

接着，你还需要安装 python-dev ，一些软件包的安装需要依赖它。命令如下：

$ sudo apt-get install python3-dev

后面，你需要进入 Django 应用目录（我们这里是 /home/ubuntu/iot/watering_web），然后运行下面的命令，安装所有依赖的软件包。：

pip3 install -r requirements.txt

如果出现 locale.Error: unsupported locale setting 的错误，请在命令行输入下面的命令：

export LC_ALL="en_US.UTF-8"

requirements.txt 文件的内容如下：

tencentcloud-sdk-python==3.0.313
mysqlclient
Django

现在，环境已经准备就绪，下面我来讲解一下数据库和 uWSGI、Nginx 的配置。

数据库 Migrate

在 Django 中，数据库的创建已经处理得非常简单，框架本身做了很多的工作。

首先，在 manage.py 文件所在的目录，运行下面的命令：

$ python manage.py makemigrations

接着，只需要运行下面的命令，就可以完成所有的数据库表创建工作。

$ python manage.py migrate

确认测试

现在，你可以执行下面的命令，启动 Django 应用。然后，你可以通过浏览器确认 Django 应用是否可以正常运行。

$ python manage.py runserver 0.0.0.0:8080

下面，我们就可以安装 uWSGI 和 Nginx 了。

uWSGI 安装

uWSGI 相当于是 Django 应用和 Nginx 之间的桥梁，它使用标准的 WSGI 接口与应用通信。你需要运行命令，安装 uWSGI：

$ sudo apt-get install -y uwsgi
$ sudo apt-get install uwsgi-plugin-python3
$ pip3 install uwsgi

然后，在 /home/ubuntu/iot/config 目录下为 uWSGI 增加配置文件，文件内容如下:

[uwsgi]
socket = 127.0.0.1:3031
chdir = /home/ubuntu/iot/watering_web
wsgi-file = watering_web/wsgi.py
processes = 4
plugins = python3
threads = 2
stats = 127.0.0.1:9191

接着，运行下面的命令，查看 uWSGI 是否可以正常运行。

$ sudo uwsgi --ini ./config/uwsgi.ini

之后，我们还需要配置基于 systemd 的 uWSGI 的自启动流程。这需要创建 systemd 的 unit 文件。我们还是在 /home/ubuntu/config 目录下增加 uwsgi.service 文件，内容如下：

[Unit]
Description=uWSGI
After=syslog.target

[Service]
User=ubuntu
ExecStart=/usr/bin/uwsgi --ini /home/ubuntu/iot/config/uwsgi.ini
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all

[Install]
WantedBy=multi-user.target

这时，你还不需要添加这个 service，在后面我们会通过命令添加、执行这个配置文件。

安装 Nginx

首先，你需要执行下面的命令，安装 Nginx 软件。

$ sudo apt-get install nginx

然后，我们需要配置它。我们通过新添加文件，来配置我们的服务，在 /etc/nginx/conf.d/ 目录下，增加 iot.conf 文件，文件的内容如下:

server {
    listen 80;
    #listen [::]:80 default_server;
    server_name 159.75.214.14; #替换为自己服务器IP地址

    location / {
        root /home/ubuntu/iot/watering_web/web/;
        index index.html;
    }

    location ^~ /geektime/ {
        include uwsgi_params;
        uwsgi_pass 127.0.0.1:3031;
    }

    error_page 404 /404.html;
    location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    }
}

并且，将 /etc/nginx/nginx.conf 文件中的“user www-data;” 修改为“user ubuntu;”。

现在，你需要执行下面的命令检查 Nginx 配置文件的语法：

$ sudo nginx -t

如果没有错误，就可以重启 Nginx，来加载新的配置文件：

$ sudo service nginx restart

然后，把 uWSGI 的 service 配置文件拷贝到 systemd 配置目录下：

$ sudo cp uwsgi.service /etc/systemd/system/

现在，你可以执行下面的命令，启动 uWSGI 服务：

$ sudo systemctl start uwsgi

如果一切正常，我们就可以把 uWSGI 添加到开机自启动中：

$ sudo systemctl enable uwsgi

到这里，服务就部署完毕了。

云平台数据同步 URL 更新

云服务器上的 Web 应用运行正常后，你可以对腾讯云物联网平台上的 HTTP 数据同步进行更新。修改配置中的 URL 地址为自己云服务器的视图地址。

更新完成后，你就可以在浏览器上访问自己的服务器地址，查看自动浇花器的实时数据，并且控制它进行浇水。

小结

总结一下，在这一讲中，我围绕自动浇花器讲解了智能家居系统中设备数据的应用系统开发方法。主要的内容有：

腾讯云物联网平台为我们提供了简单易用的数据开发方法。基于可视化界面，我们编辑“输入”，“处理”和“输出”就可以完成一个数据流的创建，实现数据的过滤和消息推送。

我们可以基于物联网平台的数据同步能力，比如 HTTP 推送，实现物联网平台和应用服务器的对接。在应用服务器上，我们可以灵活地开发数据应用系统。

Django 是非常流行的，基于 Python 语言的 Web 应用开发框架。工作中，你可以使用 Django 比较快速地实现一个 Web 应用系统。

在这一讲中，我们的数据应用系统只包含了自动浇花器的数据，你在时间允许的情况下，也可以尝试在这个系统中增加一下智能电灯和光照传感器的数据。

另外，物联网平台的数据同步也提供了 CKafka（Cloud Kafka）的数据转发方式。通过订阅 Topic 主题消息，我们可以消费 Kafka 的消息，也可以直接将 Kafka 消息转储到 MySQL 云数据库中。

不过，Kafka 的费用比云服务器要贵不少，这里就不介绍了。如果你的条件允许，也可以动手实践一下这种方式。

这里，我整理了一个思维导图，供你参考：

思考题

最后，我给你留一个思考题吧。

在 Django 应用开发的介绍部分，我提到了 admin.py 是后台管理应用的配置文件，而且访问http://127.0.0.1:8000/admin/地址，就会出现管理员登录页面。你知道在 Django 项目中如果创建管理员账号吗？另外，如何在 admin.py 文件中增加 Watering 数据库模型对象，实现对自动浇花器监测数据的查询呢？

欢迎你在留言区写一写自己的思考，同时，也欢迎你将这一讲分享给对 Web 应用开发感兴趣的朋友，大家一起交流学习。

结课测试 | 这些物联网的问题，你都掌握了吗？

你好，我是郭朝斌。

到这里，《物联网开发实战》这门课程已经全部结束了。我给你准备了一个结课小测试，来帮助你检验自己的学习效果。

这套测试题共有 20 道题目，有单选题，也有多选题，满分 100 分，系统自动评分。

还等什么，点击下面按钮开始测试吧！

最后，我为你准备了调查问卷，题目不多，希望你可以花两分钟填一下。十分期待能听到你的反馈，说说你对这门课程的想法和建议。

加餐二 | 行业应用：物联网的发展将如何重塑我们的生活？

你好，我是郭朝斌。

在之前的课程中，我们已经系统地学习了物联网的理论知识，并且动手完成了一系列的实战项目。我想，你已经对物联网有了一个比较全面的了解。

但是，从了解到灵活应用，我们还有很长的路要走。这个过程你既需要积累丰富的经验，也需要有一定的想象力。

今天，我就试着为你展望一下，物联网的发展在不久的未来，将怎样重塑我们的生活。

以下故事如有雷同，实属厉害。

起床

“小特，早上好，现在是早上 7:05，根据你的监测数据，现在是起床的最佳时机。接下来，我会为你播报今天的热点资讯…….”

智能家庭管家甜美的声音伴随着轻柔的音乐，从床头一个类似闹钟的设备中发出。

所谓的监测数据，是从床垫和枕头的传感设备上采集的生理数据。基于脑波扫描数据和身体姿势的变化，智能家庭管家可以分析出人的睡眠周期次数，和每个周期中所处的睡眠阶段。它会选择在睡眠周期中最浅的阶段将人叫醒，既能让起床不那么痛苦，也能让起床之后的精力更充沛。

故事的主人公是小特，他的大名叫司马特。他一毕业就来到北京工作，成为一名“北漂”，算算日子，已经整整 3 年了。今天是星期五，上午十点有一个项目会议，绝对不能迟到，所以他起床之后，马上就开始洗漱了。

洗漱

司马特使用的是昨天快递刚送到的一款新牙刷，这款牙刷号称有很多智能功能，不但可以指导正确的刷牙动作和步骤，还可以通过微型摄像头分析牙齿和牙周的健康状况。这是司马特的同事艾欧媞媞推荐的，他马上就买了。

刷牙的时候，梳妆镜已经获取到牙刷的数据，在镜面上显示出了动作提示。司马特感慨：这款牙刷果然很智能。因为他之前刷牙很少关注的上颌的后槽牙，被重点关注到了，梳妆镜提示他多刷了 1 分钟。

刷牙完毕，梳妆镜上展示出了司马特的口腔健康报告：没有大问题，但是牙结石比较多，建议安排洗牙，否则会影响到牙龈的健康。

司马特直接说道：“帮我预约一个牙科诊所吧。”

梳妆镜传出家庭管家那熟悉的甜美声音：“好的，我查询到你工作的办公楼附近 200 米有间诊所，能够预约的最早时间是今天下午 4 点，请问可以吗？”

司马特知道那间诊所，确实挺方便的，所以他说：“没问题，记得帮我提交一个请假申请。”

智能家庭管家给出肯定答复后，开始继续播报热点资讯。

早餐

司马特到了厨房，拿起已经准备好的咖啡、烤吐司和煮鸡蛋来到餐桌上开始享用早餐。喝咖啡已经成为他的习惯，如果哪天没有喝，他就会觉得身体无法调整到 100% 的状态。

这些餐食并不是别人帮忙做的，而是司马特的早餐机完成的。当床垫将他起床的消息“告诉”智能家庭管家时，管家已经安排早餐机开始工作了。司马特只需要在前一天晚上把原材料加入机器里就可以了。

不过，这一台早餐机也只能准备这些简单的餐食，想来个煎饼果子或者鸡肉卷是不可能的。这也是司马特唯一不满意的地方，他想过段时间趁着电商节的优惠活动，再换一款功能更高级的。

出门

吃完饭，司马特穿戴齐整，对着门口的立式梳妆镜整理衣服，这时，梳妆镜用开玩笑的语气给他的形象打了 100 分，并且提醒司马特戴好围巾，因为今天的天气预报显示有大幅降温。

司马特在和他的猫咪道别之后就出门了。当他关上门并反锁门锁的时候，智能家庭管家自动切换到了“离家模式”。家里的照明全部自动关闭；监控摄像头的遮蔽罩打开，开始工作；扫地机器人开始清扫房间……

要说提升幸福感最大的智能产品，司马特觉得肯定是智能门锁，一个很重要的原因就是，他再也不用像之前一样，出门想起家里有灯没有关，再跑回去关灯。

而且他出门也不需要带钥匙了，这省了很多麻烦。毕竟作为一个经常丢三落四的人，以前他花在找钥匙上的时间真的不少，现在他连“记得带钥匙”的脑力负担都没有了。家里的门像是有了魔法，对别人是有形的，对他自己是隐形的，不管是指纹、密码，还是其他方式，他总是可以轻松地打开家门。

比如有一次他在外面出差，临时计划有变，出差时间从 3 天变成了 5 天。这可麻烦大了，因为他出差之前并没有给猫咪的喂食器添加足够吃 5 天的粮食，他的手机已经收到了“即将断粮”的消息提醒。好在智能门锁支持远程生成一次性的密码，他联系同事艾欧媞媞到他家去了一趟，给猫咪喂食。

出行

今天确实很冷，因为刮风，体感温度比实际温度还要低。幸好司马特家离地铁站并不远，他走了 5 分钟就到了。司马特拿出手机，贴到闸机的提示区域，闸机打开。与此同时，播放音乐的耳机里传来了地铁站欢迎的话。

虽然媒体介绍过，但是两年前当他第一次从耳机中听到“欢迎来到井湾站，我是你的服务专员小萌”的时候，内心还是非常激动的，一句“你好”脱口而出。

那个声音继续说道：“你好，这里是地铁 5 号线和 7 号线的交汇站，站内区域分布比较复杂，请问你要去哪里？我可以为你做站内导航。”

这个站内导航服务让司马特不禁竖起大拇指，它不仅可以准确地判断出司马特所处的位置，而且语音指示详细易懂，最重要的是，从导航提示的位置上车，车厢里人很少，还有几个空位可以选择。

今天，在简单地和司马特确认过目的地是否还是科技城之后，导航就又开始服务了。他在站台上没有等多久，地铁就到了，他选了一个靠近门口的座位坐下。

远程逗猫

结束了一个上午的忙碌工作后，司马特和几个同事一起吃午餐。午餐后是他每天的固定活动，远程逗猫。

司马特拿出手机，打开猫咪喂食器 App，开始呼唤猫咪。这时他家中的喂食器上，屏幕开始显示他的远程视频影像，扬声器也同时发出他的声音。猫咪来到喂食器的镜头前，很认真地看着屏幕，时不时做几个伸展动作。

司马特通过远程控制激光红点，和猫咪开始互动。猫咪随着红点左扑右跳，身形非常敏捷。十分钟后，司马特不舍地结束了逗猫，开始下午的工作。

看牙

下午 3 点半，司马特的手机收到了智能家庭管家发来的消息，提醒他 4 点去洗牙。这时，他才想起早晨预约牙科诊所的事情。幸好有家庭管家，否则他肯定会忘记。

于是司马特收拾好自己的东西，背上背包，离开公司。到达诊所时，距离 4 点还有 5 分钟。他来到前台登记，前台人员给了他一个手环。

当司马特戴上手环，来到诊室通道，远远就看到了他的名字出现在诊室门口的显示屏上。当他进入诊室后，医生的显示屏自动地弹出他的基本信息。经过他的授权，医生访问到了他的口腔相关的医疗档案记录。

数字化的医疗档案记录是司马特本人所有的加密信息，它包含有完整的医疗记录，存储在云端。只有经过他的授权，医生才可以获取到部分信息。这些信息在所有医院之间都是相互认可的。

洗完牙，医生根据司马特的智能家庭管家提供的饮食信息，给他提出了一些饮食建议，建议他进一步减少甜食的摄取，适量增加纤维食物的比例。司马特谢过医生，来到前台归还手环。基于手环的记录，屏幕上显示出本次医疗的费用明细。司马特确认后，刷脸完成了支付。

回家

离开诊所后，司马特前往地铁站回家。回到小区后，他先来到了鲜食货柜区，通过手机扫描取出了自己的蔬菜、水果和面包。这是家庭管家根据他选择的食谱类别，提前一天网购的。

当他刷指纹打开房门时，门廊和客厅的电灯也自动打开了。家庭管家向司马特打招呼：“小特，欢迎回家。”猫咪也跑过来欢迎他。他和猫咪玩耍了一会儿，然后来到厨房，开始准备晚餐。

在做饭方面，他还是一个新手。之前他主要是在外面的餐馆解决晚餐，偶尔也订外卖。今年，他开始尝试自己做饭。在食谱教程的指导下，他现在也能做出一份味道及格的晚餐。

他通过语音指令打开厨房的显示屏，查看今晚的食谱教程。食谱配备了语音讲解，而且可以根据进度适当地等待。一通手忙脚乱之后，司马特总算可以享用晚餐了。

晚餐之后，他将餐具放入自动洗碗机，来到客厅看电视。电视上显示的是这几天他正在追的一部剧，并且询问他是否从上次播放的位置继续。

司马特说：“继续播放。”于是客厅的灯光自动调整到了观影模式，电视开始播放。

看完最新的一集，司马特开始看书。到了晚上十点，智能家庭管家通过语音提示他可以洗漱就寝了。

睡觉

司马特洗漱完，进入卧室。他躺在床上，听着枕头里传出的轻柔的音乐。据说这种音乐有助于快速进入睡眠。

以前司马特是有入睡困难的，躺在床上刷手机，不知不觉就到很晚了。后来，他开始尝试早睡早起。听说看枯燥的书籍有助于睡眠，他也尝试看深奥的专业书籍，但是效果不理想，稍微看一会儿，他就想拿起手机。

再后来，司马特买了这款助眠枕头，它不仅可以助眠，还可以和床垫一起通过传感器收集数据，分析睡眠质量。

伴随着音乐，司马特进入了梦乡。智能家庭管家收到助眠枕头的入睡消息后，切换到了“休息模式”。

小结

总结一下，随着物联网的发展，我们的生活可能发生巨大的变化。当我们考虑生活领域的物联网产品布局时，可以试着从人们最根本的“衣食住行”需求出发，展开思考。

衣：衣物的增减和搭配是我们每天都要关心的日常问题，而借助天气预报、衣物识别等技术的发展，我们可以给出更准确、更贴心的建议。

食：围绕着做饭和吃饭的需求，我们可以用物联网技术做出体验更好的产品和解决方案，比如早餐机、基于现代营养学的食谱推荐和做饭指导方案都是有广泛需求的方向。

住：对于住所，我们总是希望越智能越舒适越好，智能门锁、照明、睡眠提示、娱乐活动和宠物照看等都是非常值得尝试的产品领域。

行：在公共出行的方式上（比如地铁），任何一点改进和优化都会惠及很多人，基于室内精准定位技术的导航应用就是一个这样的创新改进。

思考题

最后是今天的思考题。生活领域的物联网产品创新空间非常大，你不妨也畅想一下有哪些可能的物联网应用吧，说不定就挖掘到了一个很有潜力的产品方向哦。

欢迎你在留言区写一写自己的想法，也欢迎你将这一讲分享给你的朋友，大家一起交流学习。

加餐二 | 行业应用：物联网的发展将如何重塑我们的生活？

你好，我是郭朝斌。

在之前的课程中，我们已经系统地学习了物联网的理论知识，并且动手完成了一系列的实战项目。我想，你已经对物联网有了一个比较全面的了解。

但是，从了解到灵活应用，我们还有很长的路要走。这个过程你既需要积累丰富的经验，也需要有一定的想象力。

今天，我就试着为你展望一下，物联网的发展在不久的未来，将怎样重塑我们的生活。

以下故事如有雷同，实属厉害。

起床

“小特，早上好，现在是早上 7:05，根据你的监测数据，现在是起床的最佳时机。接下来，我会为你播报今天的热点资讯…….”

智能家庭管家甜美的声音伴随着轻柔的音乐，从床头一个类似闹钟的设备中发出。

所谓的监测数据，是从床垫和枕头的传感设备上采集的生理数据。基于脑波扫描数据和身体姿势的变化，智能家庭管家可以分析出人的睡眠周期次数，和每个周期中所处的睡眠阶段。它会选择在睡眠周期中最浅的阶段将人叫醒，既能让起床不那么痛苦，也能让起床之后的精力更充沛。

故事的主人公是小特，他的大名叫司马特。他一毕业就来到北京工作，成为一名“北漂”，算算日子，已经整整 3 年了。今天是星期五，上午十点有一个项目会议，绝对不能迟到，所以他起床之后，马上就开始洗漱了。

洗漱

司马特使用的是昨天快递刚送到的一款新牙刷，这款牙刷号称有很多智能功能，不但可以指导正确的刷牙动作和步骤，还可以通过微型摄像头分析牙齿和牙周的健康状况。这是司马特的同事艾欧媞媞推荐的，他马上就买了。

刷牙的时候，梳妆镜已经获取到牙刷的数据，在镜面上显示出了动作提示。司马特感慨：这款牙刷果然很智能。因为他之前刷牙很少关注的上颌的后槽牙，被重点关注到了，梳妆镜提示他多刷了 1 分钟。

刷牙完毕，梳妆镜上展示出了司马特的口腔健康报告：没有大问题，但是牙结石比较多，建议安排洗牙，否则会影响到牙龈的健康。

司马特直接说道：“帮我预约一个牙科诊所吧。”

梳妆镜传出家庭管家那熟悉的甜美声音：“好的，我查询到你工作的办公楼附近 200 米有间诊所，能够预约的最早时间是今天下午 4 点，请问可以吗？”

司马特知道那间诊所，确实挺方便的，所以他说：“没问题，记得帮我提交一个请假申请。”

智能家庭管家给出肯定答复后，开始继续播报热点资讯。

早餐

司马特到了厨房，拿起已经准备好的咖啡、烤吐司和煮鸡蛋来到餐桌上开始享用早餐。喝咖啡已经成为他的习惯，如果哪天没有喝，他就会觉得身体无法调整到 100% 的状态。

这些餐食并不是别人帮忙做的，而是司马特的早餐机完成的。当床垫将他起床的消息“告诉”智能家庭管家时，管家已经安排早餐机开始工作了。司马特只需要在前一天晚上把原材料加入机器里就可以了。

不过，这一台早餐机也只能准备这些简单的餐食，想来个煎饼果子或者鸡肉卷是不可能的。这也是司马特唯一不满意的地方，他想过段时间趁着电商节的优惠活动，再换一款功能更高级的。

出门

吃完饭，司马特穿戴齐整，对着门口的立式梳妆镜整理衣服，这时，梳妆镜用开玩笑的语气给他的形象打了 100 分，并且提醒司马特戴好围巾，因为今天的天气预报显示有大幅降温。

司马特在和他的猫咪道别之后就出门了。当他关上门并反锁门锁的时候，智能家庭管家自动切换到了“离家模式”。家里的照明全部自动关闭；监控摄像头的遮蔽罩打开，开始工作；扫地机器人开始清扫房间……

要说提升幸福感最大的智能产品，司马特觉得肯定是智能门锁，一个很重要的原因就是，他再也不用像之前一样，出门想起家里有灯没有关，再跑回去关灯。

而且他出门也不需要带钥匙了，这省了很多麻烦。毕竟作为一个经常丢三落四的人，以前他花在找钥匙上的时间真的不少，现在他连“记得带钥匙”的脑力负担都没有了。家里的门像是有了魔法，对别人是有形的，对他自己是隐形的，不管是指纹、密码，还是其他方式，他总是可以轻松地打开家门。

比如有一次他在外面出差，临时计划有变，出差时间从 3 天变成了 5 天。这可麻烦大了，因为他出差之前并没有给猫咪的喂食器添加足够吃 5 天的粮食，他的手机已经收到了“即将断粮”的消息提醒。好在智能门锁支持远程生成一次性的密码，他联系同事艾欧媞媞到他家去了一趟，给猫咪喂食。

出行

今天确实很冷，因为刮风，体感温度比实际温度还要低。幸好司马特家离地铁站并不远，他走了 5 分钟就到了。司马特拿出手机，贴到闸机的提示区域，闸机打开。与此同时，播放音乐的耳机里传来了地铁站欢迎的话。

虽然媒体介绍过，但是两年前当他第一次从耳机中听到“欢迎来到井湾站，我是你的服务专员小萌”的时候，内心还是非常激动的，一句“你好”脱口而出。

那个声音继续说道：“你好，这里是地铁 5 号线和 7 号线的交汇站，站内区域分布比较复杂，请问你要去哪里？我可以为你做站内导航。”

这个站内导航服务让司马特不禁竖起大拇指，它不仅可以准确地判断出司马特所处的位置，而且语音指示详细易懂，最重要的是，从导航提示的位置上车，车厢里人很少，还有几个空位可以选择。

今天，在简单地和司马特确认过目的地是否还是科技城之后，导航就又开始服务了。他在站台上没有等多久，地铁就到了，他选了一个靠近门口的座位坐下。

远程逗猫

结束了一个上午的忙碌工作后，司马特和几个同事一起吃午餐。午餐后是他每天的固定活动，远程逗猫。

司马特拿出手机，打开猫咪喂食器 App，开始呼唤猫咪。这时他家中的喂食器上，屏幕开始显示他的远程视频影像，扬声器也同时发出他的声音。猫咪来到喂食器的镜头前，很认真地看着屏幕，时不时做几个伸展动作。

司马特通过远程控制激光红点，和猫咪开始互动。猫咪随着红点左扑右跳，身形非常敏捷。十分钟后，司马特不舍地结束了逗猫，开始下午的工作。

看牙

下午 3 点半，司马特的手机收到了智能家庭管家发来的消息，提醒他 4 点去洗牙。这时，他才想起早晨预约牙科诊所的事情。幸好有家庭管家，否则他肯定会忘记。

于是司马特收拾好自己的东西，背上背包，离开公司。到达诊所时，距离 4 点还有 5 分钟。他来到前台登记，前台人员给了他一个手环。

当司马特戴上手环，来到诊室通道，远远就看到了他的名字出现在诊室门口的显示屏上。当他进入诊室后，医生的显示屏自动地弹出他的基本信息。经过他的授权，医生访问到了他的口腔相关的医疗档案记录。

数字化的医疗档案记录是司马特本人所有的加密信息，它包含有完整的医疗记录，存储在云端。只有经过他的授权，医生才可以获取到部分信息。这些信息在所有医院之间都是相互认可的。

洗完牙，医生根据司马特的智能家庭管家提供的饮食信息，给他提出了一些饮食建议，建议他进一步减少甜食的摄取，适量增加纤维食物的比例。司马特谢过医生，来到前台归还手环。基于手环的记录，屏幕上显示出本次医疗的费用明细。司马特确认后，刷脸完成了支付。

回家

离开诊所后，司马特前往地铁站回家。回到小区后，他先来到了鲜食货柜区，通过手机扫描取出了自己的蔬菜、水果和面包。这是家庭管家根据他选择的食谱类别，提前一天网购的。

当他刷指纹打开房门时，门廊和客厅的电灯也自动打开了。家庭管家向司马特打招呼：“小特，欢迎回家。”猫咪也跑过来欢迎他。他和猫咪玩耍了一会儿，然后来到厨房，开始准备晚餐。

在做饭方面，他还是一个新手。之前他主要是在外面的餐馆解决晚餐，偶尔也订外卖。今年，他开始尝试自己做饭。在食谱教程的指导下，他现在也能做出一份味道及格的晚餐。

他通过语音指令打开厨房的显示屏，查看今晚的食谱教程。食谱配备了语音讲解，而且可以根据进度适当地等待。一通手忙脚乱之后，司马特总算可以享用晚餐了。

晚餐之后，他将餐具放入自动洗碗机，来到客厅看电视。电视上显示的是这几天他正在追的一部剧，并且询问他是否从上次播放的位置继续。

司马特说：“继续播放。”于是客厅的灯光自动调整到了观影模式，电视开始播放。

看完最新的一集，司马特开始看书。到了晚上十点，智能家庭管家通过语音提示他可以洗漱就寝了。

睡觉

司马特洗漱完，进入卧室。他躺在床上，听着枕头里传出的轻柔的音乐。据说这种音乐有助于快速进入睡眠。

以前司马特是有入睡困难的，躺在床上刷手机，不知不觉就到很晚了。后来，他开始尝试早睡早起。听说看枯燥的书籍有助于睡眠，他也尝试看深奥的专业书籍，但是效果不理想，稍微看一会儿，他就想拿起手机。

再后来，司马特买了这款助眠枕头，它不仅可以助眠，还可以和床垫一起通过传感器收集数据，分析睡眠质量。

伴随着音乐，司马特进入了梦乡。智能家庭管家收到助眠枕头的入睡消息后，切换到了“休息模式”。

小结

总结一下，随着物联网的发展，我们的生活可能发生巨大的变化。当我们考虑生活领域的物联网产品布局时，可以试着从人们最根本的“衣食住行”需求出发，展开思考。

衣：衣物的增减和搭配是我们每天都要关心的日常问题，而借助天气预报、衣物识别等技术的发展，我们可以给出更准确、更贴心的建议。

食：围绕着做饭和吃饭的需求，我们可以用物联网技术做出体验更好的产品和解决方案，比如早餐机、基于现代营养学的食谱推荐和做饭指导方案都是有广泛需求的方向。

住：对于住所，我们总是希望越智能越舒适越好，智能门锁、照明、睡眠提示、娱乐活动和宠物照看等都是非常值得尝试的产品领域。

行：在公共出行的方式上（比如地铁），任何一点改进和优化都会惠及很多人，基于室内精准定位技术的导航应用就是一个这样的创新改进。

思考题

最后是今天的思考题。生活领域的物联网产品创新空间非常大，你不妨也畅想一下有哪些可能的物联网应用吧，说不定就挖掘到了一个很有潜力的产品方向哦。

欢迎你在留言区写一写自己的想法，也欢迎你将这一讲分享给你的朋友，大家一起交流学习。

加餐三 | 行业应用：物联网的发展将如何升级第一、第二产业？

你好，我是郭朝斌。

上一讲中，我们展望了物联网对于生活的重塑作用。这一讲，我们继续畅想一下，物联网将为第一产业和第二产业提供哪些升级的机会。

故事的主人公，还是我们熟悉的司马特。

这个周末有一个物联网的年度展会，司马特早就想好了要去参加，了解一下行业里的最新动态。中午吃饭的时候，他提到了这件事，看看有没有同事想要一块儿去，这样路上也能有个伴，但是很可惜，没有一个人响应。

这也正常，大家都会把周末的娱乐活动安排得满满的，谁会想去参加这种枯燥的展会呢？

于是周六早上，司马特独自一人早早地来到了展会的现场。会场很大，有 4 个展馆，分别是农业展馆、工业展馆、智能家居展馆和云计算展馆。对于智能家居和云计算，司马特比较熟悉，所以他主要是想了解物联网在第一产业和第二产业的发展情况，也好开阔一下自己的眼界。

温室大棚

司马特首先来到农业展馆，远远地就看到一个温室大棚造型的展厅，这吸引了他的注意。他快步来到展厅前，看到大棚中均匀分布的架子上安装着一些有伸出探头的设备，地面上摆着十多盆鲜花，花盆中也插着一些设备。

这时一个展厅的工作人员走过来，跟他打招呼：“你好，我是花都科技的 Annie。可以交换一下名片吗？”

司马特一边说着你好，一边拿出手机交换电子名片。他看了一眼名片信息，以询问的语气说道：“你们公司的业务是种植鲜花吗？”

“哈哈哈！”一阵爽朗的笑声之后，Annie 接着说道：“不是的，我们主要是提供鲜花种植的智能化解决方案。我们的方案集成了各种先进的传感器和无线通信技术，能够采集所有影响鲜花生长的环境数据。”

随后 Annie 指引司马特来到一个架子前，指着上面的设备说：“这个设备可以采集温室大棚里面的温度、湿度、气压和二氧化碳的浓度。基于这些数据，我们的系统可以自动控制温室大棚上的卷帘，这些卷帘覆盖在大棚上，可以通过机械装置实现收起和放下。这些数值也是控制大棚风机通风的重要依据。”

司马特指着架子上的一个摄像头说：“这也是你们解决方案的一部分吗？”

“是的，它可以让种植人员远程查看鲜花的生长情况，比如植株的大小和花苞的发育阶段等。这些影像是不可或缺的判断依据。”Annie 说。

接着，Annie 又指着地上的花盆说：“这里面的设备可以测量土壤里面的水分含量、有机质含量、pH 值和花盆位置的光照度。光是基于土壤湿度的智能化灌溉，我们就可以节约 60% 以上的种植用水。”

“那这些数据是在哪里处理的啊？”司马特问道。

“这些传感器设备是通过 LoRa 无线技术，把数据发送给那个网关设备的。”Annie 指着大棚角落一个盒子说，“网关设备通过 5G 蜂窝网络将数据上传到云端服务器。摄像头是直接通过 5G 网络上传视频数据的。”

他们来到一个显示屏前，屏幕上面的左边显示着各种数据，右边是几个摄像头的实时影像。

Annie 介绍道：“这里是整个大棚的数据。你可以看到很多需要重点关注的数值，比如水分含量和有机质含量等。同时，系统还会为你提供灌溉总量和施肥的建议。这都是云端基于收集到的数据实时生成的。

“根据我们的系统建议，菊花从分化到现蕾的时间可以缩短 5 到 7 天，出产合格商品的比例提高 15% 左右。现代的鲜花种植已经发展到了智能化阶段，过去的种植能手的经验已经转化成了数字化的精确决策。”

听到这里，司马特非常感慨：“原来物联网不仅可以节省人力，而且可以提高经济收益。”

智能养猪

司马特向 Annie 道完谢，继续往前走。他看到一个“智能养猪”的展厅，觉得很新奇，径直走了进去。

展厅内摆着几只可爱的小猪模型，有一个盒子在悬空的轨道上，来回移动。一个大屏幕上显示着各种数据，以及用不同色块标识的小猪的视频影像。

一位名叫 Jason 的工作人员向司马特介绍了这套智能养猪的解决方案。轨道上的盒子是一个物联网设备，它集成了不同的传感器，可以采集猪舍中的温度、湿度、氨气、二氧化碳和光照度等环境指标，也可以识别不同的小猪，完成每一头猪的跟踪、盘点和测重等工作。

“为什么需要特别监测猪舍中的氨气呢？”司马特问道。

Jason 解释说：“猪毕竟是直接在猪舍内排便排尿的，粪便和尿中的含氨物质经过微生物的发酵就会产生氨气。氨气是一种强刺激性的气体。氨气会导致猪的采食量减少，消化能力也会受到影响，这些都会严重影响猪的生长速度。另外，氨气还会导致猪的抵抗力下降，更容易受结核病和其他传染病的影响。”

“那如何自动监测每一头猪的生病情况呢？”司马特继续问道。

“你是问到点上了。”Jason 夸赞道，“就像人一样，主要是监测一些异常的指标。一方面是体温，猪一旦生病，体温往往也会升高，通过红外测温就可以持续测量猪的体温，及时发现异常的猪；另一方面是每头猪的采食量和饮水量，一旦它们食欲不振，我们就会注意到。”

Jason 继续补充道：“及时发现生病的猪并且进行干预和处理，是预防疾病大面积扩散的重要措施。我们国家每年出栏的猪有近 7 亿头，整个养猪产业有万亿元级别的规模。所以，智能化的养猪模式是非常有前景的行业趋势。”

这么大的产业规模是司马特完全没有想到的。他越发觉得，自己来展会了解不同的行业知识是正确的决定。

无人机

司马特再往前走，看见一些无人机摆在一个展厅里面。他有些惊讶，以前自己观赏过无人机的夜空灯光秀，也看过无人机用于快递运输的介绍，却不知道无人机可以用于农业生产呢！

带着疑问，司马特来到了展厅。他询问工作人员：“无人机在农业领域有哪些应用？”

工作人员介绍说：

“无人机的特点就是快速灵活的空中机动能力和一定的有效负载运输能力。所以，它的应用主要体现在两个方面：

“首先，病虫害情况、锯齿类动物的破坏和农作物的长势情况等都是种植业者非常关心的问题。行业内已经有基于卫星遥感影像技术来实现农作物监测的解决方案，但是，无人机可以提供成本更低、更加灵活的方案。无人机安装的高清成像设备，可以对田间进行成像，结合 GIS（Geographic Information System，地理信息系统）技术，还能实现绘图和测量的目的。

“通过对这些数据进行分析，就可以得到农作物 LAI（Leaf Area Index，叶面积指数）等数据，进而分析出农作物的长势和病虫害等危害情况。基于农作物数量的测算和长势情况，系统就可以预测出农作物的产量。

“那另一个方面是什么呢？”司马特迫不及待地问。

工作人员接着说：“我们还利用无人机执行一些原来需要人来完成的工作，比如喷洒农药。这样一来可以减少人力投入，二来也可以减少人工田间作业对农作物的毁坏，三来还可以节约 30% 以上的农药投放。”

司马特道完谢，离开无人机展厅，继续往前走。展馆里面还有一些基于 RFID 标签和蓝牙标签的食品追溯系统。这些追溯系统可以实现从田间到消费者的全链条溯源和记录，从而有效提高供应链的效率和产品质量。不过时间有限，司马特没有停留太久，就匆匆地向工业展馆走去。

机器故障监测

对于工业领域，司马特更加陌生，而且这里展厅的布局也没有农业展馆那样活泼有趣，所以整体来说他的兴趣不是太浓厚。

不过，一想到工业制造是最强调效益的地方，物联网技术应该有不错的应用潜力，所以司马特还是希望获得一些新的认知。

他来到一个显示屏内容比较吸引人的展厅。显示屏上的数字快速地变化着，提示着这台设备的健康指数和待检修的预估时间。司马特找到一个工作人员询问具体细节，接待他的是一位叫 Heidi 的工作人员。

Heidi 指着一个机器模型上的盒子说：“这是一个传感器设备，可以监测机器运转时的震动数据。设备会将这些数据发送到车间里部署的网关设备上。网关设备再将震动数据上传到云服务器。”

“这个好像并不是很难。”司马特随口说道。

“因为工作环境很复杂，传感器需要长时间可靠、准确地测量机器的震动数据。这个难度还是不小的。”Heidi 平静地说，“另外，我们真正的核心在于数据的分析处理。仅仅根据震动数据来实现对机器运转状态评估，这需要在模型和算法上投入很多精力。基于机器学习算法，我们的系统还可以预测机器发生故障的时间，从而提前安排维修保养，实现延长机器使用寿命的目的。”

“原来是这样，看来确实不简单。”司马特表示赞同，同时也会自己刚才的失言感到懊悔。找个机会，他赶紧离开了展厅。

智能制造

司马特再往前走，看到一个围了很多人的展厅。在好奇心驱动下，他也径直走了过去。

一位工作人员正在讲解无人工厂：“智能工厂主要是实现了全面感知、协同优化、预测预警和科学决策；而无人工厂是在这个基础之上，建设全面的工业物联网平台，做到生产流程的全面联网化和自动化。

“生产需要的原材料是由自动导引运输车（Automated Guided Vehicle，AGV）基于 ERP 和 PDM 系统中的生产计划，从库房运输到生产线。生产线完全自动运转，每条生产线之间的衔接也是系统自动协调完成的，不需要人员的干预。

“产品生产完成后，机器人会将货物整齐地码放在自动运输车上，运到库房存放。产品的销售出货过程也是全面自动化的。”

工作人员指引司马特他们来到一块大型的显示屏前，接着说：“整个流程都有传感器和摄像头进行全面监控。这里展示的是现场实景的各种数据，如果有那个工序出现故障，屏幕马上就会显示出来，工厂管理人员可以及时了解情况。整体运营成本可以降低 50% 以上，生产效率至少提高 80%，某些情况下甚至可以提升 2 到 3 倍以上。”

“中国作为世界工厂，在智能工厂和无人工厂领域的发展相当令人期待啊！”司马特一边听着，心里不禁这样想。

不知不觉已经中午了，司马特还有很多感兴趣的展厅没有去，但是他也只能不舍地离开，因为中午他还有一场约会呢！

小结

总结一下，除了智能家居和公共交通等生活领域，物联网技术在农业和工业这些传统的第一、第二产业也有广阔的应用前景。

在农业产业，物联网解决方案可以有效减少人力投入，获得更好的经济收益，比如：

利用传感器和摄像头实现智能化种植的温室大棚。

实现猪舍环境监测和猪只识别的智能养猪解决方案。

利用无人机进行田间影像监测和喷洒农药等创新应用。

在工业产业，物联网技术是企业追求效益的必然选择，比如：

基于传感器数据的机器故障监测和预测性维护应用场景。

智能工厂、无人工厂这些覆盖原材料、生产、库存和销售等环节的全流程数字化、自动化解决方案。

思考题

最后，我给你留一个思考题吧。你还了解农业、工业产业中有哪些物联网应用案例呢？

欢迎你在留言区写一写，也欢迎你将这一讲分享给你的朋友，大家一起交流学习。

加餐四 | 5G技术将如何推动物联网的发展？

你好，我是郭朝斌。

今天我继续给大家讲讲司马特的故事。

这天，通信技术专家 Vincent 来到司马特的公司做 5G 技术讲座，司马特非常兴奋。他上次去物联网展会的时候就注意到，很多物联网系统都用到了 5G 技术，所以他早就想深入地了解一下 5G 了，这次讲座正好是一个机会。

他早早地就来到了报告厅，此时报告厅里还一个人都没有呢。他选了一个前排的座位坐下，等待的过程中，在电脑上搜索、阅读着 5G 相关的文章。

讲座快开始了，报告厅里来的人越来越多，已经有 6、7 成的座位上坐上了人。司马特心想：“看来对 5G 感兴趣的人真是不少啊！”

专家提前 5 分钟到了，在讲台上准备着。时间一到，讲座就正式开始了。

演讲笔记

司马特一边听，一边在电脑上记着笔记。

司马特的笔记之一：5G和物联网

司马特的笔记之二：5G的技术特点

司马特的笔记之三：5G技术创新

司马特的笔记之四：5G标准化组织

问答交流

讲座结束后，司马特心想，这么难得的学习机会，绝对不能放过，于是趁着专家还没有离场，立即走上前去提问：“Vincent 老师，能请教您几个问题吗？”

“叫我 Vincent 就行。”专家笑着说，“没问题，我还有几分钟时间。”

为什么 5G 这么热？

司马特说：“谢谢！请问 5G 现在为什么这么热呢？”其实他心中一直有这个疑问。

Vincent 回答道：“要理解这个问题，你就要看 4G 时代发生了什么。4G 出来之前，很多人认为 3G 已经够用了，手机无非是打电话、发短信和偶尔上网查个网页；但是当 4G 真正到来的时候，我们的生活方式发生了巨大的变化。

“比如，一系列的创新业务开始出现，移动支付取代了钱包、ATM 机，我们用手机叫出租车、导航、骑共享单车，用手机点外卖，用手机看视频、玩游戏。总之，移动互联网改变了生活方式，也创造了很多新产业和新业态。”

“我们看到了通信基础设施发展的具体威力，所以希望 5G 能够做强做大移动互联网，甚至实现万物互联。技术是推动社会发展的根本，我们期待 5G 能够创造更大的价值，推动更多产业的发展。”

毫米波是什么？

“我刚才听您讲 5G 技术创新的时候，提到了一个新的频段，毫米波。请问毫米波到底是什么呢？”司马特接着问。

“毫米波是频率在 30GHz 到 300GHz 的电磁波。现在的蜂窝网络频率主要在 3GHz 以下，虽然这部分频率利用率并不高，但是能分配给 5G 使用的资源并不多，所以 5G 技术只能向高频率发展。不过毫米波因为频率高，波长自然就短，这导致穿透障碍物的能力不强。实践中，我们需要使用多天线波束成形技术来实现稳定的信号收发。”Vincent 解释道。

为什么要用 Polar 码？

“您刚才还提到，5G 会使用新的调制编码，Polar Code。关于这个编码，您能再多说明一下吗？”司马特问道。

Vincent 笑道：“一看你就是个技术控。Polar 码的提出充分说明了数学的重要性。之前的 Turbo 码都没有能达到香农极限，而 Polar 码基于信道极化的概念，在理论上已经可以达到香农极限，并且它的编解码算法的复杂度并不高。”

SDN 和 NFV 的意义是什么呢？

“嗯，具体算法我回去查一下资料学习一下。”司马特说道：“那么 5G 网络架构中，SDN 和 NFV 的意义是什么呢？”

Vincent 介绍说：“这两个技术其实都来自 IT 领域，现在用到了通信领域。SDN 主要是实现了控制和转发的分离，路由决策等控制功能由独立的控制器来统一处理，网络设备只负责具体的转发工作。这样可以通过编程来灵活定义网络功能。

“NFV 是将传统的核心网功能部署到虚拟机上，从而实现硬件资源的高效利用。在 5G 网络架构中，SDN 和 NFV 是结合起来使用的。这样网络可以根据负载情况灵活调度网络资源，实现网络高峰时的横向、纵向扩展。”

如何思考 5G 在物联网中的具体应用呢？

“明白了。最后一个问题，5G 在物联网中有哪些具体应用呢？”司马特说道。

“你需要结合 5G 的发展阶段来考虑。从 2019 年我们国家的 5G 元年开始，起步阶段主要是 eMBB 的应用，也就是传统业务和高网络速率的应用，比如高清视频、云游戏。接着是低时延的渐进式发展，这个阶段有工业制造、自动驾驶、远程医疗等场景。最后是基于 5G 的海量物联网设备的接入和应用。应用场景的创新一定是一个不断探索、验证到正式商用的过程。”Vincent 说道。

低头看了一眼手表，Vincent 接着说道：“你也可以回头看看 4G 时代，新应用的诞生过程，这会有启发的。我必须得走了，否则时间来不及了。”

交流完，司马特满意地离开了报告厅，心里想着：“我对 5G 的理解，终于不是混沌一片了。改天也给小王他们几个科普科普。”。

小结

总而言之，5G 是我们非常关注的通信技术，它蕴含着巨大的产业机会。接下来，我对司马特的笔记中的重点内容做一个梳理：

5G 是物联网发展的重要基础，而物联网也是 5G 最重要的应用场景。5G 的三大应用场景是 eMMB 超高速率移动带宽，uRLLC 超高可靠低时延通信，和 mMTC 超大数量设备接入通信。其中 uRLLC 和 mMTC 都是面向物联网的应用场景。

5G 的特点主要有高速率、高地域覆盖、高连接数、高移动性、低时延和低功耗，这些也是 5G 的技术需求。

5G 的的无线通信技术主要有新型多址技术、新波形技术、大规模天线（Massive MIMO）、新调制编码技术和新频段通信等。

5G 的网络架构也在创新，包括接入网和核心网的功能融合、软件定义网络（SDN）和网络功能虚拟化（NFV）等。

5G 的标准化组织，国际上有 ITU 和 3GPP，国内有 IMT-2020（5G）推进组。

思考题

最后，我给你留一个思考题吧。

你使用过支持 5G 通信的手机吗，有什么使用感受呢？你认为随着 5G 技术的推广，还会有哪些创新应用和产品出现呢？

欢迎你在留言区写一写自己的思考。也欢迎你将这一讲分享给对 5G 技术感兴趣的朋友，大家一起交流学习。

加餐五 | 投身物联网行业，如何做好职业规划？

你好，我是郭朝斌。

物联网是一个新兴行业，我想你选择进入物联网行业之前很可能有这样一个疑问：怎么做好自己的职业规划呢？

别着急，我们的老朋友司马特跟你也面临同样的问题，于是找到了学长求助，我们来看看他的学长是怎么说的吧。

今天是周日，司马特已经有了安排，中午要去拜访一位学长。这位学长在大学时期就是学校的“风云人物”，现在已经成为一家知名公司的中层管理者。司马特非常羡慕和崇拜他，并且把他当成自己的榜样，不断努力着。

司马特和学长是在一次同乡会上认识的，一直保持着联系。不过，平时他们各自忙于工作，虽然在同一座城市，见面的次数却不多。

最近司马特一直在思考下一步的职业发展，但是思路还是不清晰。今天好不容易约了一个饭局，他正盘算着好好请教一下学长。

吃过早餐，司马特开始看书。十点十五分，他听到了智能家庭管家的提醒。穿戴整齐后，司马特离开家，前往地铁站，他需要在十一点半赶到丽泽商城。那个商城离学长的家不远，司马特特意选在了那里，希望给学长省点时间。

十一点半，司马特到了丽泽商城，直接来到约定好的一家湘菜馆。他心里庆幸自己来得早，因为门口已经有人开始取号等待了，要是再晚一会儿，他等待的时间会更久。

很快就叫到了司马特的号码，他进入了餐厅，选了一个靠近墙角、比较安静的座位坐下。

十二点差五分的时候，学长终于到了，坐定后问道：“小特，你来得很早吧？我看外面等号的人很多。”

“还好了，没有太早，我到的时候，正好人还不多。你来点菜吧，我不太擅长。”司马特一边说，一边把菜单递过去。

职业瓶颈

点完菜，学长问司马特：“工作最近怎么样？”

司马特回答说：“最近工作上有些困惑，正想向你请教呢。”

学长示意他说下去，所以他接着说：“我感觉自己最近一年都没有明显的进步，做的事情难度不大，所以也提不起太大的兴趣。我是不是遇到职业瓶颈了？”

学长说：“瓶颈不可怕，天花板才可怕。遇到职业瓶颈有很多种原因，一种原因是自己的能力无法提高了。这在职业的初期比较少遇到，因为只要你肯学习，专业知识总是可以熟练掌握的。再加上现在的学习资源这么丰富，这么方便，学习的门槛更是大大降低了。以我对你的了解，你肯定不是这种情况。”

喝了一口水之后，学长接着说：“还有可能的原因是外部环境导致的，比如行业的趋势变化，影响了公司的业务发展，或者公司业务拓展的速度太慢等等。你们公司今年的业务的发展情况怎么样啊？”

司马特说：“公司今年业务挺好的，老产品销量不错，还开辟了一个新产品线。”

学长继续分析：“所以也不是公司成长的原因。据我了解，你们公司对员工的职业发展规划还是很完善的。那是不是你没有向领导提过自己的升职期望呢？”

司马特有些惊讶：“这怎么好意思主动提呢？上次我们老大问我‘你的职业规划是什么？’，我还不好意思地说‘听领导的安排’呢。”

学长听完，立即说：“这可千万不能不好意思啊！你工作已经有 3 年了吧，而且你的工作成绩很好，其实你已经符合晋升的条件了。但你们老大下面也有十来号人呢，如果你不主动说明自己的诉求，他是很难了解你的想法的。毕竟指标也有限，他不得已降低你的优先级，这种情况也是完全有可能的。”

这时，服务员端上来两道菜。在服务员走后，学长接着说：“而且，这会影响到工作任务的分配。你的工作没有挑战也是自然的事情了。”

司马特点头，表示明白了。

学长说道：“关于职业瓶颈，你也不需要太担心，分析出原因，去解决就是了。在你的职业生涯里面，你肯定还会遇到多次瓶颈，解决之道就是一个个跨过去。真正难突破的是天花板，这在小公司尤其常见。

“小公司层级少、业务小，一旦发展速度不够快，你很容易就会碰到职业的天花板，往往只能通过跳槽才能解决。不过你们公司足够大，已经给你提供了一个很好的平台。从这点说，你一毕业就站在了一个不错的起点。”

司马特感激地说：“我也是当时听取了你的建议，才进入了这个公司。”

学长摆摆手，说道：“我没有什么功劳，这是你努力学习，用你的才能争取来的。”

把握职业节奏

“不过，有一点我要提醒你。”学长接着说道，“你需要把握好自己职业发展的节奏。”

司马特问：“这个节奏要怎么把握呀？”

学长说道：“一个简单的方法是，每 3 年上一个台阶。这个‘3 年’的数字不是定死的，你可能快一点，也可能慢一点，但是它可以提醒你对照自己的职业情况。”

吃了口菜，学长接着说：“时间是最宝贵的，失去了，你就永远也找不回来了。所以，千万不能停滞不前，否则短期内你可能看不出来，但是 5 年、10 年过去你再看，可能已经跟别人拉开了很大的差距。”

司马特说：“确实是这样，那我要抓紧了，争取每年都有突破。”

学长提醒说：“你可千万不要从一个极端到另一个极端啊。每个人的职业生涯差不多都有二三十年，这是一场马拉松，而不是短跑冲刺。初期就是打基础、做好积累，不要太关注职位的提升、薪资的涨幅。否则你就成了揠苗助长，不利于自己职业的长期发展。”

司马特点了点头。

这时，服务员又端上两道菜，说道：“菜上齐了。祝你们用餐愉快。”

先深度后广度

司马特继续请教：“说到打基础，我一直有个疑问。我们上学的时候学得非常全面，计算机软硬件、通信技术、电子电路、传感器技术、网络安全、数据分析，甚至还有控制技术和物流管理等。但是工作中，我感觉好多东西都没有用到，这是不是基础打错了呢？”

学长思考了片刻，说道：“在学校学的主要还是通识性知识。虽然物联网专业的课程涵盖了很多不同的专业，但都只是对基本概念的了解，目的是让你有一个基本的认识。真正的职业基础还是从工作后开始积累的。

“打基础的一个基本原则是先选择一个方向，最好是自己感兴趣的方向，深入钻研下去，把这个方向变成自己的强项。然后再向外扩展自己的能力，也就是‘先深度后广度’。这样的话，你永远有一个根基，学习起其它知识，你也可以触类旁通。

“在拓展广度的时候，你仍然会遇到职业瓶颈。比如，你需要学习项目管理、团队管理、财务、营销等各种和你的专业完全不同的知识，这就需要你不断去突破自己。”

积累通用能力

司马特听完，有点沮丧：“这么多需要学习的东西啊，这怎么学得过来呢？”

学长鼓励道：“这没有你想象的那么难。因为你不一定要把每个技能都学到专家的程度，很多东西只要投入一定的时间，掌握个七七八八就够用了。而且，有些通用的能力，你只要不断积累，就会在各方面受益。”

司马特问道：“有哪些能力是通用的呢？”

学长说：“比方说沟通能力，无论你以后是继续从事技术专业的工作，还是走管理路线，它都非常重要。阐述技术构想的时候，你需要通过沟通来让大家真正理解，从而获得认可和支持。管理团队的时候，你也需要通过沟通来阐述团队目标，协调各种资源。”

司马特说道：“我感觉自己一直比较内向，不擅言辞。”

学长进一步鼓励说：“你放心，沟通能力你肯定可以不断提高。而且，你记住，关于沟通能力更重要的一点是，沟通不只是表达，还包括倾听。有效沟通的前提是听懂了对方，然后才能准确地回应，并且简单、清晰地表达出自己的意图。”

职业相关书籍

司马特听了学长的话，高兴地说：“那我有信心了。之前我只关注技术，以后我会多留意训练这方面的能力。学长你有什么书可以推荐给我学习吗？”

学长回答说：“我也只是工作了十年而已，对职场的认识也不一定全面。像《学习力》《金字塔原理》《麦肯锡工作法》这些讲具体的工作方法的书，我想别人也跟你推荐过了。这样吧，我给你推荐一本跟个人成长有关的书。

“这本书从更宏观的角度介绍了职业生涯的不同阶段，以及做好整个生涯职业规划的方法。书名是《远见：如何规划职业生涯的 3 大阶段》。你回头可以读一下，我相信一定会有收获的。”

司马特立即说：“我回去一定买来读一读。”

接着，他们一边吃饭，又一边聊了聊自行车骑行和家乡的话题。

饭菜吃得差不多了，学长看了一眼手表，说道：“我下午需要带孩子去上击剑课，今天我们只能聊到这里了。下次我们约着一起去骑行吧。”

“没问题。”司马特边说，边掏出手机，打算结账。结果，学长亮出手机，对司马特说已经支付了账单。司马特知道自己又晚了一步，也不再说什么。他们说笑着，一起走出了饭馆。

小结

总结一下，职业生涯是我们大部分人都要经历的一段旅程。在这段并不算短的旅程中，你可以从下面几个方面多思考：

职业瓶颈。意识到职业瓶颈是你在职业生涯中经常要面临的情况，你需要分析原因，并且不断突破。

把握职业节奏。你需要不断对照自己的职业情况，有节奏地不断进取。

先深度后广度。在专业技能上，你需要先关注深度，然后再扩展其它方面的能力。

积累通用能力。有些能力是可以让你在多方面受益的，比如沟通能力，你要更早地关注这类能力的积累。

职业相关书籍。关于职业生涯的规划，你可以读一读《远见：如何规划职业生涯的 3 大阶段》这本书。

思考题

关于职业发展，你有什么困惑吗？欢迎在留言区和我交流，也欢迎你将这一讲分享给你的朋友，大家一起讨论交流。

加餐一 | 这5本关于物联网的好书，值得一读

你好，我是郭朝斌。

《物联网开发实战》这门课程上线之后，很多同学在留言区和交流群提议，请我分享物联网相关的学习资料。我当时答应过你们，说一定会抽时间整理一下，今天终于要兑现这个承诺了。

有同学问我：“朝斌老师，你平时是怎么学习技术的？”我想，最主要的途径还是读书。

书籍有一个很大的优点，那就是它的知识内容组织是成体系的。这非常关键，因为只有体系化的知识，才能帮助你在一开始的时候就打好框架，然后你就只需要在这个基础上“添砖加瓦”就可以了。这一点也是符合大脑认知规律的。

所以今天，我会为你推荐 5 本非常值得读的书，如果你想从事物联网行业，一定不要错过。

1. 每一个人都可以读的物联网科普书：《图解物联网》

我推荐的第一本书是《图解物联网》。这本书适合所有人，就算你不是开发人员，甚至不是物联网从业者，也可以拿来阅读。

因为这本书的特色在于，用大量优美的图形来呈现知识。图形的好处是非常直观，有利于大脑的记忆和理解。所以你可以比较轻松、高效地了解物联网的知识。

不过，你千万不要以为这本书的内容不全面或者非常水。恰恰相反，它的内容很全面，讲解也深入浅出。

它由日本 NTT DATA 集团的河村雅人等人组成的团队共同完成。这么多人协同创作，还能写出行文风格一致，高质量的技术图书，真是非常难得。

物联网行业的工程师阅读这本书也会很有收获，因为它既有物联网架构，也有硬件工程师和嵌入式工程师关注的硬件设备知识，还有云平台工程师关注的服务器和数据分析等内容。甚至就连机器人它也有介绍，我想极客们大概会非常感兴趣。

2. 最适合物联网专业学生和开发人员的书：《从创意到原型：物联网应用快速开发》

我推荐的第二本书是《从创意到原型：物联网应用快速开发》。它的内容是按照教材的方式组织的，非常适合物联网专业的学生和开发人员来学习。

这本书为学生考虑这么周到，正是因为它出自教授之手。它的作者是浙江大学的董玮教授和高艺副教授。

书里的每一章都配有习题，所以你学习完一章内容之后，就可以通过习题检测和巩固学习效果。这非常有利于知识的掌握。

除了习题的设计之外，这本书对物联网硬件和物联网操作系统也做了很多有价值的总结和介绍。更重要的是，它还介绍了作者团队打造的 TinyLink 系统。虽然你不一定使用这个系统，但是了解 TinyLink 的设计思路还是有利于你在工作中的实践的。

另外，作者他们的实验室，物联网系统与网络实验室，一直专注于物联网方向的研究。经常关注他们的研究成果和相关论文，对你的学习和工作都是有帮助的。

3. 物联网架构师和技术领导者不可错过的书：Internet of Things for Architects

我推荐的第三本书是Internet of Things for Architects，它非常适合物联网行业的从业者，尤其是物联网架构师和技术领导者。

因为它对物联网技术体系覆盖得非常全面，不仅总结了物联网的整体架构，而且详细阐述了各种具体技术。可以说它是广度和深度兼备。

比如通信技术，无论是蓝牙、ZigBee、Z-Wave、6LoWPAN、Thread、Wi-Fi 等局域和个人域技术，还是 4G、5G、LoRa、LoRaWAN 和 SigFox 等广域技术，它都有涉及到。

再比如网络协议，MQTT、MQTT-SN、CoAP 和 AMQP 等常见协议，它也都讲到了。

更重要的是，这本书是 2018 年出版的。所以在这个技术发展日新月异的时代，它的内容还比较新，对云计算、边缘计算和数据分析等内容也做了介绍，这一点非常难得。

这么系统的内容，足以彰显作者的功力。这本书的作者叫 Perry Lea，他曾经在惠普工作了 21 年，担任首席架构师的职位。进入物联网行业之后，帮助 Cradlepoint 公司进行了 5G 和 IoT 方向的转型；作为联合创始人参与了边缘计算领域的 Rumble 公司的创业；在微软时期，又作为首席架构师参与了微软云游戏产品的研发。

由此可见，能力和见识的背后是行业内的长期深耕。

不过有一点要提醒你，这本书还没有中文翻译版，如果你想读的话，可以跟我一样从京东上买英文电子版。

另外，Perry Lea 还有一本物联网专著也是值得关注的，那就是 2020 年刚出版的IoT and Edge Computing for Architects。只不过这本书我现在也没有读完，没法为你详细介绍。

4. 介绍传感技术和通信技术最全面的书：《万物互联：物联网核心技术与安全》

我推荐的第四本书是《万物互联：物联网核心技术与安全》，它的特色是传感技术和通信技术介绍得特别全面。

这本书的作者是宋航博士，他潜心研究物联网技术十余年，是资深的物联网专家。同时他承担过物联网方向的很多课题，在物联网技术的广度和深度上都有积累。

这本书把物联网分为感知识别层技术体系、网络传输技术体系、管理服务技术体系和综合应用技术体系四个层次，然后围绕这四个技术体系进行了全面阐述，尤其是传感技术和通信技术的部分。比如传感器技术部分，对嗅觉和味觉都有介绍。

这本“大部头”著作，适合物联网从业者用来拓展技术视野，物联网专业的学生也可以拿它当参考书，作为教材的补充资料。

5. 了解 WoT 最应该看的书：《从物联到万联：Node.js 与树莓派万维物联网构建实战》

我推荐的第五本书是《从物联到万联：Node.js 与树莓派万维物联网构建实战》。它比较特别，为什么呢？因为它是关于 WoT（Web of Things，万维物联网）的。

WoT 可以说是 IoT 的一个子集。在这门课里，我主要讲解 IoT 的架构和技术体系；而 WoT 试图直接基于 Web 来构架物联网体系，这样就可以充分利用我们熟悉的 Web 应用的很多技术栈，而且可以实现物联网系统的互操作性。

这本书的作者是 Dominique Guinard 和 Vlad Trifa 两位物联网“老兵”，他们都是智能物联网平台 EVRYTHNG 的联合创始人。除了 EVRYTHNG ，他们还创立了一个WoT 组织，专门用于推进 WoT 的发展。

在这本书中，两位作者基于 Node.js 和树莓派讲解了 WoT 的架构和各种概念，并且通过代码展示了各层的实现。

对 HTML 和 JavaScript 熟悉的开发人员可以基于本书的内容大展身手。另外，WoT 已经是 W3C（万维网联盟）组织在推进的一个标准规范，也许在未来会有不错的应用前景。如果你希望拓展关于 WoT 的技术视野，它是你最应该看的书。

小结

现在我们总结一下，今天我推荐了 5 本书，分别是：

NTT DATA 集团：《图解物联网》

董玮，高艺：《创意到原型：物联网应用快速开发》

Perry Lea: Internet of Things for Architects

宋航：《万物互联：物联网核心技术与安全》

Dominique Guinard，Vlad Trifa：《从物联到万联：Node.js 与树莓派万维物联网构建实战》

衷心地希望你能从这些书籍中受益，也欢迎你和我分享读书心得。祝你在学有所成之后，真正地在物联网行业发挥自己的价值，做出自己的贡献。

另外，这只是我针对物联网领域的个人书单，难免会有不够全面的地方。如果你也有其他想推荐的书，欢迎在留言区和我分享。

用户故事 | 让野蛮生长成为职业发展的助推剂

你好，我叫 Yueshi。

我是一名 Java 开发工程师，工作 2 年，坐标杭州。很荣幸有机会在这里分享学习《物联网开发实战》这门课程的心得。

为什么学这门课程

首先要感谢极客时间搭建了一个优秀的学习成长平台。当我在这个平台上看到郭朝斌老师的《物联网开发实战》这门课的时候，顿时兴趣十足，主要的原因有以下三点。

第一，环境。在我家里，小米生态链的东西林林总总加起来有几十件之多。在一个围绕着小爱同学构建的初级智能家居环境中，我实实在在地感受到了物联网的魅力。

享受便利的同时，我也经常想要去了解背后的实现原理，所以就趁着这个机会直接上车了。希望郭老师能带我打开物联网的大门，进一步深化自己对智能家居的理解。

第二，知识体系。当我看到郭老师贴出来的知识体系图时，就被深深地吸引了。以前我对物联网的技术体系是完全陌生的，但是看着这张图，我陷入了沉思：为什么还有一个行业用着和 Java 开发相似的技术栈，而我却对它一无所知？会不会还有很多行业用着和我们差不多的技术栈在蓬勃发展？我们的技术栈还是制约职业选择的重要因素吗？

这些疑问强烈地驱使着我去了解这些技术在物联网中是怎么运用的，跟我自己在工作中使用的有什么区别。

第三，问题。生活在智能家居的环境中，我也在不断地探寻一些深层次的问题：什么是智能？智能仅仅是场景的联动吗？智能仅仅是简单的 IF-ELSE 吗？

智能究竟是什么，该怎么去定义，这个问题目前还没有标准的答案。只有亲自去实践，我们才能体会到智能的含义。

学习课程的收获

现在回顾一下，从这门课程中我学到了很多。课程每一讲都图文并茂，不仅定义足够简洁、准确，而且拓展的知识也指明了我们研究的路径。总的来说，我的收获主要体现在以下四个方面：

1. 完善的知识体系

正如郭老师在开篇词里说的：“物联网涉及的知识非常庞杂，包括通信技术、嵌入式开发、互联网系统、大数据、人工智能等等，甚至还有相关应用领域的专业知识。”我平时吸收的知识都是零散的、碎片化的，特别不利于物联网领域的学习。

但是在跟进这门课程的时候，郭老师带着我把这些零散的、碎片化的知识逐步网络化、系统化，让我对物联网建立了更全面、更立体的认知。

我的一部分笔记

2. 知识网络的拓展点

除了带来系统的认知，郭老师还给我指出了很多知识的拓展点（或者说知识的交互节点），而这些拓展点让知识的局域网变成了知识的互联网，实现了从“以点到面”进阶为“以面到体”的维度升级。

郭老师很自然地把这些重要的拓展点嵌入到课程中去，等待着我们去发现和探索，比如在第 2 讲补充的两个物联网行业专门的通信技术LoRa和SigFox，以及在介绍 BLE（低功耗蓝牙）技术的时候，还特地准备了一份基础知识的资料作为参考，可以说非常用心了。

3. 动手操作的环境

网上的各种教程浩如烟海，每一种都会教你搭建环境，教你快速入门。可是收藏了那么多教程之后，我却始终没有开始动手，没有去实现从 0 到 1 的突破。

一方面，当我面临的选择太多的时候，选择成本有时候比学习成本更大。

另一方面，网上虽然已经有一些 DIY 硬件设备的资料，但是大多都没有放在物联网的技术背景下去做。

所以我决定以这门课程为起点，完成从 0 到 1 的突破，借助极客商城的硬件套装和郭老师的操作教程，动手搭建操作环境，打出那一句“Hello World！”，毕竟“好的开始是成功的一半”嘛！

4. 一群热情好学的同学

其实不只是课程内容本身，留言区的问答和学习交流群的互动，同样是这门课程的精华所在。

留言区的用户提问，让这些知识经过不同的应用场景洗礼，越发显得生动立体；郭老师的认真回答，让这些知识经过进一步拓展，焕发出更强的生命力。而在交流群里热火朝天的讨论，让不同的思想碰撞，使得我们更加接近学习的本质。在这种氛围下，知识不再是冷冰冰的定义，而是像一个个生命体。

总的来说，在这门课程中，我学到了很多货真价实的物联网知识；而在留言区和交流群中，我也收获了很多应用和拓展的经验，这些都要感谢郭老师和热情好学的同学们。

感触最大的两讲

在这门课程中，有两讲是我觉得感触最大的，它们是第 13 讲《隐私：在实践中如何保护用户隐私？》和第 14 讲《安全：物联网平台如何应对安全风险？》。

这两讲的主题是隐私和安全，这两个词跟我们的日常生活关系非常密切。

当我们作为消费者时，是否足够在意隐私和安全，是否愿意为了隐私和安全买单，是否愿意成为良性市场的一份子，让市场的力量倒逼企业重视隐私和安全，从而促进行业准则的制定？

当我们作为开发者时，是否能做到比公司的开发要求多想一步，多思考一些？很多公司面临的机会和挑战是相似的，也许每次多思考一点，会成为我们在横向对比中胜出一筹的关键。

讨论隐私和安全，也不仅是局限当下，也是在放眼未来。未来的问题需要我们分配怎样的精力去面对和解决，是用长远的规划去提早应对变化还是着眼于当下更稳地走好每一步？这些开放式的问题，可能也需要我们不断思考和实践，锤炼出高超的职业素养之后才能回答。

我对学习方法的思考

接下来，我想聊聊对学习方法的思考。

设想这样的一个场景，在你面前有一堆陌生的零件和一份说明书，你需要把它们组装成一个能正常工作的产品，你会采取什么路径来解决这个问题呢？

你可能会先观察零件，试着组装一下，发现没那么简单之后，然后再去研究说明书，在说明书的指导下完成组装。

你也可能会直接把说明书摊开，一边看说明书，一边组装。

你还可能先快速地过一遍说明书，形成大致的印象后就开始组装。

但我想，应该没有人会先去仔细学习说明书，把说明书上的每一个知识点都了解得滚瓜烂熟以后，再动手开始组装吧？

现在我们看一下这门课程的安排，它分为基础篇、进阶篇和实战篇三个模块。假如把基础和进阶篇比作说明书，把实战篇比作我们动手组装的过程，你又会怎么安排学习进度呢？

我先分享一下我的学习计划：

第一步，跟着课程的节奏，形成课程的完整画像，做到心中有课程大纲。

这一步在于跟着老师的讲述，有序地梳理课程的脉络，做到心中有课程大纲，知道各个知识点分别在哪个篇章，能够快速地定位到它们。这一步同时也注重节奏，尽量避免“攒一波，一起消化”结果却消化不良的状况。

第二步，跟着实战环节，通过动手去实践学到的知识，不断地在场景中深化对知识的理解。

实战这一步是一个不小的挑战，要充分利用好交流群的作用。很可能自己遇到的问题别人也遇到过，并且已经有很成熟的解决方案。这个阶段的主要目的就是搭建预想的智能场景，通过第一步建立的知识点索引体系快速地找到不清楚的知识点并做补充。

第三步，完成了实战环节，通过总结和回顾去形成体系化的知识总结。

因为已经快接近课程尾声了，我会快速地过一遍课程的内容，然后以文字形式记录课程中的关键知识点，整理出知识网络图。

第四步，沿着自己的兴趣去拓展知识网络。

在前三步完成后，我还会根据自己感兴趣的方向去拓展知识体系。

在技术上，有一种叫做“银弹”的理想化方式，能一劳永逸地解决工程上的问题。但是计算机科学家佛瑞德·布鲁克斯却告诉我们，“没有银弹”。

同样的道理，在学习中，也不存在“银弹”。我们在学习的时候，就不要妄想一劳永逸了，真正的知识只有在反复阅读和思考之后才能提炼出来的。我们必须摒弃“一遍就会”的想法，只有时常回顾、点面俱到，才能彻底地掌握到知识要点。

毕竟学习的本质就是不断重复 + 刻意练习 + 不断思考。

成长的本质是野蛮生长

最后，我还想谈谈关于成长的思考。

自从参加工作以来，我们就一心扑到自己的专业领域中，稳扎稳打、逐步提升。专业上的精进既为我们带来了职位、薪资和阅历的提升，也悄悄地为我们埋下了一些不好的种子。

在专业领域上越探越深的同时，我们的视野却可能会变得越来越窄，思考方式也可能会变得越来越单一，我把这种现象称为“发展的孤岛效应”。（你可以打开公众号，统计一下关注的占比；也可以尝试设想一个完全陌生的岗位一天的工作内容是什么，然后验证自己的设想是否准确。）

怎样摆脱这种束缚呢？我自己的看法是，技术的野蛮生长。

我们都在羡慕年轻人，公司也喜欢年轻人，整个社会大环境都在迎合年轻人。可是当我们羡慕年轻人的时候，我们究竟在羡慕什么？是面对困难的勇气？是朝气蓬勃的精神？还是在其身上找到当年自己的影子？

也许这些都是正确答案，但是对我来说，羡慕的是年轻人身上的无限种可能。

回想在校园的时光，我们的学习无拘无束，我们的研究单纯又高效，我们可以对单片机玩得痴迷，也可以对数学建模颇感兴趣，还可能为了学习拉普拉斯变化和傅立叶变化，去研究拉普拉斯和傅立叶的个人事迹。这种状态就是我所说的野蛮生长的状态，一切都是兴趣使然。

虽然这样的效率并不高，可当我们回头看一看的时候，却无比羡慕这种状态。

不知从什么时候开始，我们被“高效”这个词语给奴役。资本家要求我们高效工作，相亲市场要求我们高效配对，房价也要求我们高效赚钱；同时，我们也开始用“高效”去要求身边的人和事，我们要求高效地学习，高效地购物，甚至连上班通勤和外卖订单，我们都要求高效。

高效确实一种很厉害的状态，但它并不是人生的终极目的。在越来越快的节奏中，我们不能忘了成长的本质。

成长的本质是什么呢？我觉得就是野蛮生长，释放天然的学习热情，解锁人生的无限可能，不那么功利，不预设目的，尽情地去体验和感悟人生。

总结

不知道你有没有注意到，在这次分享中我特意使用了“同学”这个词。

我们所处的时代，正是技术大爆炸的时代；我们所熟悉的技术，可能用不到五年就已经有了巨大地变化了。只有不断学习，我们才能在这个变革的年代找到自己的一席之地。

在工作中，我们面临着薪资和职级倒挂、中年危机、中产阶级陷阱和 35 岁末日之类的各种压力，我们只有不断学习，不断成长，才能让自己此刻稍微安心一些。

所以我今天的分享如果用一句话来概括就是，让野蛮生长成为职业发展的助推剂。

不知道你是怎么学习这门课程的？有没有什么独特的学习方法和心路历程呢？欢迎你在留言区分享，我们一起交流，相互鼓励，共同进步！

用户故事 | 转战物联网，我相信天道酬勤

你好，我叫谭方敏。

我是一名物联网架构师，坐标广州，目前已经工作十四年了，很高兴有机会能跟你分享自己学习《物联网开发实战》这门课程的心得。

为什么要学这门课？

首先我想跟你聊一聊为什么要学这门课，主要有三个方面原因。

第一，完善物联网知识体系。

在游戏行业泡了 10 年之后，因为一个偶然的机会，我进入了物联网行业，到现在差不多 4 年了。转行之后虽然也遇到了不少问题，但我依靠自己的摸索，一路上都有惊无险地解决了。

不过我毕竟是半路出家，底子不够厚，还是希望能全面地了解物联网的知识体系。我觉得一个人的技能应该长成一棵完整的大树，而不是一堆零乱的杂草，这样才拥有核心竞争力。

第二，尝试物联网全生态的落地实践。

知识不用，自然就会荒废。只有在实践中不断地消化和理解知识，才能形成闭环，做到知行合一，真正产生价值。

物联网的整个生态分为设备层、网络层和应用层，而我一直专注于应用层的研发，在工作中很少直接接触设备层和网络层。而在这门课程中，郭老师在实战篇介绍了几个实战小项目，这对我来说是一个很好的机会，让我能够打通全生态，完成整个物联网知识栈的建设。

第三，为创业寻找方向和思路。

2012 年我在游戏行业的创业失败，到现在已经快十年了。我一直认为创业就是一个严谨的科学实验。复盘失败的原因，我发现自己在各个层次上都或多或少缺少某些创业者必备的能力。所以在一顿恶补之后，我也希望能看看还差多少。

另外，物联网相对互联网而言，从发起创业项目到真正落地，并不需要那么多的资源。一个简单的智能家居项目，成本也许可以控制在 1000 元以内，时间也可以控制在一周左右，这就勾起了我的创业兴趣。

最后，疫情这个外部环境深刻地影响着每个人，我们都需要去思考如何面对不确定性。一般来说，创新型创业是一件非常烧钱的事情，而物联网则先可以孵化一些小项目，用极低的成本完成前期的原型验证。

我是怎么学习课程的？

以前看到过一句话，我觉得说得挺好的：学习是个耗能的过程，所以想要效率更高，你就要尽量调用身体各个器官储备的能量一起来完成这件事情，包括五官、大脑和四肢。

记得当年我背英语单词那会儿，条件可比现在艰苦多了，因为音频资料获取途径不多，非常珍贵。我边读边写边思考，才实现了大二就通过大学英语六级的目标。

同样的道理，学习这门课程也得尽量调动身体储备的能量。我觉得学习是一个费力费神的事情，首先就要做好思想准备，否则你就把学习当成 kill time 的方式了。

我学习用的是土方法。因为如果太讲究方法，就会导致操作难度太高，很难长期坚持下去。知识的学习过程是一个“润物细无声”的过程，所以我们还是尽量采用相对自然的方式比较好。

第一遍对照文字听，熟悉文字内容。

做笔记，将要点记录下来。

盲听 3～4 遍，一般是通勤路上，比较方便。

跟这门课程的同学互动。

在互动的过程中，我会尽量抽空回答同学们的问题，知无不言，言无不尽。这样做会让知识更容易形成闭环，加深我自己对知识理解，并且还有机会意外获得同窗的“革命友谊”。

比如“mosquito”同学曾经提过一个问题：“能否将消息存储在数据库或者 redis 中？”我当时就明确地告诉他，有相关的插件。（不过这样的互动大部分都发生在微信交流群，课程留言区的相对少一点。）

我的一部分学习笔记

学习过程中遇到过什么有趣的经历？

古语云：“读万卷书不如行万里路，行万里路不如阅人无数。”

这个道理也可以用在现代职场。当你学会一些东西的时候，最好去跟面试官“对打”，打得过就赢得了一份工作，打不过也获得了一次深度沟通和交流的机会。毕竟翻到行业大佬牌子的机会不多呀，应该好好珍惜。

前段时间我去面试，发现面试官可能是极客时间铁粉，并且极有可能就是《物联网开发实战》这门课程的同学。他问了好几个问题，都是这门课讲过的。

比如，物联网跟互联网的区别是什么？

物联网 = 互联网 + 物（设备）。这是郭老师在第 1 讲探索物联网定义的时候说明的。

又比如，智能快递柜不能使用发布订阅的原因是什么？

因为关门指令和关门动作是分开的。郭老师在第 3 讲中介绍网络协议的时候解释过。

再比如，批处理跟流处理的区别？

批处理的关键点有离线数据、延迟、以 Mapreduce/Spark 为代表；流处理的关键点有实时数据、低延迟、以 Storm/Flink/Spark Streaming 为代表。这些知识，郭老师在第 11 讲解读数据处理框架的时候都详细说明过。

我发现面试官居然在帮我复习这门课程的知识，感觉特别亲切。虽然最后没有一起合作，但是这种经历还是蛮有趣的。传统意义上，面试只是为了找工作，而今天，它更像是一个跟行业专家深度交流与沟通的机会。

学习课程有什么收获？

我觉得到目前为止，自己学习这门课程的收获主要分为两个方面。

第一个方面是刷新概念。

在学习课程之前，我就看过《图解物联网》这本书，对物联网建立了基本的整体认识。比如整个物联网可以分为 5 个环节，分别是设备、传感器、网络、物联网服务和数据分析。我觉得这种分法挺好的。

不过在这门课程中，郭老师把物联网的知识体系简化为三层，分别是设备层（传感器 + 网络），网络层（网络），应用层（物联网服务 + 数据分析），这样界定业务边界的时候就更方便了。

我原来以为，把物联网服务处理好就相当于把应用层搞定了。学习课程之后才发现，应用层边界可不只是物联网服务（准确地说，这个算物联网接入层 + 部分业务逻辑），还包括数据分析这块。意识到这一点之后，我大吃一惊，马上就去强迫自己更新数据分析的技能树了。

另外，我以前对物联网数据的理解也不够深刻。我会把数据分为三个阶段，分别是数据采集（数字化）、数据传输（网络化）和数据分析应用（智能化）。

而在这门课程中，郭老师分成了五个阶段，分别是数据源数据采集、数据传输、数据存储、数据处理和数据分析应用，其中数据分析应用还进一步细化成三个部分，数据存储，数据处理和数据应用。这样学习和实践的时候，就更能做到有的放矢了。

业内实践派也有一些更加接地气的分法，比如比较常见的“云端数”。所谓的云是物联网云（类似于阿里飞燕平台）；端主要是设备端，有一些还会包括业务端；数则是指数据平台（大数据）。

这个分法则在更大尺度上界定了物联网各个部分的边界，也从一定程度上提炼了物联网生态最有价值的关键元素，数的提出更明确地确定了大数据在物联网生态的重要性，以及数据分析作为物联网全流程中必不可少的一环。在智慧系列项目里面，这种提法还是比较多的。

第二个方面是获得新知。

我在原来的公司主要负责物联网平台这一块，所以对配网方面的知识不够了解。而在这门课程的第 2 讲中，郭老师把设备配网解释得很清楚，Wi-Fi 设备配网需要解决两个问题。

连上路由器（Wi-Fi 或 AP），实现数据上传和远程控制，也就是说，设备要获取到 Wi-Fi 热点的名称（SSID）和密码。

获取 Wi-Fi 设备的 MAC 地址，或者生产时预存的信息，并与用户账号一起上传服务器，完成绑定。

具体来说，有两种实现方式，一种是 smart config，另一种是设备热点。

我在负责物联网平台的应用和开发的时候，为了更好地辅助运营小伙伴决策以及我们研发人员对业务做监控，在日常开发当中，我们会采用埋点打日志的方式。

我们注意到，很多智能设备一旦下线，就长期处于失联状态。将智能设备连接上物联网云各个环节拆解后，我们发现配网过程的成功率非常低，甚至有些设备除了第一次是成功的，之后的配网就再也没有成功过。

在学习郭老师的课程后，我们知道了，除了 smart config 之外，还有设备热点配网和手机热点配网等方式。于是我们把配网方式从一开始选择的 smart config，换成了设备热点配网，从而在一定程度上改善了配网成功率。最终，我们在公司范围内推广了这个思路。

总结

从社会发展趋势来看，现在我们正处于互联网和物联网的过渡阶段，物联网很有可能会进一步成为新的网络基础设施，所以这门课程让我觉得非常有价值。

我在写这篇文章的时候，课程已经更新到第 22 讲，接近尾声，实战部分的内容悉数登场。目前，我已经完成了第一个产品场景，可以手机控制的智能电灯，并且接入了自己的物联网平台，真正实现了云端数一体化的目标。

当初我从做了 10 年的游戏行业转战物联网，就曾下定决心要“Make myself distinctive”，如今已经在物联网行业，自当努力奋斗。我相信天道酬勤，所有的付出都是值得的，不管是学知识，还是知识以外的其他内容。

在个人成长过程中，除了纵向地学习，建立自己的知识体系技能树之外，还需要横向地向同行学习，以技会友，相互切磋，补足或者加深自己在不同场景下的理解。即便同样是智能家居范畴，扫地机器人跟家用照明系统就完全不一样，技术不仅仅需要结合业务，更需要结合场景以形成深度认知。

最后，我想再分享孟子的一句话：“天降大任于是人也，必先苦其心志，劳其筋骨，饿其体肤，空乏其身，行拂乱其所为，所以动心忍性，曾益其所不能。”

与诸君共勉！