物联网全栈开发原理与实战

978-7-115-57882-2
作者: 吴志辉
译者:
编辑: 李永涛

图书目录:

详情

本书就底层设备的嵌入式开发、设备无线组网设计、网络通信传输设计、监控服务器设计、云端服务器设计、移动应用开发都做了全方位的介绍,用一个个实例把整个物联网应用系统串连起来,使用了多种开发语言、开发工具、设计技巧和方法,完整描述了一个复杂的“物联网设备监控平台”的设计和诞生。
本书适合物联网工程专业的本科生、研究生阅读,对有志于复杂物联网应用系统开发的设计师,特别是全栈设计师,本书也有较高的参考价值。

图书摘要

版权信息

书名:物联网全栈开发原理与实战

ISBN:978-7-115-57882-2

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

编  著 吴志辉

责任编辑 李永涛

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


物联网应用系统的开发需要“全栈”开发人员。从底层的智能传感器硬件设计开发、通信程序开发、服务程序设计、Web网站到移动应用开发都需要使用多种技术和编程语言,对人才的要求比较高。

本书就底层设备的嵌入式开发、设备无线组网设计、网络通信传输设计、监控服务器设计、云端服务器设计、移动应用开发都做了全方位的介绍,用一个个实例把整个物联网应用系统串连起来,使用了多种开发语言、开发工具、设计技巧和方法,完整描述了一个复杂的“物联网设备监控平台”的设计和诞生。

本书适合物联网工程专业的本科生、研究生阅读,对有志于复杂物联网应用系统开发的设计师,特别是全栈设计师,本书也有较高的参考价值。


时光荏苒,再过几年我就到退休年龄了。因为年纪的关系,过去的经历总像放电影一样在我脑海中呈现。

20世纪90年代,我从北京大学毕业,怀揣着化学硕士证书,被分配到湖南化工职业技术学院从教。那时,正是化工行业不景气的时候,除了教学,我也在想其他出路。可由于“贫穷限制了想象”,我错过了国家鼓励南下创业的机会。此时,世界银行贷款支持的项目的“春风”吹到了学校。购买了一批286个人计算机后,学校终于有了计算机机房。回想起在学校时,我学过BASIC和FORTRAN语言,毕业论文还用到了FORTRAN程序计算实验结果,就决定改行学计算机程序设计。1992年,我在辽宁丹东参加了一个化工行业的学术会,会上展示了美国化工生产的一个模拟系统,该系统是使用BASIC语言写的,要卖2万美元。这更加坚定了我要从事计算机程序设计的决心。

那时的计算机图书还没有现在这么多,其中比较多的是有关C语言的。好在当时Borland C出现了,于是我就从它学起。

一个偶然的机会,朋友介绍我给电视台做自动播出系统,于是我用C语言写了第一个真正商业化的程序,赚了3000元,比当时一年的工资还多。狠下心,我花5000多元买了台386计算机——500MB硬盘、4MB内存、DOS操作系统,虽然花了小两年的工资,但我还是很开心的。

由于我终究不是计算机专业毕业的,因此只能自己去看书学习基础知识。好在我有了自己的计算机,实践起来比较方便。当时我经常学到晚上12点,学到的东西很多,有五笔打字系统、打字比赛系统、化学反应模拟系统等。慢慢地,我开始用FoxPro给一些企业、电视台做收费管理系统。其间,宝兰公司的Delphi开发语言开始流行,于是我又开始用Delphi做程序设计。

1996年,我决定停薪留职出去闯荡一下。长达4年的时间,我在长沙、珠海、深圳等地工作,虽然很辛苦,但开发能力还是提高了不少。2000年年初,我又回到湖南,与几个朋友一起经营一家小公司,专为铁路行业服务,生产小配件,开发检测设备、自动控制设备,编写的应用系统有几十个。

可小企业生存很艰难,又遇到“非典”,公司盈利非常少,导致股东意见不合。2008年,我离开了公司,进入湖南工业大学计算机学院,又重新成为一名教师。7年的“创业”期间,我的大学同学出国创业、进入政府部门任职、下海创业成功的占了大多数。与他们相比,我总感到自己太不起眼了。不过回想一下,离开公司前,公司为国家纳税400多万元“真金白银”,也算是为国家做了些许贡献。

我在湖南工业大学的主要工作任务就是教学,主要教授的课程有软件工程、系统分析、信息系统、物联网技术与平台等。由于教学的需要,我逐渐开始用C#、Java编写程序。其间,我写了不少系统程序,一直被用户使用,如至今仍被上百家广播电台使用的“多路音频自动播出系统”,被几十家小电视台使用的“图文字幕视频自动播出系统”等。

2014年,学校创建物联网工程专业,于是我在物联网系一直工作到现在。

物联网工程是个全新的专业,很多专业课程是新开设的,任课教师也要经过培训和自学才能上课。无线传感器网络与应用、移动应用程序开发、RFID原理、嵌入式Linux网络系统开发,这些课程我都教过。对于很多知识,我也要从零开始学习,为了更好地掌握与硬件相关的知识,我自己从天猫购买设备,熟悉设备功能,并自己开发程序。

从事物联网教学多年,我有了不少感悟,也踩过不少坑。我把它们写出来,希望可以使后面的人少走些弯路。

物联网应用系统的开发,确实需要“全栈”开发人员。从底层的智能传感器硬件设计开发、通信程序开发、服务程序设计、Web网站到移动应用开发,都需要使用多种技术和编程语言,对人才的要求是比较高的。现在网上流传的使用某某语言“全栈”开发的资料很多,我认为大部分是不可取的,是误人子弟的。所谓的“全栈”开发,也大多局限在Web应用系统设计上,难以与物联网“全栈”开发相提并论。物联网应用系统极其复杂——尽管底层硬件配置越来越高,但不可能使用一种程序设计语言来满足所有应用开发的需要。

我在从事物联网工程专业的本科教育时发现,课程内容主要集中在基础理论知识的学习,实验课也只是用以对基本原理的验证而已。一周的课程设计或综合实训,很难让学生完成一个像模像样的系统开发。所谓的一个月的生产实习,也解决不了什么大问题。加上学生的学习任务也很重,无法腾出更多精力来专心做一个物联网应用系统。但一个本科生,至少需要体验一个完整物联网应用系统开发的全流程,并参与其中,才会获得深刻的认识,动手能力才会真正有所提高。

由于各种基础知识的学习时间段不同,在教授某门课程时,我也不好让学生去设计一个完整的物联网应用系统,但是设计部分且相对完整的子系统是可以的。

所以,设计一个良好的应用项目,既能满足物联网教学各阶段的学习要求,又能循序渐进,最终完成大部分教学要求。这是值得探索的。

为此,我编写这本有关物联网应用系统的图书时,既要满足教学知识的要求,又要有一定的现实意义和价值,还能拓展学生的想象力、创新思维能力。

在阅读本书之前希望读者能了解以下几点。

1.物联网本科教学的要求

目前各大院校的物联网专业开设的专业课程,大都包含单片机原理、传感器原理、无线传感器网络、RFID原理、通信技术、云计算、移动应用开发等。因此,全栈开发项目应该是一个涉及底层传感器、传感器网络、无线通信、互联网通信、云平台、移动应用等技术内容的物联网应用系统。

2.程序设计语言的要求

本科物联网工程专业开设的计算机语言课,主要有C/C++、Java,可选修C#、Python、JavaScript。全栈开发项目可以使用各种程序设计语言,便于提升学生使用开发语言的能力。同时,精心设计每个子系统,使每个子系统都可以分别用多种语言来实现,以便喜爱不同语言的学生都可以加入开发项目中。

3.硬件条件的要求

在练习时,可以使用从淘宝/天猫上购买的硬件,大部分的价格都不超过3位数,还包括开发工具和技术资料。

4.应用系统的选择

我之所以选择用“物联网设备智能监控系统”,是因为这个系统是从我的智能家居系统扩展而来的,且实现了绝大部分功能,已经是一个完整的应用系统,不间断在线运行时间超过4年。该系统稳定、可靠,使用方便,基本满足了上述3个方面的要求。

“物联网设备智能监控系统”稍加改造、扩展,可应用于物联网众多行业,如智能农业、智慧工厂、智能大厦、智能家居、智能医疗、智能安防。

正文中会详细描述该系统的结构和开发过程。所有子系统的源代码,都收录在本书的配套资源中。但如果用于企业软件、商业软件、付费软件等开发,须得到笔者本人的同意并支付一定费用。

5.适合学习的人群

本书为物联网本科生量身打造。学完C语言,就可以参与该系统的开发。对有志从事物联网应用开发的IT人员,本书也具有一定的参考价值,特别是在企业、公司研究部门从事软件开发的人员。如果是自学物联网系统开发的人员,以下是推荐必须要学完的课程,或者需要具备相应的能力:C语言程序设计、Java程序设计、C#程序设计、JavaScript程序设计、软件工程。

学习该系统的开发会是一个先苦后甜的过程。我用3年时间设计、完善了该系统,如果感兴趣的话,它应该值得用半年时间去学习。

本书可作为物联网专业综合实习/实训的实验指导书,也可整理成专业课程的课程设计教材,当然还需要花时间将本书的案例分解为一个一个的实验或小项目。

学习并掌握了该系统的设计,可以帮助读者在物联网应用开发方面更上一层楼。让我们开始吧!

由于时间仓促及笔者的水平所限,书中内容难免有误,还请读者不吝斧正,联系邮箱:liyongtao@ptpress.com.cn。

吴志辉

2021.8


由于应用行业不同,各种物联网应用系统的形态可能有很大差异,但核心结构是一样的。物联网是万物互联的网络,特别是众多的传感器信息收集终端(如温度传感器)或执行机构(如电冰箱、空调)。之所以说是万物,是因为其数量极其庞大。根据行业统计,2021年,全球物联网终端设备至少为200亿台。如一个4口之家的城市家庭,拥有的设备可能有4台智能手机、无线路由器、一个或多个冰箱、洗衣机、电饭煲、微波炉、空调、空气净化器、扫地机器人、煤气报警器、门磁报警器、智能摄像头、热水器,以及多达两位数的智能开关等。这些设备通过各种方式接入家庭局域网或互联网,与监控中心相连。用户通过手机等移动设备对这些设备进行监控或智能管理。

