前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于人体红外热释电检测的树莓派邮件报警器

基于人体红外热释电检测的树莓派邮件报警器

作者头像
聪明的瓦肯人
修改2020-02-26 20:54:51
1.4K0
修改2020-02-26 20:54:51
举报
文章被收录于专栏:工科生日常工科生日常

只要标题够唬人,你们就会点进来,内容什么的其实都无所谓,无聊已经逼迫帅气的我做出了这种东西?!

——聪明的瓦肯人

01

写在前面

在一个闲人免进的地方

老板恰巧少了一个摄像头

这个时候

你就可以帅气的出场

带着这款高端大气上档次的

树莓派邮件报警器

承担起临时重任

系统的核心

其实就是python邮件收发功能

使用的是SMTP与POP3协议

配合外设人体红外传感器

实现人员检测警报

02

说说硬件

正如封面所示

硬件也就这样

左边是树莓派3B+单板微型电脑

搭载Arm架构的博通CPU

右边白色圆状物体就是

人体红外热释电传感器

红色杜邦线是正极VCC

蓝色杜邦线是负极GND

黄线是信号线,信号为3.3V

再次检测的封锁时间默认为2.5s

检测到人后延迟信号时间默认为0.5s

面包板上

下面的红色LED是人员检测信号灯

上面的红色LED是闪烁驱离警报灯

当然你也可以添加蜂鸣警报器,喇叭等等

在这里只是做一个演示

所以器件简单

现在

我们来聊聊这个人体红外传感器

人体红外热释电传感器,利用的是人体37摄氏度体温所发出的大约9~10微米的红外线,通过菲涅尔透镜(正上方图)聚焦在热释电元件上,热释电元件一般由单晶、压电陶瓷、高分子薄膜制成,其遇热会在晶体两端产生电荷相反数量相等的电荷。

该传感器有两个热释电元件,但是极性相反串联,正常情况下两热释电元件自发极化产生的或是环境温度变化产生的电荷会相互抵消而不会放电产生信号,巧妙的设计大大加强了它的抗干扰能力。

只有当人体移动时,两元件接收的热能不相等,导致极化电流不相等,无法抵消,从而输出电流信号;当人体静止时,电流消耗完毕,温度也不再变化,两元件处于平衡状态,信号消失;需要注意的是,突然的阳光照射会导致电荷不平衡而输出信号等等。

简而言之,任何造成两元件受热不一致的情况,都会造成传感器放电直至电荷消耗完毕。

03

看看软件

正所谓

python在手,天下我有

本次依然使用优雅的python编程

代码在树莓派中编写运行

这意味着

你得首先会树莓派基本操作

烧录镜像系统,远程登录等等

主要用到

SMTP发邮件,POP3收邮件

所以你需要两个邮箱

需要注意的是

无论是SMTP还是POP3

都是在使用第三方操纵邮件

以QQ邮箱为例

你需要在代码中写入口令

而这个口令并不是你的邮箱密码

你可以在你的邮箱后台获得

具体信息可以百度查得

多线程实现收发检测警报“同时”进行

调用GPIO模块操控树莓派IO口

总的来说

是一次python基础大测验

话不多说

上代码

(有注释哦)

邮件警报:

代码语言:javascript
复制
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#date:2020/02/20
#name:send_mail.py
#作者:聪明的瓦肯人
#微信公众号:工业光线
#网站:http://www.tech-xjc.com

#导入树莓派GPIO模块
import RPi.GPIO as GPIO
#导入发邮件相关模块
from email import encoders
from email.header import Header
from email.utils import parseaddr, formataddr
from email.mime.text import MIMEText
import smtplib
import time

#设置GPIO口引脚编号为BOARD模式
GPIO.setmode(GPIO.BOARD)
#设置7号引脚为输入模式,默认拉低电平
GPIO.setup(7,GPIO.IN,pull_up_down=GPIO.PUD_DOWN)
#设置16号引脚为输出模式,默认低电平
GPIO.setup(16,GPIO.OUT,initial=GPIO.LOW)

msg = MIMEText("警告:\n非法人员闯入!请立即采取相关措施!", 'plain', 'utf-8')
# 输入Email地址和口令:
from_addr ='16******81@qq.com'
password_smtp ='qnfs********bacj'
# 输入收件人地址:
to_addr4 = '34******84@qq.com'
# 输入SMTP服务器地址:
smtp_server = 'smtp.qq.com'

def _format_addr(s):
    name, addr = parseaddr(s)
    return formataddr((Header(name, 'utf-8').encode(), addr))

