## 前言:     其实做这个智能家居系统我还是因为学校的毕业设计,距离上篇文章发布已经过去了20多天了,之前想着只是做一个烟雾报警,然后通过Zabbix进行报警,但是通过这20多天的设计,我发现实现报警的功能其实除了邮件,还有短信、微信、甚至电话。但是因为各种原因,比如.....钱0.0,哈哈哈,因此我计划用企业微信进行一个报警,然后貌似通过普通微信进行一个简单的交互(interactive),还是不错的选择,并且做出来的效果也很棒。   最后想说的是,下面文章如果有不正确的地方,还请多指正。   ## 一、实现内容(重要)    前面我已经做了阐述,要做智能家居控制系统,除了实现各种报警之外,还要做到和Raspberry的交互,通过手机来控制它的输出,和执行相应的动作,还有最重要的一点,后面会用到运动识别检测。。。噢耶:)   ### 1.实现目标   下面是实现的具体目标,分三部分:交互端,报警端,摄像头运动物体检测报警端。   普通微信端交互控制: >* 通过“MQ-2烟雾报警模块”实现对家中的气体检测。 * 通过“DHT11温度检测模块”实现对家中的温度测控。 * 通过“继电器模块”实现对家中电器设备的开闭控制。 * 随时查看摄像头所拍摄的照片   企业微信端的报警: >* 当家中温度出现异常,进行报警。 * 当家中有害气体含量异常,进行报警。 * 当家中有继电器控制的电器设备异常开启或者关闭,进行报警。   家中运动物体识别检测报警: >* 基于OpneCV算法实现对摄像头运动物体的检测,识别,拍照,最后上传到企业微信端。 ### 2.注解 **   这里我想大家解释一下我的计划组成部分:**   ①微信端交互功能。 >  通俗的讲,也就是用户通过菜单的指引,在微信端输入输入一系列关键字,然后Raspberry识别,并进行相应的动作,比如控制灯的开关啦,检测室内温度啦,看看摄像头拍的什么照片啦等等,这些都是通过调用API接口来实现。   ②微信端报警功能 >  报警我计划使用的还是微信,但是这个不是普通微信,而是企业微信,也就是可以类似于纯净版的微信,QQ的TIM,它的功能大部分和公司的相关业务靠的很近,用起来很舒服,比如当家中有不明气体产生,或者温度湿度超过我们规定的阈值,我们都进行报警,或者当离开家的时候,忘记关闭电器开关,也会进行一个提醒,这里用到的也是企业微信提供的API接口,其实用法是差不多的,但是总结下来。还是企业微信在调用的时候会方便一点,最起码不用扫码,哈哈哈哈。   ③运动物体检测。 >  为什么要加这一块儿呢?试想一下,当家中没有人的时候,可能会有陌生人闯入,我们这时该如何检测呢?答案当然是:PiCamera+OpenCV。这里简要介绍一下,PiCamera是Raspberry官方的摄像头,可以用来拍照,录制视频,一般为800万像素的比较好,某宝或者某东都有卖的,为了能检测出来摄像头当前区域的物体活动迹象,我们用到了跨平台计算机视觉库:“OpenCV”,很强大的一个库,支持多种语言,我在这里就Python2写了,因为Raspbian系统当中Python2默认是可以安装OpenCV2,而Python3是好像不能装的,总是失败,想要安装OpenCV3在Python3的话,需要自己去找网上的教程,需要源码编译,这里我就不过多赘述了。通过编写一定的算法,对图片的每一帧进行一个比对,灰度化,二值,过滤出轮廓,然后进行统计,通过设定的阈值,来判断这一帧是否有像素的变化,从而感知是否有物体运动,这里会用到很多的数字图像处理知识,希望做这一块儿的读者,可以先去网上搜下关于数字图像处理的书籍,了解基本概念很重要。     最后附上我的文件结构,不太符合Python工程标准,嘻嘻,野生程序员0.0... ![](https://s1.51cto.com/images/blog/201804/11/9da017f7935388963cbf9ac35030da58.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)   > * interactive # 用户微信交互 >> * air # 烟雾报警测试目录 > >>MQ-2_module.py > >* camera # PiCamera拍照录像目录 >> >photo.py > >>video.py > >>picture >> >video >>* face # 人脸识别目录 >>>friend.py >>>haarcascade_frontalface_alt2.xml >>* relay # 继电器模块测试目录 >>>RELAY.py >>* temp # 温湿度测控目录 >>>DHT11.py >>* weixin # 微信交互主文件目录 >>>weixin_group.py > * warning # 企业微信报警检测 > >* main.py # 主文件 > >* Face # 运动物体报警检测目录 > >>conf.json > >>motion_detection.py > >>picture >>* Temp # 温湿度报警检测目录 >>>DHT11.py >>* Weixin_warning # 企业微信报警调用目录 >>>picture_send.py >>>video_send.py >>>wechat.py   **   上述的所有点我会在后面一一进行详细讲解,并实现其功能。**       ## 二、继电器功能实现   ### 1. 继电器模块简介     继电器(英文名称:relay)是一种电控制器件,是当输入量(激励量)的变化达到规定要求时,在电气输出电路中使被控量发生预定的阶跃变化的一种电器。它具有控制系统(又称输入回路)和被控制系统(又称输出回路)之间的互动关系。通常应用于自动化的控制电路中,它实际上是用小电流去控制大电流运作的一种“自动开关”。   按照我的话说:继电器模块是通过Raspberry输入高低电平到IN口,来控制继电器的当中的电磁铁铁芯和衔铁的相互作用,当给线圈通电的时候,就会产生一个磁场,使得电磁铁铁芯被吸引到一个端口,从而实现端口的开路或者闭路,进而控制电器的开关。   继电器靓照: ![](https://s1.51cto.com/images/blog/201804/10/eff045dfea5e433f5a3346ab0bd71d05.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)     官方简介:(便于后面的连线和理解原理) >>①采用松乐继电器,单刀双掷,一个公共端,一个常开端,一个常闭端。 ②低电平吸和,高电平释放,吸和时状态指示灯亮起,否则是熄灭状态。 ③VCC系统电源,JD_VCC继电器电源,5V电源 ④继电器最大输出:直流30V/10A,交流250V/10A ⑤VCC:系统电源正极 ⑥GND:系统电源负极 ⑦IN1--IN2:继电器控制端口   ### 2. 三种工作原理介绍     我在这里使用了一个模块,是松乐的SRD(T73)型号家用继电器,这里,分为三个端,依次是:“NO”,“COM”,“NC”,后面还有个“IN”端,用来连接树莓派的GPIO口,根据它输入的高低电平,来进行继电器的控制,也就是: >>* NC —— Normal Connected ,指常闭端(接点),平常(不加电时)的状态 >* NO —— Normal Open,指常开端(接点),平常(不加电时)的状态 >* COM —— Common,指公共端,根据是否加电,可以与NC端或NO端接触     这里有两种模式,常开,常闭。我经过测试以后,做了一个总结: >  1. 当连接“COM”和“NC”的时候,灯常亮,也就是没有给任何电平的时候,它就连通了,说明这个是常开模式,这个时候通过“IN”给一个高电平,或者把“IN”线拔掉,都会导致这个灯是亮的,不会受到影响,线路为通路;但当通过“IN”给一个低电平的时候,这个时候灯就灭了,但是可以看到工作模式灯亮起,也就是说,这个时候由于通电的原因,电磁铁把单刀开关吸到了断路的端口上,这也就说通了,当不给电或者给一个高电平的时候,会让磁铁没电,不会进行吸附耦合,那就是一直接着通路了,默认有电。 >   2. 当连接“COM”和“NO”的时候,灯常灭,也就是没有给任何电平的时候,它是默认断开的,说明这个常闭模式,这时候通过给“IN”给一个低电平,会发现灯亮了,很神奇,这是为什么呢?在正常情况下,在树莓派关闭,断电时候,或者把“IN”线拔了,或者给一个高电平,都会导致灯灭;只有给到低电平的时候,才会使工作灯亮起,并且会通电,把电磁铁把单刀开关吸到了开路的那一端,在不给低电平的时候,此时里面的磁铁是不会移动的。     不过这测试的结果好像颠覆了我的世界观。。。。。因为上面测试的结果,和前面对“常开端”和“常闭端”的定义好像正好相反,我也不知道为什么,可能是他们把线接反了吗? 反正就是记着他的原理,然后用法就是这么用的就行了0.0 ** 总结:** >  ** 当输入一个低电平的时候,总会导致继电器里面的电磁铁有磁性,从而把单刀开关从原始的一头,切到了另一头。记住这里是给控制器的低电平哦,从而控制了继电器高电平,进而通过电磁铁把这个单刀吸到了有电的一方,就是这么简单。搞清楚哦 !** >*** 注意 ***:上面叙述的为什么和网上正好相反,低电平的时候,电磁铁带电,并且可以使得电器电路闭合 >   因为这是** “继电器模块 ” **,也就是结合了继电器的电路,里面是是带有一个控制器的,也就是说对于用户,这些暴露的I/O口是连接控制芯片的,然后通过芯片来控制继电器,所以低电平是给控制器一个信号,然后让控制器给继电器一个高电平,那么内部的线圈就带电,然后产生磁场,把电磁铁铁芯吸到了一端。反正要是还有些迷糊的话,大家可以找找网上的关于继电器的相关知识。   ***记住:给“IN”一个低电平就是使磁铁有磁性,改变当前单刀开关的位置 *** ***    为了方便,我后面会使用第二种连线方式,也就是“常闭”模式,为了保证安全!!! ***   ### 3. 继电器连线     开始接线,我们的目的是要通过继电器控制家用电器,也就意味着需要把电线的两头接到继电器的两个端口,来通过控制继电器,从而控制两个端口是否是闭合还是断开,先上个图吧。 ![](https://s1.51cto.com/images/blog/201804/10/f20bf1dcd60a38a621370a4267177b91.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)   #### 3.1 I/O口连线及解释: >>  ① 通过跳线帽把JD_VCC和VCC进行了一个连接,也就是意味着,通过最右边的VCC,就把电源除了供给给继电器模块的控制器之外,也并联给了继电器本身一个5V的电源,省去了插两根电源线的麻烦。   ②当然他们的地线GND,则是都需要连接在Raspberry的GND上,这是毋庸置疑的。   ③IN1和IN2,这是控制继电器的I/O口,通过对两个口的高低电平的输入,来控制继电器的断开和闭合。   ④ 看到正面这里,我为了试验的真实性,在五金店买了一根带插头的电线,和家用四孔插座,带插头的电线,一端把线接在四孔插座的后面,然后在中间位置,剪开其中的一根线(双头插头默认都是一根零线,一根火线,一共两根),但别都剪短断哈......然后把这根剪开的两端分别接在** “公共端口COM” ** 和** “常开端“NO” **,就像我前面说的,根据自己测试的结果,我的需求是:在Raspberry没有开机,或者不给任何电平的时候,那它就是常闭的,不带电的,只有给它一个低电平的时候,它才会亮。因此我们要接在公共端和常闭端,但是由于我也不知道这个继电器是怎么设置的,只有接公共端和常开端,它才能达到我们的目的,虽然很奇怪,但是先这样把。   这里放一个连接公共端和常开端的图: ![](https://s1.51cto.com/images/blog/201804/10/009aa36b63918e967d7c8a2dcbf650a7.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)   ***   注意: 由于连接的是220V的电压,所以很具有危险性,因为闭合开关以后,继电器模块底部有一定的几率带电,所以尽量在下面垫一层纸板,用绝缘胶带缠上,通电以后,不要触摸,可以买一根电笔来测试连个端口是否有电。***   ### 4. 命令行测试     这里安排了一个简单的测试,是为了保证正确的连线,用到了一个软件wiringPi和上篇文章提到过的Python当中的RPi.GPIO库。   #### 4.1 wiringPi介绍:   WiringPi 是应用于树莓派平台的 GPIO 控制库函数,WiringPi 遵守 GUN Lv3。wiringPi 使用 C 或者 C++ 开发并且可以被其他语言包转,例如 Python、ruby 或者 PHP 等。 wiringPi 包括一套 gpio 控制命令,使用 gpio 命令可以控制树莓派 GPIO 管脚。用户可以利用 gpio 命令通过 shell 脚本控制或查询 GPIO 管脚。   #### 4.2 wiringPi安装: >使用 GIT 工具,通过 GIT 获得 wiringPi 的源代码。 git clone git://git.drogon.net/wiringPi cd wiringPi ./build build 脚本会帮助你编译和安装 wiringPi。   #### 4.3 命令行测试: ``` ① gpio readall ```   读取当前Raspberry的GPIO引脚情况。在这个地方可以查看40个的BCM编码规则;wPi编码规则;Name:引脚名称(有些I/O兼备多种功能的);Mode:模式,此时这个引脚是输入模式还是输出模式(默认开机的时候是输入模式),V:此时引脚的电平:“1”为高电平,“0”为低电平(大部分默认开机是低电平),可以看到图中方框圈住的是引脚,它是此时是输入模式并且是高电平。 附图: ![](https://s1.51cto.com/images/blog/201804/10/4f728cd881586b08fe3281b5afadf6f7.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)   ``` ② gpio mode 11 out ```   我采用了wPi编码规则,根据刚刚我上面的连线,我选取了IN2引脚,通过杜邦线把它接在了Raspberry的26号引脚上,就是中间的“Physical”物理引脚命名规则的26号引脚,而26号引脚对应的是wPi命名规则的“11”号引脚,然后用上面的命令设置该引脚为输出模式“OUT”,这样的话,才可以输出高低电平;相反要接收并且判断连在某个引脚上的电平是高电平还是低电平,那么就设置为输入模式“IN”,用来接收。这个地方大家一定要分清。 附图: ![](https://s1.51cto.com/images/blog/201804/10/1905153215df7620bf35a0f56c8dde74.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)   ``` ③ gpio write 11 0 ```   向wPi命名规则的11号引脚输出一个低电平,然后就会发看到“V由1到0”,并且灯也亮了,状态灯也亮了,还有刀片的切换的声音,说明灯光打开成功啦。   附图: ![](https://s1.51cto.com/images/blog/201804/10/1f40a32711dbe21f19213e9f9c5e1a25.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)   状态灯图: ![](https://s1.51cto.com/images/blog/201804/10/85e01a988b187c26cba61b9b7e78edd7.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) ``` # gpio write 11 1 ```   向wPi命名规则的11号引脚输出一个高电平,然后就会发看到“V由0到1”,并且插在插座上的灯灭了,同时状态灯也灭了,同样刀片有切换的声音,说明灯光关闭成功啦。   附图: ![](https://s1.51cto.com/images/blog/201804/10/226f6bd7740e70b6f4e38cbe4dcad93b.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)   ``` # gpio read 11 ```   可以读取刚刚定义好命名规则的引脚的状态,当然就是“0”或者“1”啦。    附图: ![](https://s1.51cto.com/images/blog/201804/10/d710d533f7533216723f24a889db8502.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)     当你上面的都做出来的时候,那么恭喜你,成功地通过命令行的方式控制了连接在继电器模块上IN2引脚,到Raspberry的GPIO口的高低电平,进而控制了灯的开关,好像是不太难吧。。。只要按照我的连线,大家肯定可以做出来,并且可以多几次的输出高低电平,来感受继电器的工作原理和工作方式,并且熟悉这样命令行界面控制GPIO口的方式,很方便,很快可以的到一些结论,并且前面的对MQ-烟雾检测模块的测试,也可以通过上面的 ** gpio read 11 **的命令来查看引脚状态。 >> ***注意: > 上面对于一个引脚的输入高低电平,获取高低电平状态,这一切的一切的都需要你必须在提前定义好了引脚的命名规则,是wPi还是BCM。不然是无法成功的!!!***   ### 5. Python代码测试     还是老样子,我使用的是Python3,通过调用RPo.GPIO库来实现在脚本当中,对GPIO的操作。   #### 5.1 程序段一 ``` #!/usr/bin/env python3 import RPi.GPIO as GPIO import time relay = 40 # 采用BOARD编码,40号引脚,来控制继电器 buzzer = 32 # 采用BOARD编码,32号引脚,来控制蜂鸣器 GPIO.setmode(GPIO.BOARD) # 声明BOARD编码规则 GPIO.setwarnings(False) GPIO.setup(relay, GPIO.OUT) # 设置继电器的引脚为输出模式 GPIO.setup(buzzer, GPIO.OUT) # 设置蜂鸣器的引脚为输出模式 ```   ①对GPIO库进行一个引用,然后先定义引脚号,并且定义BOARD编码规则,这里需要看着前面的GPIO对应表,不要写错了,需要注意是当我们每次去执行完脚本以后,GPIO引脚有可能没有关闭调用,或者之前引脚在设定之前就是输出模式,要是再次定义它为输出模式,就会产生一个警告,很烦人,那么如何避免这个问题呢?就是中间的那条命令** GPIO.setwarnings(False) **,需要大家特别注意哦!   #### 5.2 程序段二 ``` j = 0 while j<5: GPIO.output(relay, GPIO.LOW) print ("灯亮了") GPIO.output(buzzer, GPIO.LOW) print ("蜂鸣器鸣响") time.sleep(3) GPIO.output(relay, GPIO.HIGH) print ("灯关闭") GPIO.output(buzzer, GPIO.HIGH) print ("静默") time.sleep(3) ```   ②这里进行了一个5次的程序执行,就拿出我们前面的结论:控制继电器,当给它一个低电平的时候,继电器模块的控制器则给它一个高电平,继电器内部线圈带电,产生磁场,导致单刀吸附到闭合那一端,这样上面我们接线的两个端口“COM”和“NO”就是通路了,就好像他俩没有被剪开一样,这个时候,灯就亮了,然后我设置了一个低电平给蜂鸣器,蜂鸣器也响了;后面则是给继电器IN2一个高电平,然后灯就关闭了,同时也给蜂鸣器一个高电平,蜂鸣器静默。   相信大家也看到了,我进行了两次的睡眠** “time.sleep(3)” ** ,这是因为当进行了电平转换以后,一个是我们需要检验结果,所以有个状态驻留时间,尤其是最后的那个睡眠,记得必须加上,否则看不到效果,因为下次循环又转换为低电平,在这个期间,需要时间去让大家看出效果,就是灯是一直灭着的,同时也避免继电器瞬间切换产生的异常,要是大家感觉有些绕,可以自己进行一个实验,尝试去掉睡眠,这样也有助于你理解。   > >***   最后的效果就是:灯亮起三秒,蜂鸣器响起;灯灭三秒,蜂鸣器静默;如此往复5次。 >   设计这个程序的效果就是让大家能熟悉继电器的工作原理,以及Raspberry如何调用,博主可谓用心良苦啊0.0***   ## 三、温度传感器功能实现     温度传感我觉得还是整个项目里比较有分量的,也是比较难的部分,这是为什么呢?其实大家可以在网上搜到很多关于温度传感器DHT11在Raspberry上的实现,有用Python写的,有用C写的,各种方法不尽相同,但是绝大部分都不够详细,比如程序当中有些语句 ** “while GPIO.input(channel_point) == GPIO.LOW:” **。这个就需要去结合DHT11模块本身的工作模式去理解,就会好很多,后面我会逐句一一进行讲解。   ### 1. DHT11简介 >* 可以检测周围环境的湿度和温度 * 湿度测量范围:20%-95%(0 度-50 度范围)湿度测量误差:+-5% * 温度测量范围:0 度-50 度 温度测量误差:+-2 度 * 工作电压 3.3V-5V * VCC 外接 3.3V-5V * GND 外接 GND * DATA 小板开关数字量输出接口,可接I/O口   附一张帅气的自拍: ![](https://s1.51cto.com/images/blog/201804/11/a51f45ba8094963b6187df38f2c9918f.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)   ### 2. DHT11接线 > ***这里连线需要注意的点是:    经过测试,必须要在DHT11模块电源和DATA数字接口当中并联一个电阻,否则测量出来的值非常的不准确,很多网上的资料没有说并联电阻这个事情,导致我程序写完,总是测不出温度的数值,还以为是程序有问题,所以走了很多的弯路,望大家千万注意。***   下面结合图片进行连线讲解: **①原理图:** ![](https://s1.51cto.com/images/blog/201804/11/9663f6297de43a4aa463ad62ed03dc97.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)    上面这张图是设计模块的使用说明电路图,很明显,在VCC和DATA需要并联一个电阻,貌似是需要做一个上拉电阻,来增加DATA的输出能力,并且图中是要求并联4K左右的电阻,但是实际测试得出结论,只要有2K就够了。   **②虚拟连线图:** ![](https://s1.51cto.com/images/blog/201804/11/5fe7285a2feef9f699bf83ca444e6aa0.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)    红色的是电源线,连接在Raspberry的3.3V电源上,蓝色的是数据口DATA,黑色的是GND,就是按照上面图并联的电阻,不理解的多看看电路图。   **③实际效果图:** ![](https://s1.51cto.com/images/blog/201804/11/e8ac6c627de411c7a9432802b8495057.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)    由于手头只有1K的电阻,所以简单的进行了一个串联,有些乱,但是这样连线,再跑程序就完全没有问题0.0。   ### 3. 驱动程序      程序实现的过程还是比较简单的,但是需要结合DHT11的模块的说明来进行讲解,这样的解释程序的方式,相信大家读完会才可以更好的理解,希望仔细阅读这一部分。 > *** 注意: >    把引脚设置为输出“OUT”,意味着需要Raspberry输出高低电平信号给data数据口。 >    把引脚设置为输入“IN”,意味着Raspberry准备接收data数据口给它引脚的高低电平信号***      下面是产品公司对DHT11整个工作流程的时序图的概括,这其中包括了初始化操作。 ![](https://s1.51cto.com/images/blog/201804/11/bb8bebf9d35db77565de08834370baac.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)   #### 3.1 DHT11初始化(步骤一)    官方原文:DHT11 上电后(DHT11 上电后要等待 1S 以越过不稳定状态在此期间不能发送任何指令),测试环境温湿度数据,并记录数据,同时 DHT11 的 DATA 数据线由上拉电阻拉高一直保持高电平;此时 DHT11 的DATA 引脚处于输入状态,时刻检测外部信号。   ``` #!/usr/bin/env python3 import RPi.GPIO as GPIO import time def dht11(channel): channel_point = channel data = [ ] GPIO.setmode(GPIO.BOARD) # 设置编码格式 time.sleep(1) ``` >* 首先进行了库的调用,为了在后面方便调用整个文件,并作为一个模块导入其他文件,所以这里定义成了函数。 * ** “channel_point”**是函数输入的参数,作为引脚变量。 * ** “data[ ]”** 是一个空字典,用来存储后面data引脚发来的40位数据,我会在后面细讲。 * 这里我还是使用BOARD编码命名规则进行定义。 * ** “time.sleep(1)” ** 的目的就是程序开头我叙述的那样,当刚刚通电,DHT11模块需要进行一个初始化操作,不能发送任何指令,所以要等待一秒   #### 3.2 DHT11初始化(步骤二)    官方原文:微处理器的 I/O 设置为输出同时输出低电平,且低电平保持时间不能小于 18ms,然后微处理器的 I/O设置为输入状态,由于上拉电阻,微处理器的 I/O 即 DHT11 的 DATA 数据线也随之变高,等待DHT11 作出回答信号。   ``` GPIO.setup(channel_point, GPIO.OUT) # 设置为输出 GPIO.output(channel_point, GPIO.LOW) # 输出一个低电平 time.sleep(0.02) # 睡眠0.02秒 GPIO.output(channel_point, GPIO.HIGH) # 输出一个高电平 GPIO.setup(channel_point, GPIO.IN) # 设置GPIO引脚为输入 ```      由于步骤二要求的是需要微处理器,也就是我们用的Raspberry,输出一个低电平,并且保持18ms以上,然后设置为输入模式,再把电平拉高,进行一个等待。 > * 为了达到上面的目的,首先设置GPIO引脚为输出模式,然后给一个低电平。 > * 这里为什么睡眠** “0.02s” **呢?前面已经讲到:必须要有不小于20ms的低电平,这是初始化的必要操作。 > * 然后输入一个高电平给data,由于上拉电阻的原因,导致data现在被拉高了, 但要是GPIO这里不拉高的话,就会有电流,因为前面还是输出给data低电平嘛,所以这里 保持电平的高低一致。。。 > * 最后使得引脚为输入模式,准备接收来自data的信号。   #### 3.3 DHT11初始化(步骤三)    官方原文:DHT11 的 DATA 引脚检测到外部信号有低电平时,等待外部信号低电平结束,延迟后 DHT11 的 DATA引脚处于输出状态,输出 80 微秒的低电平作为应答信号,紧接着输出 80 微秒的高电平通知外设准备接收数据,微处理器的 I/O 此时处于输入状态,检测到 I/O 有低电平(DHT11 回应信号)后,等待 80 微秒 的高电平后的数据接收。 ``` while GPIO.input(channel_point) == GPIO.LOW: continue while GPIO.input(channel_point) == GPIO.HIGH: continue ``` >这两句while语句还是比较难理解的,需要结合上面的官方步骤来看。 > * ***第 一个while语句: ***这里就是接收GPIO的INPUT输入的低电平,这是data端发过来的,作为应答信号,并且是80微秒的低电平,由于实际生产环境当中,80微秒不一定准确, 因此,这里用了while循环,当树莓派接收到的GPIO为低电平的时候,就一直在while循环,并且是跳过下面的程序的,直到接受的GPIO口为高电平了,这里while循环后面的判断条件就为假了,这时候就不循环了。然后就往下走 > * ***第二个while语句 : *** 当上面“80微秒”低电平结束以后,又会发送一个“80微秒”的高电平给树莓派的GPIO,来告诉树莓派,要准备发送数据了,准备接受了!!!   #### 3.4 DHT11采集数据    官方原文:由 DHT11 的 DATA 引脚输出 40 位数据,微处理器根据 I/O 电平的变化接收 40 位数据,位数据“0”的格式为: 50 微秒的低电平和 26-28 微秒的高电平,位数据“1”的格式为: 50 微秒的低电平加 70 微秒的高电平。 位数据“0”、“1”的图两张: ![](https://s1.51cto.com/images/blog/201804/11/b98145d516046adf168ad2a2bc48bd9c.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) ![](https://s1.51cto.com/images/blog/201804/11/cb164d7e430da8e86fc5bced97d9c641.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)      用我自己的话概括就是:经过上面的初始化以后,data开始发送数据,是40位的高低电平,也就是40位的“0”和“1”,然后开始发送数据了,位数据“0”的格式为:50微秒的低电平和26-28微秒的高电平,位数据“1”的格式为:50微秒的低电平和70微秒的高电平。 > ***    其实这里就可以看出,只需要判断后面的高电平是输入给GPIO多少微秒,就可以来判断是“0”还是“1”,就是一个时间长短的判断而已,认真理解,这对于后面的程序理解有很大的帮助。***   ``` data_number = 0 # 设置计数器 while data_number < 40: high_time = 0 # 统计 “while高电平循环”次数 while GPIO.input(channel_point) == GPIO.LOW: # 接收一个50微秒的低电平 continue while GPIO.input(channel_point) == GPIO.HIGH: # 开始接收高电平 high_time += 1 # 开始对上面的while的循环次数进行计数 if high_time> 100: # 当循环次数超过100,退出本次循环 break if high_time < 8: # 对前面的while循环的次数进行判断 data.append(0) # 如果小于8追加位数据“0”到字典当中 else: data.append(1) # 大于8的追加位数据“1”到字典当中 data_number += 1 # data_number自加1 print("Modular is working.") print(data) ``` > * ** data_number ** 的作用就是计数40次,也就是写入数据40次,因为DHT11就是传输40个二进制数,五个字节,需要特别注意,用于下面的while循环判断。   > * 然后Raspberry的引脚开始接收一个50微秒的低电平,while检测只要是低电平就跳过,不执行底下的程序,直到高电平的到来,这个while条件为假,它就退出了,并且接着自动执行下面的同级程序。这是因为我前面说的,我们只统计后面高电平输出的时长,低电平的不管,因为高低电平都是50ms,所以执行下面的程序没有意义。 >   > * 开始接收高电平,这里通过高电平的输出微秒的不同,来判断这整个输入的是“0”还是“1”,一般情况下“0”是高电平输出的时间为26-30微秒,如果输出的是“1”,那么输出高电平的时间为70微秒,也就是通过输出高电平的时长来判断此时输入数据是“0”还是“1”。    这里做一个计数,意思是这个*** while循环,执行一次需要“4微秒”***,那么也就是说:例如在输出一个“0”的时候,需要输出“28微秒”的高电平,而while循环的执行的逻辑判断为后面只要是一直输入的高电平,那么这个while循环就一直循环,不往下走,直到这“28微秒”的高电平输出完了,while循环判断data引脚是低电平了,那么才执行while循环下面的指令,但这将近 “28微秒”的高电平,由于while循环本身执行也需要时间,也就是“k”会计数这个while循环在这次的高电平输出,共循环了几次,依照前面给出的时间来算,*** 当输入的是“0”的时候,高电平 输入“28微秒”,while要循环7次或者8次,才会循环到这次高电平输入结束。 同样的,如果输入的是“1”,那么就是“70微秒”,也就是说,这次的while要循环17次以上,才会循环到这次高电平输入结束,也就是“k”为17,最后其实就是判断“k”的次数,来判断data到底是输入的“0”还是“1”*** ,这里注意读者要认真理解。    当出现意外,也就是高电平循环时间过于长,导致while陷入了死循环,这样就需要** if **判断,当“k>100”,其实也就是while循环了100次以上、高电平输出了** 0.4s**以上,那么说明这个数据肯定有问题了,因此需要退出这次循环,并且数据作废。   > * 最后的** if high_time < 8: ** 的判断,其实就是上面说的,根据while循环的次数来判断,每次发送来的数据,到底是“0”还是“1”,小于8那么肯定说明接收的是位数据“0”,反之肯定是1啦,要是这里想不通,就多看看中间那部分对“while”循环次数的技术的原因。   >***    上面的程序可以说是整个程序的核心部分,通过前面官方文档中data引脚输出位数据的特点,写出的判断数据的程序,可以说是很厉害了。***   #### 3.5 DHT11数据计算    前面通过对while循环次数的计算得出了data引脚输出的位数据是“0”还是“1”,并且全部追加到字典当中,下面开始对字典当中的值进行取出和计算。    官方文档:数据格式:8bit 湿度整数数据 + 8bit 湿度小数数据+8bit 温度整数数据 + 8bit 温度小数数据+8bit 校验位。共40位,其中校验位是为了判断前面获取的温度和湿度是否数据由错误。 对于这个40位的“0”和“1”,我们如何计算呢?下面给出了一个示例: >接收到的 40 位数据为: 0011 0101 0000 0000 0001 1000 0000 0000 0100 1101 湿度高 8 位 湿度低 8 位 温度高 8 位 温度低 8 位 校验位 计算: 0011 0101+0000 0000+0001 1000+0000 0000= 0100 1101 接收数据正确: 湿度:0011 0101=35H=53%RH 温度:0001 1000=18H=24℃      上面用到的知识基本上就是2进制转换为10进制的操作,这里有个公式就是:就是2的次方乘以每个对应的0或者1,比如上面的温度的计算,“18H”就是先从二进制转换为16进制,24则是10进制;2的四次方 + 2的三次方=24,进制方面的知识可以去百度,但是必须要搞懂,因为下面的程序就是基于这个公式进行进制转换的。 ``` # 这里进行一个分片,一共40位的列表,先取出来前八个 hum_bit = data[0:8] # 湿度高八位,其为整数位 hum_decimal_bit = data[8:16] # 湿度低八位,其为小数位 temp_bit = data[16:24] # 温度高八位,其为整数位 temp_decimal_bit = data[24:32] # 温度低八位,其为小数位 check_bit = data[32:40] # 校验位 # 温湿度的初始值 hum = 0 hum_decimal = 0 tem = 0 temp_decimal = 0 check = 0 # 遍历字典当中的数据 for i in range(8): hum += hum_bit[i] * 2 ** (7 - i) hum_decimal += hum_decimal_bit[i] * 2 ** (7 - i) tem += temp_bit[i] * 2 ** (7 - i) temp_decimal += temp_decimal_bit[i] * 2 ** (7 - i) check += check_bit[i] * 2 ** (7 - i) ```    对于上面的遍历的公式:比如第六位的数字“1”,那是就用“1”去乘以2的5次方,因为它是从0开始的,所以到第六位就是“5”,也因此,后面算,也就是要减一位,也就有了上面的式子,认真理解。 最后就算出来10进制的数据了,一共5个数据。   #### 3.6 DHT11数据整合判断    这块儿程序是进行逻辑判断,如果有前四个数据加起来的** sum **的值不等于** check **校验位,那么说明数据有误,如果相同,说明数据是正常的,后面进行了数据的打印和整合。 ``` sum = hum + hum_decimal + tem+ temp_decimal info_dht11 = ''' 温度:{0}.{1}°C 湿度:{2}.{3}'''.format(tem,temp_decimal,hum,hum_decimal ) print(info_dht11) return check,sum,info_dht11 if __name__ == '__main__': dht11(38) dht11(29) ```      由于这个模块我是打算在后面把它作为一个微信交互程序调用的模块的,所以我的写法可能和前面叙述的不一样,我在这里说明一下,尤其是和校验位的比较,我放在了微信交互程序当中,为了把参数传到另一个文件的函数当中,我就运用了Python当中最伟大的发明 ** return **----一个可以返回任何值的关键字,我把这三个参数作为返回值传到微信交互函数当中,进行逻辑判断,并且温湿度的信息也一并传过去了。   > ***注意: 为了调试:我加了一个**“ if __name__ == '__main__': ”**,对函数进行调用,并且输入一个参数,这是两个DHT11温湿度模块的data引脚连接Raspberry的引脚,而输入的参数就是引脚编号(BOARD命名规则)。***   ## 四、总结    上面讲的只是用户微信需要的两个功能模块;后面我就开始讲Raspberry对PiCamera的调用,拍照,录像;对发来的照片进行简单的人脸识别;最后通过微信交互主程序对前面所有的功能进行统一调用,就完成了交互的全部功能。   >    企业微信的报警系统,还在不断的完善,但是已经基本实现了:*** 温湿度异常的报警,烟雾模块检测到异常的报警;继电器控制的电器开关异常开路或者闭路;通过PiCamera对监测区域内的运动物体检测并报警 ***      我会把上面所有实现的方法在后续写出来,就看这篇文章和下篇文章有多少人看了0.0....