图1-1所示是普遍认可的物联网应用系统的四层结构示意图。

图1-1 联网应用系统的四层结构

需要澄清的一个误区是:物联网应用系统不一定要架设在互联网上,在局域网中也是可以运行的。信息感知设备不一定是单一的设备,可以是多个设备组成的“传感器网络”。

在物联网领域,也有科技企业、专家提出了边缘计算,期望在信息感知层、数据传输层和智能处理层之间插入边缘计算,就近提供最近端服务,也为数据中心提供计算数据。

目前,国内外主流的一些物联网生态系统以云服务为中心。海尔U-Home、阿里智能、苹果HomeKit、华为HiLink等物联网监控系统(智能家居为主)都采用该方式。其特点是,家居设备采用Wi-Fi通信方式,通过家庭网关或移动基站接入云端服务器;智能手机等客户端程序,从互联网同样接入云端的服务器,从而实现移动端远程设备监控,如图1-2所示。

图1-2 监控平台在云端的物联网监控系统结构示意图

这种结构的优势在于以下几个方面。

(1)借助互联网云服务商提供的强大功能,可以集中管理众多物联网设备。特别适合于单件“智能”设备的家庭使用,其云监控平台是共享的。

(2)消费者无须关心网络连接问题和费用。由于目前行业巨头都有自己的云平台,消费者一旦购买其产品,就可以连接到云服务中。

(3)便于云服务商收集消费者的设备信息和使用习惯等数据,可以延伸各种服务。

这种结构的缺陷也很明显,主要表现在以下几个方面。

(1)设备通信方式单一,目前只能使用Wi-Fi方式接入互联网。即使接入局域网,最终也必须通过网关接入互联网,无法直接在局域网内监控。使用NB-IOT技术接入互联网,会产生一定的费用,家庭消费者难以接受。

(2)集中管理风险大。过于依赖互联网和特定云平台,一旦云服务宕机或受到黑客攻击,影响面极大,远程监控和各种服务都将停止。如果服务商倒闭,消费者的利益得不到保障。

(3)如果不开放通用的监控平台,则“绑架”了硬件设备生产商和消费者。迫使硬件厂商为同一个产品生产符合多个平台要求的设备。

(4)如果消费者使用的设备较多、交互频繁,复杂的场景任务管理等功能很难实现,无法满足消费者对智能监控高度自动化服务的要求。

(5)消费者隐私、设备安全难以保障。在安全和隐私越来越被关注的社会,该问题越发突出。

鉴于这种架构的缺陷,一些企业把监控平台的部分功能移植到了专用的智能网关设备中,以期减轻云服务的压力和提供本地服务,如小米、华为的专用智能网关设备;但依然会迫使消费者购买价格不菲的网关设备,有些甚至打起了使用客厅电视机充当监控中心的主意。

从实际使用物联网智能监控(如智能家居)的角度考察,大部分的设备操控是在企业、组织、家庭内部进行的,只有出门在外才需要通过互联网进行设备监控。因此,把监控平台设立在组织机构局域网内部是明智的。这样为设备的多样性接入、安全管理、任务定制、智能监控,都提供了强有力的保障。图1-3所示是全栈开发项目实现的智能设备监控系统的结构示意图。其中,连接到云端服务是可选的。

图1-3 监控平台在局域网内部的监控系统结构示意图

这种结构具有很多特点和优势。

(1)设备连接的多样性。

设备可通过Wi-Fi接入局域网后连接到设备监控平台(Device Monitor Center,DMC)使用TCP/IP协议;也可通过USB、RS232、有线以太网等直接连接到监控中心,甚至通过远距离的485通信方式连接。这保证了传统已有设备的智能化升级改造,无须硬件的重新设计和改造,只需简单改写部分程序代码即可,成本极低。这对传统工厂的智能化改造十分重要。

(2)对路由器或网关设备没有特殊要求。

企业、家庭使用已有的网络结构可满足监控平台的搭建和使用,省心、省力、省费用。

(3)可使用成本低廉的低功耗平板电脑安装监控平台系统。

甚至可以使用价格低于某些品牌的智能网关设备。如果监控设备众多,且监控中心提供众多的服务,也可以使用高配计算机或服务器。

(4)监控平台(中心)是一套多功能的软件系统。

不在操作系统级别进行硬件的管理,兼容性强。理论上,任何可连接计算机的设备(包括程序等虚拟设备),都可为其设计监控驱动程序,从而纳入平台的监控之下。软件的升级、维护、功能扩展都很方便。

(5)本地、互联网皆可进行智能监控。

在组织机构内部,移动监控App直接连接到监控平台进行监控,速度快、安全性高。如果需要从互联网监控,则可购买云服务商提供的虚拟机,在其中安装通信服务程序,充当远程客户端与监控平台的通信桥梁,仅此而已,并无其他功能,从而保证了系统的安全性。对虚拟机配置的要求不高,如使用阿里云最基本的几百元/年的云服务器,可部署4个云通信服务程序。消费者可自由选择不同的服务商。由于通信服务程序不保存任何设备和消费者的信息,因此可随时迁移到其他服务商的云平台中。当然,也可在云服务器上搭建数据中心和Web服务,满足行业的需求。这种场景下,DMC实际上就是一个边缘服务器。

这也为互联网服务商提供了广大的市场。互联网服务商可利用其强大的资源为千万级别的家庭搭建特色的通信服务(目前主流云平台通过大规模消息队列服务提供物联网设备监控的信息交换)。

在使用互联网监控时,本全栈开发的项目使用了华为的云虚拟机,并且为开发者提供了一个测试通信服务器,但对连接客户端数量做了限制。开发者最好自己购买一个云虚拟机。如果是在校学生,可申请9元/年购机。

(6)设备也可直接连接到云通信服务器。

我们设计的通信服务程序也实现了全栈项目核心技术的3个协议(见1.2节)。这样,很多移动设备,如车载导航系统、穿戴式设备,都可通过Wi-Fi或移动互联网(如、、NB-IOT)连接到通信服务器,被视为组织内部设备的一员。DMC用统一的方式监控所有的设备,实现设备互联互通。设备的安装位置已经没有限制了,由于监控仍然是从内部的DMC发出的,安全性也得到了保障。

图1-4所示是全栈开发系统可能使用的设备连接示意图,描述了各种可能的设备连接方式,包括有线或无线连接、进程间通信、局域网或互联网连接。

从图1-4的设计可以看出:监控平台是以“设备监控系统”为单元来连接设备的,单件设备监控只是一种特例;一个“设备监控系统”可以容纳众多的子系统,子系统容纳具体的设备。目前众多的Wi-Fi智能设备通过局域网连接到DMC,而蓝牙等无线设备可以直接连接到DMC。对于有线连接的设备,则必须直接连接到DMC所安装的计算机上,如串口通信连接线、并口连接线、USB连接线、网线等。

图1-4 物联网监控平台硬件结构及通信连接示意图

移动客户端,既可从互联网接入DMC,也可从局域网接入DMC进行监控。出于安全考虑,必要时DMC也可断开互联网连接,全部在组织内部运行。

由于在本地部署监控中心,多样化的通信得以实现,因而不会过度依赖互联网,安全和可靠性得以加强。

物联网应用系统结构中,监控中心或监控平台是核心组成之一,是万物互联的控制中心。设计良好的软件结构是监控平台稳定、可靠运行的关键技术,对监控的实时性、可用性、可扩展性至关重要。目前主流物联网智能监控生态系统,都是基于直连云平台服务,通信方式比较单一。

图1-5展示了全栈项目结构中,各个设备或程序间的通信方式。

图1-5 监控平台软件结构及通信方式示意图

软件结构总体分为4个层次:设备层、监控层、管理层和应用层。

监控平台系统程序主要由两部分组成:设备监控中心(DMC)程序和多个设备监控进程(Device Monitor Process,DMP),它们之间使用消息队列进行双向通信。

(1)设备层。

硬件厂商可根据全栈系统提供的协议、模板和自身的硬件环境,自由编写设备监控驱动(Device Monitor Driver,DMD)程序。没有任何硬件上的约束,内部通信自由选择。系统监控的响应速度与此处的通信有较大关系。

(2)监控层。

由多个设备监控进程DMP组成。每个DMP由两部分组成:管理程序和设备监控驱动模块。它们之间也使用消息队列双向通信。DMP进程中的DMD是动态加载的,在管理层中的设备监控中心(DMC)程序启动一个设备监控进程DMP时,传递设备监控驱动相关信息给设备监控进程,DMP启动后,再加载监控驱动(DMC)程序。

图1-6 设备监控中心程序构件依赖关系

处在监控层的设备监控驱动DMD是真正与设备监控系统交互的程序模块。通信方式由硬件设备系统决定,在设备监控驱动程序中实现底层的通信交互(如RS232、Wi-Fi、IPC)。

设备监控驱动程序是一个软件中间件DMM(Device Monitor Middleware,实例化后就是DMD),实现系统核心协议与具体硬件系统内部的工作协议之间的双向转换。它是监控平台监控设备的核心程序。

(3)管理层。

主要是DMC程序,它由多个程序模块组成,如图1-6所示。各模块分工协作,合作实现设备监控的核心功能。目前主要完成监控相关任务的实现。数据存储、数据分析等扩展功能,留待以后完善,也可以放在客户端行业应用程序或Web网站中实现。

(4)应用层。

主要是通用移动/PC客户端监控应用程序和行业化要求的各种信息服务平台。

客户端程序使用标准的TCP/IP协议与监控中心服务程序通信。主要用于远程设备监控——发送监控指令或接收设备状态信息等,使用标准的通用数据格式协议。在应用层中的移动客户端程序,可自由编写灵活的、功能强大的界面,也完全可以由第三方软件企业开发设计成各种服务平台(如基于地图的公交车位置实时监控系统),只要通信规范,符合系统的3个协议即可。