#构造头部From信息
msg['From'] = _format_addr('树莓派邮件警报器 <%s>' % from_addr)
#构造头部Subject信息
msg['Subject'] = Header('Alert from raspberry pi', 'utf-8').encode()

#连接SMTP服务器
server_smtp = smtplib.SMTP(smtp_server, 25) # SMTP协议默认端口是25
#是否显示调试信息,1显示,0不显示
server_smtp.set_debuglevel(0)
#登录
server_smtp.login(from_addr, password_smtp)

def check_send_alert():
#轮询7号引脚状态,高电平触发邮件发送
    while True:
        state = GPIO.input(7)
        GPIO.output(16,state)
        if state == 1:
            server_smtp.sendmail(from_addr,to_addr4, msg.as_string())
            print('\n*****已发送警报邮件!*****')
            #1分钟后再检测状态,是否再次发送邮件
            time.sleep(60)

人体红外传感器信号口接在7号GPIO

通过下面这句检测7号IO口状态

这种方法需要通过循环遍历

来实时监测

比较消耗资源

当然也可以检测电平边沿

来减少资源的使用

代码语言:javascript
复制
state = GPIO.input(7)

高电平有人,低电平无人

之后会收到警报邮件

邮件接收与处理:

代码语言:javascript
复制
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#name:email_alert_system.py
#date:2020/02/20
#作者:聪明的瓦肯人
#微信公众号:工业光线
#网站:http://www.tech-xjc.com

#导入自己的邮件报警模块send_mail
import send_mail

#导入树莓派GPIO模块
import RPi.GPIO as GPIO
import time
import threading

#导入POP3收邮件相关模块
from email.parser import Parser
from email.header import decode_header
from email.utils import parseaddr
import poplib

#设置GPIO引脚编号模式为BOARD
GPIO.setmode(GPIO.BOARD)
#设置11号引脚为输出,初始化为低电平
GPIO.setup(11,GPIO.OUT,initial=GPIO.LOW)

#task变量用于存储消息,并在thread1线程中作出判断
task = ''
#收邮件地址,口令与POP3服务器
email = '16******81@qq.com'
password_pop3 = 'qnfs*********acj'
pop3_server = 'pop.qq.com'
#guess_charset用于解析邮件的编码格式,要根据不同的编码进行解码
def guess_charset(msg):
    charset = msg.get_charset()
    print(charset)
    if charset is None:
        content_type = msg.get('Content-Type','').lower()
        pos = content_type.find('charset=')
        if pos >= 0:
            charset = content_type[pos + 8:].strip()
            print(charset)
    return charset

#decode_str用于解码邮件头部信息字符
def decode_str(s):
    value,charset = decode_header(s)[0]
    if charset:
        value = value.decode(charset)
    return value

#根据邮件类型解析内容并打印
def print_info(msg,indent=0):
    if indent == 0:
        for header in ['From','To','Subject']:
            value = msg.get(header,'')
            if value:
                if header == 'Subject':
                    value = decode_str(value)
                else:
                    hdr,addr = parseaddr(value)
                    name = decode_str(hdr)
                    value = u'%s <%s>' % (name,addr)
            print('%s%s:%s' % (' ' * indent,header,value))
    #判断邮件是否为multipart类型,粗略的理解为复合消息(比如text与html)
    if (msg.is_multipart()):
        parts = msg.get_payload()
        #将不同内容编码分块,并重新执行print_info递归一直到part不再是multipart类型
        for n,part in enumerate(parts):
            print('%spart %s' % (' ' * indent,n))
            print('%s-------------------' % (' ' * indent))
            print_info(part,indent + 1)
    #part不再是multipart则执行else
    else:
        content_type = msg.get_content_type()
        if content_type=='text/plain' or content_type=='text/html':
            #获取内容
            content = msg.get_payload(decode=True)
            #解析编码类型
            charset = guess_charset(msg)
            if charset:
                #根据编码类型进行解码
                content = content.decode(charset)
                if content_type=='text/plain':
                    global task
                    task = content
            print('%sText:%s' % (' ' * indent,content))

#灯光闪烁驱离函数
def light_blinker():
    while True:
        if task == '闪烁驱离':
            #1是高电平,0是低电平
            GPIO.output(11,1)
            time.sleep(0.5)
            GPIO.output(11,0)
            time.sleep(0.5)
            print('\n正在闪烁驱离!')
        if task == '停止闪烁':
            GPIO.output(11,0)
            print('\n已停止闪烁!')
            time.sleep(1)
            