移动客户端程序也可直接从局域网接入监控中心,从而减少对互联网的依赖。

掌握全栈开发系统的结构层次和通信方式、流程,是正确把握全栈项目开发的轴心所在。

项目总体结构简单,层次清晰,分工明确。对外,可以呈现一致性的监控界面,消费者只需一个App就可监控所有设备;对内,提供了丰富多样的设备连接方式,是传统电子电气设备智能化升级改造的福音。使用监控驱动中间件把各种异构、不同形态的硬件设备,无差别地接入监控系统,真正意义上实现了万物互联。

对设备生产商,“标准”协议完全开放,没有利益绑架,有利于其创新、专注生产高质量设备,从而可以让成千上万的中小微企业加入物联网设备的生产行业中。只有消费者和生产者都加入这个行业,物联网设备智能监控(如智能家居)的春天才会真正到来。

任何系统,只要涉及数据通信和交换,都需要协议。只有遵守共同的协议,才能无障碍地交换数据。我们的目标是,制定一个通用的协议,实现真正的设备互联互通。看起来似乎不可能,将成千上万的设备、无数的通信方式和数据格式整合成通用的描述确实很难。但通过多年的实践,终于有所收获。系统目前制定了3个核心协议。系统实际运行证明,这些协议有效解决了物联网设备监控的难题。下面将详细介绍系统的3个协议,只有理解了其中的设计理念,才有可能痛快淋漓地编写核心代码,应用开发才会得心应手。

万物互联的底层是千差万别的设备,它们有着各自的使命和任务。有采集各种数据的传感器设备,如温湿度、车速、光照度、开关的状态、空气污染指数、燃气浓度等,描述方式数以万计,是大数据的发源地;有些设备是执行机构,可按设定的程序指令工作,满足人类生活或工作的需要,如各类电器开关、热水器、扫地机器人、电子门锁、电动窗帘等;有些设备是前两者的混合体,既可采集数据,又可根据采集的数据,按设置的要求自动工作,如空调、冰箱、电饭煲、智能电梯等。这些设备由不同的硬件设备厂商设计生产,使用了不同的工艺和技术,导致对这些设备的监控都有特殊的技术要求,而物联网应用的普及,如智能工业、智能农业、智能安防、智能家居、智能医疗等,都需要的最根本、最基础的要求,就是实现万物互联。只有实现万物互联,才易于获取大数据,实现对设备的统一监控,实现高度的自动化控制,人工智能才有生存的意义。缺失这个根基,物联网无从谈起,万事皆成空想。

对设备的描述有不少的“标准”和协议。IEC61804是国际电工协会IEC制定的电子设备描述语言规范标准。笔者个人认为,该标准在物联网设备的描述方面仍然有不足之处,过于烦琐复杂。本项目不采用该标准描述设备。

IEEE 1451标准对传感器设备的描述,在“智能变送器接口模块”(Smart Transducer Interface Module,STIM)描述中,定义了电子数据表格TEDS,但对其属性类型没有做规定,缺乏通用性,因此,本项目也没有采用。

电子产品编码EPC系统使用PML实体标记语言(Physical Markup Language)描述自然物体、过程和环境。这种方式同样缺少属性描述规则,且对设备控制没有太多的帮助。我们也没有使用该XML规范来描述物联网设备。

由于需要接入物联网,这里的设备是指具有MCU或CPU处理器,且具有通信功能的“智能”设备(通信方式可以多样化,没有限制)。这里先分析一下有关设备的一些术语。

(1)简单设备和复杂设备。

简单设备,一般是使用低成本的单片机作为控制中心的设备,功能较为单一,专注完成特定的任务,如专门采集各种数据的传感器设备、控制电源开关的电气设备。这些设备数量庞大、应用广泛,升级这些传统设备,是接入物联网的必经之路。如何低成本实现需求是值得认真研究的。

复杂设备,从功能角度而言具有多样性。在通信界面,可与其他设备或计算机进行数据交换,甚至具备设备组网功能,从而形成设备系统,完成复杂的工作任务,如当前火爆的智能音箱、智能门锁等。

理论上,不管以何种方式,只要能实现与监控平台相连,就可以实现设备互联互通,并不需要设备之间直接相连。

(2)智能设备和非智能设备。

设备的智能,没有明确的定义。从单一设备的视角而言,具有数据采集、处理、自动控制工作状态的设备,都是智能设备,也是人们通常期待的解释。从物联网的视角看,要求其实不高,具备与监控平台通信,能接收指令工作的简单或复杂设备皆可视为智能设备,其“智能”的范围被转移到了整个监控平台上。由功能强大的监控平台,负责数据的处理和设备间的协同工作。所以在物联网,非智能设备应该是指无法联网的设备,而不是从单件设备功能的角度去考虑。单件设备的“智能”不管如何强大,终究是个信息孤岛,无法协作完成更多复杂的任务。

(3)设备描述的分类。

设备作为物联网系统最底层的数据源,目前缺少一个合理的描述协议。纵观目前一些主流物联网监控生态系统,也提供了开发者平台,其对设备的描述不尽相同,大多采用JSON结构描述其结构和功能,如阿里的AIoT、华为的HiLink协议,但都缺少一个灵活的、通用的描述。这导致设备开发商的同一功能的产品无法应对不同平台的要求,或者为了在不同平台上运行自己的设备,设计复杂,成本上涨,稳定性下降,因而无法吸引大多数开发商为其打造专用设备。这实际上是绑架了设备开发商。目前很多平台对设备的描述,主要是从设备功能上进行描述。

1.基于设备功能的描述

它按照设备实现的功能来分类。这是从消费者的角度看待设备,是最直观的描述方式。如ZigBee联盟推出的智能家居规范,目前支持30多种产品,如开关类、灯光类、传感器类、窗帘类等,规定了每种设备的主要功能和交互规则。这样的设备描述规则,无法满足市场上成千上万种不同功能设备的描述需求,更无法满足兼容未来无穷无尽设备的需求,其生态系统会处在无休止的、不停的变化之中,用户体验自然不佳。

如何设计一个通用的设备描述协议,能满足目前众多设备的描述需求,更能解决未来任何复杂设备的系统接入难题,无疑是万物互联的根本所在。

(1)基于电气设备的特点及使用范围分类。

这是从厂商生产设备的范围角度出发的分类方法,图1-7描述了智能家居行业对产品的分类的表达,最终也归结于设备功能分类。这种方法没有从本质上抽象设备描述,同样无法适应未来复杂设备的描述。

图1-7 基于设备使用范围的分类

(2)基于智能设备的通信方式分类。

要实现设备互联互通,必须有一个设备管理和监控中心或平台(没有分析过区块链技术是否可以打造无中心的监控系统)。设备连接到监控平台的通信方式,总体可分为有线通信和无线通信两类。传统的单总线、I2C、RS232、485、基于电缆或光纤的以太网等,都是成熟的通信技术。而在物联网前端的设备,目前更注重于无线通信,可解决大量设备布线难的问题,目前广泛使用的Wi-Fi、蓝牙、ZigBee、NB-IoT、Lora、//移动通信等,都属于无线通信技术。未来还可能出现或使用更多的无线通信技术,如超宽带技术、量子通信等。这种分类方式只是描述了通信特征,并没有描述设备的具体功能,而使用者往往只关心设备的使用功能,而忽视其后台的通信机制。所以通信方式并不是设备的核心描述内容。

2.基于数据驱动的设备描述

从大量的实践和分析中,我们可以获得高度抽象的设备描述方法。设备功能最终一定是以特定的数据表现出来的。如电源开关的开、闭功能,可以用1和0两个数字(Digital Data)表达,体现有、无或通、断等状态,我们称之为数字量开关设备Digital Device。这类设备几乎占据了设备总数量的半壁江山,很多复杂设备几乎都集成了大量的数字设备。

煤气浓度检测、播放器音量大小调节,这些功能最终体现出来的是一个模拟量数值(Analog Data),我们称之为模拟量设备Analog Device。

有些复杂设备的功能,其数据描述不能使用简单的数字或数值。例如,摄像头获取的是一幅图像数据,由大量的字节组成;3D物件由成千上万的三维坐标点组成。这类较复杂的设备,我们称之为流媒体设备Stream Device。

这种用数据替代功能的描述方法,我们称之为基于数据驱动的设备描述方法4D(Data Drive Device Description),简称为4D设备描述协议。

4D这种高度抽象的描述把无穷无尽的设备功能简化为有限的三大类设备。由于设备有输入输出的概念,4D进一步把设备分为6种,如图1-8所示,其用接口规约描述设备的组成结构。

图1-8 设备的结构描述类图

基于数据驱动的设备的各种概念介绍如下。

(1)设备IDevice。

IDevice是由设备厂商生产的硬件设备或软件开发商设计的程序系统(以下称为虚拟设备)。它负责完成设备具体功能的实现,如智能音箱完成语音的识别。它由下面描述的6种子设备组成。

(2)数字量输出子设备IDeviceDO。

数字量输出(DigitalOutput)设备(DO),是从使用者角度来看的,它对外输出数字信号,就可以控制设备工作。常见的智能开关设备,输出1或0,就可以开、关电源;音乐播放器输出1或0,就可以启动或停止播放音乐。如果这些数字指令来源于监控平台,就可以实现远程控制。DO子设备一般是控制机构。

(3)数字量输入子设备IDeviceDI。

数字量输入(DigitalInput)设备(DI),是从使用者角度来看的,它采集数字信号进入设备内部保存,代表了数字设备的工作状态,如红外探测器是否检测到有动物通过,可以用1或0来标识。采集的数字数据,如果传输到监控平台,就实现了远程设备监视。

(4)模拟量输出子设备IDeviceAO。

该类设备与数字量输出子设备类似,不同之处在于数据的表达,其使用数值来反映功能。如智能灯光的亮度,可以使用0~100的一个数值来代表。输出100,表示开启最大亮度照明;而输出0,几乎没有亮度。4D用数值代表功能,保证了兼容未来任何复杂设备的描述。

(5)模拟量输入子设备IDeviceAI。

该类设备与数字量输入子设备类似,不同之处在于数据的表达,其使用数值来反映功能。PM2.5传感器采集的数据用一个数值代表大气污染指数;穿戴式智能手表采集心跳速率,也是用一个模拟量数据反映一个健康指标。这些数据传输到监控平台,经过分析处理,可以控制其他报警设备工作。该类子设备一般是数据采集设备。

(6)流媒体输出子设备IDeviceSO。

流媒体设备相对复杂。4D用字节流来反映其功能。这也是唯一不能穷尽的数据类型。根据当前科学技术发展状态,一些常用的功能数据类型可以抽象出来,因此,在流媒体设备描述中,有子分类的描述,如文本、图像、文件、视频、音频、坐标集合等。设备使用流数据进行工作,就称为流媒体输出(SO)设备,如语音助手,可接收文本信息指令,然后朗读文字,说明它具备一个SO设备。

(7)流媒体输入子设备IDeviceSI。

如果设备采集或计算得到的数据是复杂的字节流信息,那么这种设备应被称为流媒体输入设备(SI),如摄像头采集图像得到图像数据,表明它拥有一个SI设备;如果它能进行人脸识别,计算得到人的身份证文本信息,就间接拥有了另一个文本类型的SI设备。全栈项目的监控中心对流媒体数据只进行了简单的处理。如果需要进行复杂的计算处理,可以在专用客户端进行,避免监控中心处理负载加重,同时保持监控中心系统的稳定性,防止频繁升级。

总之,4D设备描述协议描述的设备组成结构就是6类子设备的组合,即DI、DO、AI、AO、SI、SO的集合。理论上,任何设备通过仔细精心的设计,都可以分解、转换为6种子设备的集合(尽管在数据呈现方面有所不便)。

有限的类型描述了无穷的功能,为兼容未来任意复杂设备提供了基础,也奠定了万物互联的基石。读者应该将其理解掌握,因为后续的开发都围绕该核心技术展开。

为了大规模物联网设备监控的管理、实施的方便性,全栈项目系统又进一步引入了设备管理对象。图1-9所示是以监控系统为基本设备管理对象的结构图。

注意:4D协议的描述都是用抽象的接口进行的。可以用任何支持接口技术的程序设计语言实现。C#、Java实现的版本,参见后续章节。

(8)监控系统接口IMonitorSystemBase。

监控中心可以启动多个DMP来监控不同的系统。用一个唯一的识别号DMID来描述监控平台管理的监控系统。DMID由监控平台负责分发。一般地,同一厂家生产的系列产品,使用相同的通信协议,监控系统使用同一设备监控驱动程序就可以监控这些设备。一个设备监控系统在一个进程中管理,使用一个设备监控驱动程序。

例如,一个智能大厦的监控平台,可监控灯光设备系统、消防安全系统、门禁系统、语音广播系统、电梯系统等。一个监控系统,由多个设备系统组成。

(9)设备系统接口IDeviceSystemBase。

该类接口用于描述监控系统中特定用途的子设备系统的集合,主要是为了管理的方便而设置的。例如,智能大厦的灯光监控设备系统,可以分为室外照明子系统、一层照明子系统、二层照明子系统等。在监控平台的一个监控进程中,用一个DSID号来标识设备系统。

(10)子设备系统接口ISubDeviceSystemBase。

该类接口描述具体设备的集合。一般用于同一目标的设备,方便集中管理。如室外灯光子系统,由几十个不同的智能开关构成。在监控进程的某设备系统中,用一个SSID号来标识子设备系统。

(11)设备接口IDevice。

该类接口体现功能的物理硬件或软件产品。它由一个或多个6类子设备组成。在某个子设备系统中,用一个DID号来标识设备。

(12)子设备接口IDeviceXX。

该类接口体现设备某个具体功能。功能可以是硬件实现的,如物理的电子开关;也可以是程序设计出来的“虚拟”功能,如传感器采集数据的频率周期、在线工作时长等。在设备内部,用一个SDID号来标识子设备。子设备是真正实现设备功能的地方。

因此,在监控平台,一个子设备的身份用5个数字+类型来唯一标识:DMID.DSID.SSID. DID.SDID.TYPE,其中TYPE是子设备的类型。注意:这些特定的标识号,将会频繁出现在后续的各种对象的定义中,以后不做过多介绍。

下面将详细讨论图1-9中各个接口的设计内容,它是系统的核心思想。

图1-9 监控系统的结构描述类图

3.工程项目开发环境和结构

在讨论前,先搭建项目工程开发环境,将通过简要的代码来讲解。由于监控中心是核心部分,使用设备接口描述协议管理设备,所以先搭建监控中心的开发环境。可以使用安装了Linux系统的设备作为监控中心,也可以使用Windows系统的计算机作为监控中心,两者的开发环境和工具有所不同。笔者本人偏爱Windows平台,所以使用了微软的Visual Studio 2017/2019作为开发工具。这也是考虑到可以使用的计算机大都安装Windows系统,这样可以方便用户安装使用。

微软开发工具Visual Studio 2017/2019(后续简称VS)可从微软官网下载,具体安装过程请读者自行参考一些网络教程。

刚使用VS的读者,需要花点时间熟悉VS的功能和操作使用。此类图书很多,请先掌握基本的使用方法。

首先建立整个工程的框架结构。图1-10所示是全栈开发系统的项目结构。

(1)CommAssembly目录。

系统用到的一些通用的程序模块,编译结果是DLL程序集。

(2)Drivers目录。

硬件设备的监控驱动程序项目存放目录。对于非“标准”的设备(没有在程序中实现核心协议的设备),都要编写一个协议转换程序。全栈开发系统展示了其是如何为各种不同通信方式的设备开发监控驱动程序的(参见第8章)。

图1-10 全栈开发系统的项目结构

(3)Tool App。

开发过程中,需要开发一些辅助工具程序来提供数据、验证算法等。这些小项目集中存放在该目录下。工程完成后,可以删除。

(4)VirtualDevice目录。

一些程序作为虚拟设备接入监控平台。这些程序可有效扩展监控平台的功能,如语音播报程序、计算机时钟监控程序、邮件发送程序、短信发送程序、摄像头拍照程序等。笔者没有提供这些程序的源代码。不过学习完全栈项目后,读者可以自己开发出类似程序。

工程根目录下的4个项目分别如下。

Android客户端监控程序是在谷歌Android Studio上开发的,参见后续章节介绍。

设备描述协议在智能控制库项目SmartControlLib中描述,该项目的结构如图1-11所示。

在Devices目录下的IDeviceSystem.cs文档中,完整定义了图1-9所示的所有接口。其属性和方法的设计,考虑了它们的通用性,以设计属性为主。接口的方法是通用的,主要以属性的读写、存储为主。而设备的操作单独放在另外一个接口文档IMethod.cs中描述。这也是经过多次惨痛的教训而重新设计的。最早的版本中,设备的操控方法也被设计在设备接口IDevice中,结果在具体实现各种不同设备类的时候,发现代码大都相同,没有必要重复编写,还容易出错,于是进行了重构,移到一个单独的接口中。

图1-11 智能控制库项目的目录结构

首先从最底层的6类子设备接口介绍。由于这些子设备都具有一些共性,所以又抽象出一个共性的IBaseDevice接口。详细代码参见本书配套资源中的源代码。

4.IBaseDevice

public interface IBaseDevice                   //基础子设备公共接口
{
DeviceType DeviceType { get; set; }            //子设备类型:只能是6类子设备中的一类
ushort ParentID { get; set; }                  //设备父类的ID
ushort SDID { get; set; }                      //原来的ID,子设备编号:0---65535   
   Double X { get; set; }                      //X坐标(经度)
   Double Y { get; set; }                      //Y坐标(纬度)
   Double Z { get; set; }                      //Z坐标(高度)
   int Tag { get; set; }                       //一个通用的保留指针
   string UnitName { get; set; }
   string FunctionDescription { get; set; }//子设备功能的文字描述:如客厅顶灯开关
   string ControlDescription { get; set; } //子设备操作的文字描述:告诉使用者如何使用该功能
   string Blacklist { get; set; }//黑名单:出现在列表中的用户,无法控制该子设备,但子设备功能正常
   string Whitelist { get; set; }             //白名单:只有在列表中的用户,才能控制该子设备
}

坐标信息像身份信息一样,是一类主要的信息。很多物联网监控系统都需要定位监控,因此它是必不可少的属性,用XYZ坐标表示。

子设备类型拥有一个枚举数据DeviceType来标识,如下所示。

public enum DeviceType : byte                     //智能设备类型定义:只有6种设备
{
DI = 0, DO = 1, AI = 2, AO = 3, SI = 4, SO = 5, Unknow = 99
//数值量输入输出子设备,模拟量输入输出子设备,流媒体输入输出子设备
}

黑名单Blacklist、白名单Whitelist主要从设备最底层来掌控设备的控制安全性,防止非法远程控制设备工作。这是为输出型子设备DO、AO、SO设计的。默认都为空,即不受限制。目前监控中心优先检查黑名单,在没有的情况下,再检查白名单。多个账号(用户名)出现在名单中时,以英文的逗号隔开。如“洪七公,欧阳锋”代表两个账号。

6个子设备接口都是从IDeviceBase接口继承下来的,增加了各自需要的一些新属性。下面详细介绍数字输出子设备接口IDeviceDO。其他5个子设备接口,请读者自行参阅源代码。

public interface IDeviceDO : IBaseDevice, IWriteReadInterface//数字量输出DO设备接口
{   
DOType DoType { get; set; }                      //开关类型:常开Open和常闭Close 2种
PowerState PowerState { get; set; }              //开关电源状态,DoType和PowerState
                                                   共同决定DO子设备的接通或断开状态
       bool ON { get; }                          //true表示接通,false表示断开
       string PictureON { get; set; }            //接通时的图形文件名
       string PictureOFF { get; set; }           //断开时的图形文件名
       event DigitalDataChangedOnDigitalDataChanged;//当数据变化时,通知应用程序的事件
       string ToString();
 }