#连接到POP3服务器,注意QQ需要SSL加密
server_pop3 = poplib.POP3_SSL(pop3_server,port=995)
#是否显示调试信息,0是关,1是开
server_pop3.set_debuglevel(0)
#打印POP3欢迎消息
print(server_pop3.getwelcome().decode('utf-8'))
#身份认证
server_pop3.user(email)
server_pop3.pass_(password_pop3)
#返回邮件编号
resp,mails,octets = server_pop3.list()
index_former = len(mails)

def get_mail():
    while True:
        #打印邮件数量与大小
        #print('Messages:%s.Size:%s'% server.stat())
        resp,mails,octets = server_pop3.list()
        index_now = len(mails)
        #根据邮件数量差值判断是否有新邮件
        new_msg = index_now - index_former
        global index_former
        index_former = index_now
        #获取最新邮件,lines中存储了最新邮件原始文本的所有行
        resp,lines,octets = server_pop3.retr(index_now)
    
        if new_msg > 0:
            print('********************************************************')
            print('\n有一封新邮件!')
            print(mails)
            #msg_content存储了邮件的原始信息,以回车相连(str)
            msg_content = b'\r\n'.join(lines).decode('utf-8')
            print('--------------------------------------------------------')
            #通过parsestr将msg_content转换为email类
            msg = Parser().parsestr(msg_content)
            print(msg)
            print('--------------------------------------------------------')
            print_info(msg)
            print(task)
            print('********************************************************')
            if task == '停机':
                break

#新建线程thread1,thread2
thread1 = threading.Thread(target=light_blinker,name='blinker')
#设置thread1为守护线程
thread1.setDaemon(True)
thread2 = threading.Thread(target=send_mail.check_send_alert,name='alert')
thread2.setDaemon(True)
thread3 = threading.Thread(target=get_mail,name='getMail')
thread1.start()
thread2.start()
thread3.start()
thread3.join()
#退出循环,解除服务
server_pop3.quit()
send_mail.server_smtp.quit()
#释放GPIO口资源
GPIO.cleanup()
print('**********已停机!**********')

实际上

如果你看懂了代码

你可能会觉得我多此一举

因为POP3收邮件指令

根本无需读取邮件内容

读取邮件内容还需判断是否为multipart类型

大大增加了复杂度

仅仅是获取简单指令

只需要解析邮件头部subject主题即可

代码语言:javascript
复制
#decode_str用于解码邮件头部信息字符
def decode_str(s):
    value,charset = decode_header(s)[0]
    if charset:
        value = value.decode(charset)
    return value

#根据邮件类型解析内容并打印
def print_info(msg,indent=0):
    if indent == 0:
        for header in ['From','To','Subject']:
            value = msg.get(header,'')
            if value:
                if header == 'Subject':
                    value = decode_str(value)
                else:
                    hdr,addr = parseaddr(value)
                    name = decode_str(hdr)
                    value = u'%s <%s>' % (name,addr)
            print('%s%s:%s' % (' ' * indent,header,value))

这种想法也没错

但读取邮件内容可以获得复杂信息

也就意味着可以进行更复杂的控制

当然

在这里确实有点大材小用

这里全当练手了 在创建线程方面

主线程用于创建子线程

并等待线程3退出

将线程1和2设为守护线程

等待线程3结束

代码语言:javascript
复制
thread3.join()

线程3会在收到邮件指令后退出

非守护线程3退出后

守护线程全部退出

并停止邮件收发服务

系统停机

代码语言:javascript
复制
#新建线程thread1,thread2
thread1 = threading.Thread(target=light_blinker,name='blinker')
#设置thread1为守护线程
thread1.setDaemon(True)
thread2 = threading.Thread(target=send_mail.check_send_alert,name='alert')
thread2.setDaemon(True)
thread3 = threading.Thread(target=get_mail,name='getMail')
thread1.start()
thread2.start()
thread3.start()
thread3.join()
#退出循环,解除服务
server_pop3.quit()
send_mail.server_smtp.quit()

需要注意的是

这里有一个坑

如果你使用IDLE运行程序

线程1和2不会退出

因为IDLE默认悄悄开启一个线程

但你会发现LED已经不亮了

那是因为线程3退出后

主线程运行了

代码语言:javascript
复制
#释放GPIO口资源
GPIO.cleanup()

最后

来看看运行效果吧

视频内容

我想了想,还是应该好好写,不能乱搞,最近要忙毕设了,在什么山上唱什么歌,各位好汉,咱们下次见!byebye~

——聪明的瓦肯人

• end •

1.本文遵守CC BY-NC-SA 4.0知识共享版权协议 2.本文未加水印图片来自网络,侵删

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 工科生日常 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档