总共新增加了5个属性和一个事件。

DOType:对开关量设备,有两种工作模式——常开和常闭。因此定义了枚举类型,代码如下。

public enum DOType : byte                       //DO开关类型
{
    Open, Close                                 //常开和常闭开关
}

接触过各种实际开关设备的人员,都很熟悉这两种工作方式。

PowerState:设备控制机构是否供电。它是由设备中的(嵌入式)程序控制的,也由一个枚举数据表示,如下所示。

public enum PowerState : byte                   //开关的控制机构电源工作状态
{
PowerON, PowerOFF
}

ON:一般用户使用设备,只关心开关是否“通”“断”,并不关心控制机构是否供电,因此设计了这个只读属性。它由DoType和PowerState属性共同决定,是个计算字段。ON为真时,开关接通;为假时,开关断开。

PictureON:开关接通时,用于显示其状态的图形文件。默认为空。

PictureOFF:开关断开时,用于显示其状态的图形文件。默认为空。

OnDigitalDataChanged:定义了一个事件,当设备状态有“变化”时,触发该事件。主要为方便监控应用程序设计使用。也可以考虑去掉。

该事件类型定义如下。

public delegate void DigitalDataChanged(DataChangedEventArgs<bool> e); //数字量处理
                                                                         方法的代理
public class DataChangedEventArgs<T> : EventArgs//定义数据变化的事件类,包含2个数据:发送者
                                                  和具体内容(泛型)
{
public DataChangedEventArgs(object sender, T data)
{
    Sender = sender;
    Data = data;
}
public Object Sender { get; private set; }
public T Data { get; private set; }
}

6类子设备还都继承了一个接口IWriteReadInterface。该接口定义了两个方法,用于把子设备的信息写入字节流,或者从字节流中读取属性值。该接口定义如下。

public interface IWriteReadInterface              //读写对象的接口,方便对象的文件存储
{
void WriteToStream(BinaryWriterbw);               //把对象写入流中
void ReadFromStream(BinaryReaderbr);              //从流中读取数据
}

5.IDevice

它是6类子设备的集合,实现完整的设备功能,是使用者眼中的“物理实体”。但4D描述协议只定义了其结构,实现了该结构的软件系统,也可以视之为“设备”,一般称为虚拟设备。在下面的设计中,可以看到6个列表数据,代表6类子设备。

public interface IDevice : IWriteReadInterface      //某智能监控的设备定义接口
{
ISubDeviceSystemBase owner { get; set; }            //上级父类接口,便于向上路由
ushort SSID { get; set; }                            //归哪个子设备系统管理
ushort DID { get; set; }                            //设备ID
Double X { get; set; }                              //X坐标(经度)
Double Y { get; set; }                              //Y坐标(纬度)
Double Z { get; set; }                              /Z坐标(高度)
int Tag { get; set; }                               //通用指针
bool Used { get; set; }                             //是否启用,保留,暂时不用
string DeviceName { get; set; }                     //如调光开关
string PositionDescription { get; set; }            //设备位置信息文字描述,如客厅
string IEEEOrMacAddress { get; set; }               //设备IEEE或MAC地址
string Memo { get; set; }                           //备注信息,用于扩展
List<IDeviceDI> DIDevices { get; set; }             //数字量输入子设备
List<IDeviceDO> DODevices { get; set; }             //数字量输出子设备
List<IDeviceAI> AIDevices { get; set; }             //模拟量输入子设备
List<IDeviceAO> AODevices { get; set; }             //模拟量输出子设备
List<IDeviceSI> SIDevices { get; set; }             //字节流输入子设备
List<IDeviceSO> SODevices { get; set; }             //字节流输出子设备
                                                    //以下定义一些必备的方法
IdeviceDO NewDeviceDO();                            //建立DO新子设备
IdeviceDI NewDeviceDI();                            //建立DI新子设备
IdeviceAO NewDeviceAO();                            //建立AO新子设备
IdeviceAI NewDeviceAI();                            //建立AI新子设备
IdeviceSO NewDeviceSO();                            //建立SO新子设备
IdeviceSI NewDeviceSI();                            //建立SI新子设备
string GetState(DeviceType DeviceType);//某类子设备所有状态的文字描述,便于应用程序显示设备信息
void Clear();                                       //清空6类子设备
string ToString();
}

对于这些定义,其后的注释做了简单描述。几个属性介绍如下。

6.ISubDeviceSystemBase

这是设备的集合。通常,监控系统中,同一个区域有多个相同的设备组成一个子设备系统,以方便管理。如室外路灯子设备系统,由十几个智能开关组成。下面的设计描述了它的基本属性和必须具备的方法。

public interface ISubDeviceSystemBase                //智能子设备系统基础接口
{
IDeviceSystemBase owner { get; set; }                //父类接口
ushort DSID { get; set; }                            //归哪个设备系统管理
string Description { get; set; }                     //如神龙城监控子设备系统-电源开关子系统
string ParentDescription { get; set; }               //上一级系统的简要描述
ushort SSID { get; set; } 
Double X { get; set; }                               //X坐标(经度)
Double Y { get; set; }                               //Y坐标(纬度)
Double Z { get; set; }                               //Z坐标(高度)
int Tag { get; set; }
List<IDevice> Devices { get; set; }                  //所有设备列表:最主要属性管理其下所有设备
                                                     //方法接口:主要解决数据读写、存储问题
void WriteToStream(BinaryWriterbw);                  //子设备系统所有数据写入数据流
void ReadFromStream(BinaryReaderbr);                 //从数据流中恢复子设备系统所有信息
byte[] GetBytes();                                   //子设备系统所有数据用字节数组表示
void ReadFromBuffer(byte[] buffer);                  //从字节数组中恢复子设备系统所有信息
void Clear();
IDeviceNewDevice();                                  //建立新设备
}

最主要的属性是列表对象Devices,代表该子设备系统管理的所有设备。

7.IDeviceSystemBase

设备系统是一个纯粹的管理对象,用于分类不同工作目的的子设备系统。例如办公大楼的设备系统,有照明控制子设备系统、空调控制子设备系统等。

public interface IDeviceSystemBase                   //智能设备系统基础接口
{
IMonitorSystemBase owner { get; set; }
ushort DMID { get; set; }                            //属于哪个监控进程管理
string Description { get; set; }                     //神龙城监控设备系统
ushort DSID { get; set; }                            //设备系统识别号,由监控中心程序赋予
Double X { get; set; }                               //X坐标(经度)
Double Y { get; set; }                               //Y坐标(纬度)
Double Z { get; set; }                               //Z坐标(高度)
int Tag { get; set; }
List<ISubDeviceSystemBase> SubDeviceSystems { get; set; }//所有子设备系统列表,是监控系统
                                                           管理的主要对象
//方法接口:主要解决数据读写、存储问题,与ISubDeviceSystemBase相同
void WriteToStream(BinaryWriterbw);
byte[] GetBytes();
void ReadFromStream(BinaryReaderbr);
void ReadFromBuffer(byte[] buffer);
void Clear();
SubDeviceSystemBaseNewSubDeviceSystem();                 //建立新子设备系统
}

最主要的属性是列表对象SubDeviceSystems,代表该设备系统管理的所有子设备系统。

8.IMonitorSystemBase

监控中心DMC的管理对象是监控进程DMP,DMP使用设备监控驱动程序DMD来与硬件设备交互。IMonitorSystemBase是DMD与硬件交互的基本单元,如图1-5所示。它由多个设备系统IDeviceSystemBase组成。需要注意的是,为简化管理和安全起见,本开发项目的DMP只使用一个DMD来与设备交互,所以组成该监控系统的物理设备,必须都是使用同一通信方式和协议的设备(一般由同一个厂家生产)。而使用不同协议的硬件构成的监控系统,使用另外一个DMP管理。当然,DMC也允许使用相同协议的硬件构成的监控系统,使用多个DMP进程管理。

其接口定义如下。

public interfaceIMonitorSystemBase                       //一个监控系统基础接口:可监控多个设备系统
{
//属性接口
IMonitorSystemMethod iMonitorSystemMethod { get; set; }
string Description { get; set; }                         //神龙城监控设备系统
string FileName { get; set; }                            //信息存储文件
ushortDMID { get; set; }                                 //设备系统识别号,由监控中心程序赋予
Double X { get; set; }                                   //X坐标(经度)
Double Y { get; set; }                                   //Y坐标(纬度)
Double Z { get; set; }                                   //Z坐标(高度)
int Tag { get; set; }
CommModeCommType { get; set; }
bool bServer { get; set; }                               //设备系统是否为通信服务端
List<IDeviceSystemBase> DeviceSystems { get; set; }      //所有被监控的设备系统列表
//方法接口
void SaveToFile();
void ReadFromFile(string filename);
void WriteToStream(BinaryWriterbw);
byte[] GetBytes();
void ReadFromStream(BinaryReaderbr);
void ReadFromBuffer(byte[] buffer);
void Clear();
IMonitorSystemBase Copy();                               //复制到另外一个监控系统,作为数据备份
IdeviceSystemBase NewDeviceSystem();                     //建立新设备系统
}

方法接口与前述接口的设计类似,主要是属性的读写、存储。

有以下几个主要的属性。

图1-12 监控中心存储信息的目录结构

其中,iotmonitorsetXXX的文件夹用于存储一个DMP所需的信息,其后的XXX序号,就是该DMP的监控识别号DMID。

CommType:通信方式。开发项目目前设计支持的有市面最常见的几种通信方式,定义在一个枚举类型中,如下所示。

public enumCommMode : byte
{
RS232 = 0, SHAREMEMORY = 1, TCP = 2, TCPFree = 3, ZIGBEE = 4, BLUETOOTH = 5,
CLOUD = 6,                                                             //通过云端连接
UNKNOW = 99  
}

对于本系统支持实现了的通信方式,后续章节会对每类通信方式的设备驱动程序的开发做一个详细的案例介绍。

整个监控平台内,多个进程之间、进程与设备之间,都进行着大量的数据交换。对于这些数据的格式,必须设计一个通用的数据结构,便于程序统一处理。目前,网络通信大都使用XML或JSON格式作为数据表达结构,后者由于其封装简单、冗余数据少,特别受监控系统开发平台的喜爱,华为、阿里等公司都使用JSON作为通信数据结构。

可扩展标记语言XML,虽然数据表达能力强,但冗余数据多,导致通信数据包较大,因此在监控系统中使用得比JSON要少。但用键值描述数据的JSON类型,在表达复杂数据格式时也有不便之处,例如对字节数组的存储,需要转换为十六进制字符串。

全栈开发项目使用数据字典来描述通信数据包。其词条的内容为字节数组,可以表达任何格式的内容。

1.数据字典类IotDictionary

IotDictionary.cs文档定义了数据字典类,如下所示。

public class IotDictionary
{
public Dictionary<string, byte[]>mDictionary = null;      //Java可以使用Vector类型,参见
                                                            移动端App开发章节
Int32 flag = 0x;  //智能监控通信的标志,不是的则不处理
public IotDictionary(Int32 _flag)                         //构造函数
{
mDictionary = new Dictionary<string, byte[]>();
flag = _flag;
}
public void Clear()                                      //清除字典内容
{
if (mDictionary != null)
mDictionary.Clear();
}
public void AddNameValue(string name, byte[] value)      //原始增加词条方法
{
mDictionary[name] = value;
}
public void AddNameValue(string name, char[] value)     //字符数组词条,UTF8编码
{
mDictionary[name] = Encoding.UTF8.GetBytes(value);
}
public void AddNameValue(string name, string value)     //字符串词条,UTF8编码
{
mDictionary[name] = Encoding.UTF8.GetBytes(value);
}
public void AddNameValue(string name, Stream value)     //流对象词条
{
byte[] buffer = new Byte[value.Length];
value.Read(buffer, 0, (int)value.Length);
mDictionary[name] = buffer;
}
public void DeleteName(string Name)                     //删除词条
{
if (mDictionary.Keys.Contains(Name))
mDictionary.Remove(Name);
}
public string GetValue(string Name)                     //获取词条内容:返回字符串
{
if (mDictionary.Keys.Contains(Name))
{
char[] s = Encoding.UTF8.GetChars(mDictionary[Name]);
return new string(s);
}
else return null;
}
public byte[] GetValueArray(string Name)                //获取词条内容:返回字节数组
{
if (mDictionary.Keys.Contains(Name))
return mDictionary[Name];
else return null;
}
public string[] GetNames()                               //获取字典词条名称集合
{
return mDictionary.Keys.ToArray();
}
public override string ToString()                       //字典内容用字符串显示,可根据需要改写
{
string result = mDictionary.Keys.Count.ToString()+" Items :";
foreach (string Name in mDictionary.Keys)
{
if (Name == "stream")
{
byte[] bs = GetValueArray(Name);
result += Name + string.Format("= 字节流数据: {0}字节\r\n", bs.Length);
}
else if (Name == "deviceinfo")
{
byte[] bs = GetValueArray(Name);
result += Name + string.Format("= 设备信息流: {0}字节\r\n", bs.Length);
}
else
{
result += Name + "=" + GetValue(Name) + "\r\n";
}
}
return result;
}
void WritwInt(Stream stream, int i)//写入一个整数到流对象中,以便在不同操作系统保持数据一致
{
stream.WriteByte((byte)((i >> 24) & 0xFF));
stream.WriteByte((byte)((i >> 16) & 0xFF));
stream.WriteByte((byte)((i >> 8) & 0xFF));
stream.WriteByte((byte)(i & 0xFF));
}
public byte[] GetBytes()                       //根据编码返回便于发送的字节数组
{
MemoryStream stream = new MemoryStream();
                                               //1.写入标志
WritwInt(stream, flag);
                                               //2.写入一个占位符,表示整个字节流的长度为4字节
WritwInt(stream, 0);
                                               //3.写入字典词条数量
WritwInt(stream, mDictionary.Count);
foreach (string Name in mDictionary.Keys)      //写入每个词条
{
byte[] buffer = Encoding.UTF8.GetBytes(Name);
int len = (int)buffer.Length;
WritwInt(stream, len);                         //先写入长度
stream.Write(buffer, 0, len);                  //再写入数据
buffer = mDictionary[Name];
len = buffer.Length;
WritwInt(stream, len);                         //先写入长度
stream.Write(buffer, 0, len);                  //再写入数据
}
byte[] result = stream.ToArray();
int size = result.Length;
result[4] = (byte)((size >> 24) & 0xFF);      //修改占位符数据
result[5] = (byte)((size >> 16) & 0xFF);
result[6] = (byte)((size >> 8) & 0xFF);
result[7] = (byte)(size & 0xFF);
return result;
}
public string GetHexString()                   //转换为十六进制编码的字符串
{
byte[] buffer = GetBytes();
StringBuilder result = new StringBuilder(buffer.Length * 2);
for (int i = 0; i<buffer.Length; i++)
{
result.Append(buffer[i].ToString("X2"));
}
return result.ToString();
}
public static IotDictionaryConvertBytesToIotDictionary(string HexString, Int32 _flag)
{
byte[] buffer = new byte[HexString.Length / 2];
for (int i = 0; i<buffer.Length; i++)
{
buffer[i] = byte.Parse(HexString.Substring(i * 2, 2), 
System.Globalization.NumberStyles.AllowHexSpecifier);
}
return ConvertBytesToIotDictionary(buffer, _flag);
}

static int ReadInt(Stream Stream)
{
int b1, b2, b3, b4;
b1 = Stream.ReadByte();
if (b1 < 0) b1 = b1 + 256;
b2 = Stream.ReadByte();
if (b2 < 0) b2 = b2 + 256;
b3 = Stream.ReadByte();
if (b3 < 0) b3 = b3 + 256;
b4 = Stream.ReadByte();
if (b4 < 0) b4 = b4 + 256;
return (b1 << 24) + (b2 << 16) + (b3 << 8) + b4;
}
public static IotDictionaryConvertBytesToIotDictionary(byte[] buffer, Int32 _flag)
{
if (buffer.Length < 8) return null;
MemoryStream stream = new MemoryStream(buffer);
                                                          //1.读取标志
int len = ReadInt(stream);
if (_flag != len) return null;                            //非系统通信数据或乱码
                                                          //2.读取数据总长
len = ReadInt(stream);
if (buffer.Length < len) return null;                     //数据长度不对
                                                          //3.读取词条数目
int size = ReadInt(stream);
if (size > 100) return null;                              //不能超过100个词条
int count = 0;
IotDictionary json = new IotDictionary(_flag);
while (count < size)                                      //读取所有词条
{
count++;
len = ReadInt(stream);                                    //词条名字长度
byte[] destBuffer = new byte[len];
stream.Read(destBuffer, 0, len);
String Name = Encoding.UTF8.GetString(destBuffer);      //词条名字
len = ReadInt(stream);                                  //词条内容长度
destBuffer = new byte[len];                             //词条内容
stream.Read(destBuffer, 0, len);
json.mDictionary[Name] = destBuffer;                    //添加一个项
}
return json;
}
}

字典类中词条的增、删、改方法,比较容易理解。最关键的2个方法是实例方法GetBytes和静态方法ConvertBytesToIotDictionary。前者把字典内容转换成字节数组,方便在网络传输;后者把字节数组转换为字典结构,用于把接收的网络数据转换成字典,便于程序处理。仔细观察代码结构,可以确定字典转换为数组的结构(见表1-1)。

表1-1 物联网设备监控平台通信数据包结构

第一部分 第二部分 第三部分 第四部分
Flag标志 数组全部长度 字典词条数目 N个词条集合(以下是一个词条的结构)
词条名称长度 词条名称 词条内容长度 词条内容
4字节 4字节 4字节(值为N) 4字节(Len1) Len1字节 4字节(Len2) Len2字节

2.物联网设备监控平台通信数据包结构

一个标准的通信数据包,其结构见表1-1。

数据格式协议使用字典结构可以传递任意复杂的数据,通过IotDictionary对象处理起来方便可靠,通过标志和长度可验证其合法性。当然,也可增加校验部分,但为减少计算量,本开发系统没有增加该部分结构。

从转换方法中可以了解到,全栈项目对字符串处理约定都是采用UTF8编码方式处理。

在监控中心和客户端之间(DMC-CLIENT)、监控中心与监控进程之间(DMC-DMP)、监控进程与监控驱动程序之间(DMP-DMD)的通信(如图1-5所示),都是使用标准的字典结构,用统一的方式对设备进行监控的。

设备监控驱动与具体设备间的通信(DMD-DMS)可以是非标准的。但如果是标准的字典结构,我们称这些设备为标准兼容设备,使用已经设计完成的通用监控驱动就可以接入这些设备到监控平台。而“非标准”设备,则需要特定监控驱动程序起到翻译的作用。把平台的字典结构指令,解析为设备本地的指令格式,从而控制设备工作;或者把设备的状态数据转换为字典结构,然后传递给监控平台统一处理。

1.2.3 设备监控协议

设备通用描述协议只描述了设备结构。对设备的监控方法,也应该有通用的接口描述,包括监控内容和监控方法,以减少监控指令的复杂度,满足大部分通用监控要求。

1.IMonitorSystemMethod

为方便设备监控驱动程序的设计,本全栈开发项目把操控设备的方法定义成标准接口。根据监控系统的组成结构,设计了两个主要接口类,分别用于两个主要的设备层,定义在IMethod.cs文档中。把属性和方法分别定义在不同的接口类中,隔离了它们之间的紧密联系,在实现具体的设备监控驱动程序时,不变的属性处理部分就可以用通用代码实现,无需重复编写(见后面的介绍)。设备商只需关注设备监控的方法处理。

IMonitorSystemMethod接口,就是在为监控系统MonitorSystem对象设计的方法。定义的内容如下,基本满足了设备监控的需要。

public interface IMonitorSystemMethod           //监控系统对象需要实现的方法
{                                               //只定义了7个方法
void InitComm(object[] commObjects);            //设置通信对象
void InitDeviceSystem();                        //监控系统的设备系统初始化
void Login(object[] loginParas);                //由父类完成登录方法。一般不用
void LoadSubDeviceSystems();                    //设备变化时,需要重新装入监控设备的内容
IsubDeviceSystemMethod FindSubDeviceSystem(IsubDeviceSystemBase subDevSystem);
void ProcessDeviceSystemData(object sender,byte[] states); 
void ProcessNotifyData(byte[] cmds);
}

(1)InitComm(object[] commObjects)。

设备监控驱动程序所需要的通信对象是由DMP产生,通过该方法传递给DMD的。DMP需要在调用该方法之前,根据DMD的通信要求正确设置通信对象数组commObjects。之所以放在DMP中实例化通信对象,是因为可以通过可视界面调节通信参数并保存;而DMD是以黑匣子方式运行的,不方便重新设置参数。项目设计的约定如下。

① 数组第一个通信对象commObjects[0]是RS232通信对象SerialPort。如果不是该类型通信方式,一般传递过来的是NULL。DMD只关心它需要的通信对象。

② 数组第二个通信对象commObjects[1]是TCP/IP通信对象,有以下两种可能性。

a.如果设备系统为通信服务端(bServer为真),commObjects[1]是一个客户端socket对象,已经用MyTcpClient类封装了(参见源代码),它是DMP建立的客户端通信对象,DMP使用它去主动连接设备系统。

b.如果DMP为服务端(bServer为假),commObjects[1]是DMP已经连接的客户端设备的通信对象列表,对应着每个设备系统DeviceSystem。客户端设备的通信对象ConnectClient也已经在项目中封装实现(参见源代码)。

③ 数组的第三个通信对象commObjects[2]是进程间通信对象ShareMemory(参见源代码ShareMemory.cs)。它是必需的,用于DMP与DMD之间的通信。

④ 数组的第四个通信对象commObjects[3]是DMP进程的有关信息和状态的对象SmartHomeChannel(参见源代码SmartHomeChannel.cs)。DMD可根据需要获取其中的一些信息。

(2)InitDeviceSystem()。

监控系统的设备系统初始化方法。对硬件结构固定的设备,DMD需要详细描述所有子设备信息,一般在DMD的构造函数或InitComm方法中完成。对于动态加入的设备,需要在ProcessDeviceSystemData方法中处理,根据监控协议动态加入设备。

(3)Login(object[] loginParas)。

登录设备系统的方法。如果设备系统要求登录,DMP在构建DMD后,需要调用该方法。传递的参数一般为账号与密码。如果无须登录,该方法为空代码。

(4)LoadSubDeviceSystems()。

监控系统是允许设备动态接入的。当设备变化时(会收到相应通知),就需要重新装入设备的信息。由于本全栈项目的设计是把设备放在子设备系统ISubDeviceSystemBase之中管理的,该接口方法的名字也就设计成LoadSubDeviceSystems,而不是LoadDeviceSystems或LoadDevices。

(5)IsubDeviceSystemMethod FindSubDeviceSystem(IsubDeviceSystemBase subDevSystem)。

我们设计接口类IMonitorSystemMethod并没有定义直接控制设备的方法。我们把操控设备的方法定义在IsubDeviceSystemMethod接口中。主要是因为不同设备厂商,监控设备的方法协议是不同的,无法用通用的代码事先编码完成。所以必须要通过IsubDeviceSystemBase来找到监控方法的实例才能真正操控设备。因此才有了该接口方法。

(6)ProcessDeviceSystemData(object sender,byte[] states)。

设备厂家实现的处理底层设备上传数据的方法,sender一般是通信对象。该接口是DMD的核心功能。至少要完成数据的解析、翻译,使其成为标准协议格式数据,并上传数据给DMP,最后到达监控中心。

(7)ProcessNotifyData(byte[] cmds)。

DMD需要及时获取监控中心下达的指令,把“标准”指令翻译成具体设备可以识别的本地指令,从而监控设备。设备商必须根据内部的通信协议,编写正确的“翻译”代码。

2.ISubDeviceSystemMethod

子设备系统SubDeviceSystem,是设备接入监控系统的基本单元。对设备的监控方法,也就定义在ISubDeviceSystemMethod接口类中,对应着子设备系统(它需要实现该接口)。下面是该接口类的定义。

public interface ISubDeviceSystemMethod             //子设备系统需要实现的设备监控方法
{
//硬件设备商必须完成的监控设备的方法,必须在监控驱动程序的SubDeviceSystem类中实现!下面是12个获取设备状态信息的方法
bool GetDOState(ushort did);                        //获取本设备DO输出状态信息的方法
bool GetDIState(ushort did);                        //获取本设备DI输入状态信息的方法
bool GetAOState(ushort did);                        //获取本设备AO输出数据的方法
bool GetAIState(ushort did);                        //获取本设备AI输入状态的方法
bool GetSOState(ushort did);                        //获取子设备SO输出数据的方法
bool GetSIState(ushort did);                        //获取子设备SI输入状态的方法
bool GetOneDOState(ushort did, ushortsdid);         //获取设备特定DO子设备状态信息
bool GetOneDIState(ushort did, ushortsdid);         //获取设备特定DI子设备状态信息
bool GetOneAOState(ushort did, ushortsdid);         //获取设备特定AO子设备状态信息
bool GetOneAIState(ushort did, ushortsdid);         //获取设备特定AI子设备状态信息
bool GetOneSOState(ushort did, ushortsdid);         //获取设备特定SO子设备状态信息
bool GetOneSIState(ushort did, ushortsdid);         //获取设备特定SI子设备状态信息
//3个控制子设备工作状态的方法,只对输出子设备有效
void SendDO(ushort did, ushortsdid, bool On);       //对DO输出子设备发送指令
void SendAO(ushort did, ushortsdid, double[] value);    //对AO输出子设备发送指令
void SendSO(ushort did, ushortsdid, byte[] value);      //对SO输出子设备发送指令
}

6个GetOneXXState(ushort did, ushortsdid)方法:获取指定设备编号(did)中指定子设备编号(sdid)的状态数据。

在开发全栈项目的设备监控驱动程序的过程中,做了如下约定。

SO、SI子设备只对全“TEXT”子类的设备有效,各子设备的字符串之间用英文“*”号隔开,如“您好**星期一”。对非“TEXT”类型的设备,该指令无效。只能使用单个子设备状态获取指令。

SendDO()方法,是控制具体DO子设备开、关工作的方法。第三个参数是控制参数。

SendAO()方法,是控制具体AO子设备工作状态的方法。注意:第三个控制参数是数值数组,意味着可以传递多个数值进行较复杂的控制。数值类型由设备商决定,大多情况下可能是一个double类型的数值。

本全栈开发项目没有使用整数、枚举和单精度浮点数类型,统一用双精度Double数据类型来描述模拟量数据。DMD程序需要开发商自己去转换成需要的其他类型数据。

SendSO()方法,是控制具体SO流媒体子设备工作状态的方法。传递的控制参数是字节数组。这意味着可以传递任何数据,如文本、图像、文件等。

这15种接口方法,足以对设备进行全方位的监控,满足了通用监控的要求。

3.IotMonitorProtocol.cs

监控中心或移动App,需要用统一的方式对设备下达监、控等各种指令。设备返回的信息也必须包含特定指令,表明信息的类型。

IotMonitorProtocol类定义了目前支持的指令类型。指令用字符串表示,内容如下。

public class IotMonitorProtocol                  //物联网设备监控通信协议
{
public static string LOGIN = "500";              //客户登录指令
public static string AppSTATE = "501";           //获取所有DMP程序状态
public static string STARTApp = "502";           //通知DMC启动或结束DMP
public static string SHOWDMMUI = "503";          //通知DMP显示或隐藏
public static string UPDATEPICTURE = "504";      //请求更新显示图片
public static string SHACTRL = "505";            //给某个DMP系统的设备发指令
public static string DEVSTATE = "506";           //获取或报告某个设备的状态数据
public static string GETTASK = "507";            //获取智能监控的任务数据
public static string MENDTASK = "508";           //修改智能监控的任务数据
public static string RUNTASK = "509";            //通知SHS执行某个任务
public static string GETALARM = "510";           //获取智能监控设置
public static string MENDALARM = "511";          //修改智能监控设置
public static string TEXT = "512";               //文本通知命令
public static string CAMERA = "513";             //有关摄像头操作
public static string SCREEN = "514";             //获取DMC屏幕图像
public static string SETALARM = "515";           //设防/撤防
public static string MESSAGE = "516";            //移动端留言给服务器
public static string NEWDEVICE = "517";          //有新设备接入通知
public static string MENDDEVICE = "518";         //修改智能监控的设备信息
public static string REBOOT = "519";             //重新启动监控中心的计算机
public static string CLIENTEXIT = "520";         //客户端退出
public static string ERRHINT = "521";            //通用错误信息提示
public static string TESTCONECTION = "522";      //通信测试是否连接
public static string ASKUSERINFO = "530";        //请求DMC的用户信息
public static string DEVICESYSTEMINFO = "531";   //子设备系统的详细信息
public static string MENDCLOUNDIP = "532";       //修改连接云服务器的地址和端口
public static string NEWCLIENT = "540";          //有新客户登录
public static string POSITIONCHANGED = "541";    //设备(不是子设备)位置有变化
}

目前定义了20多种常用指令。这些指令里作为监控通信数据字典中最重要的一个词条是“cmd”。cmd不同时,携带的其他词条会不同。本全栈开发项目定义了表1-2中常用的一些词条。最重要的两个是获取设备状态信息的指令“506”和控制设备工作的指令“505”。其他指令都是一些辅助指令,帮助系统实现常用的业务功能。

约定:词条名称,全部小写;词条内容,除了字节数组外,全部用字符串表示数据。

表1-2 物联网设备监控平台通信字典通用词条

词条名称

含义

通信对象、范围、说明

cmd

指令词条,必须有;代表数据包的意义;在IotMonitorProtocol类中定义

CLIENT→←DMC→←DMP→←DMD

dmid

指定监控系统标识号

CLIENT→←DMC→←DMP→←DMD

dsid

指定设备系统标识号

CLIENT→←DMC→←DMP→←DMD

ssid

指定子设备系统标识号

CLIENT→←DMC→←DMP→←DMD

did

指定设备标识号

CLIENT→←DMC→←DMP→←DMD

sdid

指定子设备标识号

CLIENT→←DMC→←DMP→←DMD

type

指定子设备类型

CLIENT→←DMC→←DMP→←DMD

value

DI、DO、AI、AO子设备的状态字符串

CLIENT→←DMC→←DMP→←DMD

stream

SO子设备的状态字节数组

CLIENT→←DMC→←DMP→←DMD,用于登录时,传递用户信息表。其他情况视cmd而定

user

登录用户名

CLIENT→←DMC

password

登录密码

CLIENT→←DMC

login

登录返回值

1:要求重新登录;OK:登录成功

right

登录成功后返回的用户权限字符串

 

cloud

远端通信服务器发来信息的标志

1/0/null(不存在)

rmtdev

是否为设备远程接入

有:表示通过云端通信服务器接入

description

设备系统等的描述信息

文本

dmm

专用DMP连接到云端标志

DMP→←cloud(云通信服务器)

dmc

DMC连接到云端标志

DMC→←cloud

err

错误提示信息

CLIENT→←DMC→←DMP→←DMD

text

错误提示信息

CLIENT→←DMC→←DMP→←DMD

deviceinfo

设备系统接入时报告的设备信息

DMP→←cloud(云通信服务器),DMP→←DMD

rmtdevip

远程设备的IP地址

 

state

DMP的运行状态

1:在线运行;0:没有启动

start

启动或停止DMP的工作

 

visible

显示或隐藏DMP进程

 

pic

图像文件名

CLIENT→←DMC

size

数据大小

 

need

是否需要某种操作

1/0

act

控制设备工作的参数

CLIENT→←DMC→←DMP→←DMD

task

场景任务名称

CLIENT→←DMC,“插座1B打开.act”

index

传输数据的索引号

CLIENT→←DMC

plan

总智能监控文件名

CLIENT→←DMC,“Monitor.alm”

timetask

是否定时任务

CLIENT→←DMC

alarm

具体监控报警文件名

CLIENT→←DMC

set

设防/撤防

CLIENT→←DMC

senderip

发送者IP地址

CLIENT→←DMC cloud

sourceip

源设备IP地址

CLIENT→←DMC cloud

x

坐标位置经度

DMC→←DMP DMD

y

坐标位置纬度

 

根据需要,指令词条cmd携带了不同的其他词条。设计的原则就是,数据字典携带的信息必须是自我完备的、无状态的独立结构,不依赖于已有通信指令或后续指令。

通用的词条数量是有限的,可以防止过度膨胀带来的复杂性。出于特殊需要,也可以自行扩展词条,但只能在企业组织内部使用。监控中心也不会处理这些词条,除非我们重新改写了本项目的源代码,或者改写客户端代码来处理它们。

1.2.4 核心协议的实现

一个程序,如果完全实现了核心协议,就可以成为设备监控驱动程序。为方便使用,编译成dll链接库的形式。通过编写监控驱动程序,发现大量的代码是相同的。为减少开发工作量和难度,本全栈项目把不变的部分代码提前实现,并与核心协议编译成一个通用的程序:SmartControlLib.dll。设备商在开发自己的监控驱动程序时,只需引用该程序即可完成大部分工作,专心于具体设备的交互过程。解决方案中的CommAssembly/SmartControlLib项目,就是完成该任务的。

Devices.cs和MonitorSystem.cs两个文档是最重要的实现文档。其他一些文档是辅助性的,内容也较多,请自行阅读。下面介绍Devices.cs实现的内容。

1.Devices.cs

文档代码较多,超过2000行代码。总体上看是实现了最底层的设备和子设备对象,如图1-13所示。Device类实现了IDevice接口,结构如下(这是C#版本,Java版本见第7章)。

图1-13 设备相关类的实现

public class Device : IDevice
{
……
public string GetState(DeviceTypeDeviceType)…               //获取某类设备的状态信息
public void ReadFromStream(BinaryReaderbr)…                 //对象数据从二进制流中读取
public void WriteToStream(BinaryWriterbw)…                  //设备信息写入二进制
……
}

Device的属性很容易理解,主要关注3个方法。其中,属性读写方法的代码是重点。先来看写入方法。

public void WriteToStream(BinaryWriter bw)
{
StreamReadWrite.WriteShort(bw, SSID);                       //先写入设备本身有关属性
StreamReadWrite.WriteShort(bw, DID);   
StreamReadWrite.WriteBoolean(bw, Used);
StreamReadWrite.WriteString(bw, DeviceName);
StreamReadWrite.WriteString(bw, PositionDescription);
StreamReadWrite.WriteString(bw, IEEEOrMacAddress);
StreamReadWrite.WriteDouble(bw, X);
StreamReadWrite.WriteDouble(bw, Y);
StreamReadWrite.WriteDouble(bw, Z);
StreamReadWrite.WriteInt(bw, Tag); 
StreamReadWrite.WriteString(bw, Memo);
//以下写入6类子设备的信息
StreamReadWrite.WriteShort(bw, (ushort)DIDevices.Count);      //DI子设备的数量
for (int i = 0; i<DIDevices.Count; i++)
{
IDeviceDI device = DIDevices[i];  device.WriteToStream(bw);
}
StreamReadWrite.WriteShort(bw, (ushort)DODevices.Count);      //DO子设备的数量
for (int i = 0; i<DODevices.Count; i++)
{
IDeviceDO device = DODevices[i];  device.WriteToStream(bw);
}
StreamReadWrite.WriteShort(bw, (ushort)AIDevices.Count);      //AI子设备的数量
for (int i = 0; i<AIDevices.Count; i++)
{
IDeviceAI device = AIDevices[i];  device.WriteToStream(bw);
}
StreamReadWrite.WriteShort(bw, (ushort)AODevices.Count);      //AO子设备的数量
for (int i = 0; i<AODevices.Count; i++)
{
IDeviceAO device = AODevices[i];  device.WriteToStream(bw);
}
StreamReadWrite.WriteShort(bw, (ushort)SIDevices.Count);      //SI子设备的数量
for (int i = 0; i<SIDevices.Count; i++)
{
IDeviceSI device = SIDevices[i];  device.WriteToStream(bw);
}
StreamReadWrite.WriteShort(bw, (ushort)SODevices.Count);      //SO子设备的数量
for (int i = 0; i<SODevices.Count; i++)
{
IDeviceSO device = SODevices[i];  device.WriteToStream(bw);
}
}

程序流程十分简单。写入子设备的信息调用了子设备的写入方法。

读取数据的过程应该与写入的过程一一对应,不能有任何的顺序错误。具体请自行阅读代码。此方法虽然简单明了,但也有缺陷,就是升级比较麻烦,会造成前后版本数据格式的不一致,不能通用;如果考虑通用性,可以使用XML文档存储,但也有不安全因素,XML文档很容易被修改;也可考虑使用数据库存储,设计一个通用的数据库操作接口完成该任务,但由于数据库操作较复杂,需要安装相应数据库系统,对普通使用者而言过于复杂,体验差,因此本全栈项目没有采用。考虑通用性的原因,计划下个版本改用XML文档或JSON文档。

需要注意的是,Device类并没有实现操控子设备的方法。

6个子设备的实现都是类似的,主要实现了属性的存储读写方法,如图1-14所示。

图1-14 子设备类实现了属性的读写方法

由于把操控设备的方法转移到了上层的子设备系统SubDeviceSystem中,所以设备相关类的结构完全是确定的,系统可以提前把它编码实现。

2.MonitorSystem.cs

从图1-9的设计中知道,监控进程DMP是使用最上层的ImonitorSystemBase来管理整个设备的。对应地,需要实现这3个管理对象。我们发现,把设备的具体监控方法和数据处理方法放在接口IsubDeviceSystemMethod和ImonitorSystemMethod之中后,这些对象完全是结构固定的管理对象,可以提前设计类来编码实现。在具体编写设备监控驱动程序时,继承这些类,再实现IsubDeviceSystemMethod、IMonitorSystemMetho接口即可完成设计。

图1-15所示是基础监控系统类MonitorSystemBase的结构,主要完成整个监控系统内设备属性的数据读写、存储。目前的设计是把数据存储在一个单独的二进制文件中。

图1-15 基础监控系统类的实现

约定:DMP用文件“monitorSystem.iot”来存储监控系统数据。各设备最近一次的状态数据被保存起来。需要注意的是,该类并没有实现设备的监控操作,而是放在设备监控驱动程序中实现,因为它们与具体设备有关。

类似地,图1-16所示是基础设备系统类的结构。

图1-16 基础设备系统类的结构

与基础监控系统类一样,GetBytes和ReadFromBuffer方法可方便对网络通信数据进行转换处理。

图1-17所示是基础子设备系统类SubDeviceSystemBase的结构。可以看到,它实现了数据的读写操作。

图1-17 基础子设备系统类的结构

至此,监控驱动程序绝大部分不变的内容已经实现,系统核心协议也体现在其中。枯燥无味的部分结束了,接下来该进入系统的设计开发了。

提示:本章是整个平台系统的底层基础和原理。建议花几天或几周的时间来完全理解其内容,这对于后续提高开发效率至关重要。


相关图书

一书读懂物联网:基础知识+运行机制+工程实现
一书读懂物联网:基础知识+运行机制+工程实现
内网渗透技术
内网渗透技术
华为HCIA-Datacom网络技术学习指南
华为HCIA-Datacom网络技术学习指南
Dapr与.NET微服务实战
Dapr与.NET微服务实战
CCNP企业高级路由ENARSI  300-410认证考试指南
CCNP企业高级路由ENARSI 300-410认证考试指南
华为网络技术系列 园区网络架构与技术(第2版)
华为网络技术系列 园区网络架构与技术(第2版)

相关文章

相关课程