基于树莓派加Python智能密码锁的设计与实现
2020-10-26赵宏哲王鹏
赵宏哲 王鹏
摘 要:一般的住宅或单位,为保证室内财物的安全,通常采用门上加锁的方法解决,而且绝大多数使用传统的机械锁。随着科技的发展,智能电子锁应运而生,系统由python网络编程、树莓派系统与手机客户端组成。系统能完成远程开锁、智能监控、远程呼叫、超次锁定密码锁等功能,依据实际的情况还可以添加有声提示与智能家居相关的功能。实验表明,此系统具有安全性高、成本低、保密性好、使用灵活等优点,可应用于宾馆、办公大楼和家庭等场所。
关键词:智能锁;远程开锁;智能监控;远程呼叫;智能家居;树莓派;Python
中图分类号:TM932 文献标识码:A 文章编号:1673-260X(2020)09-0049-06
1 研究背景
传统的门锁通常一把钥匙对应一把锁。日常生活中,每个人都会面对很多门锁,需要随身携带一大串钥匙,而这把最关键的钥匙却时常忘带或弄丢,被门锁挡在外面的尴尬事情时有发生。在科技高度发达的今天,智能家居下的智能门锁已经诞生,但智能门锁的应用还不够普及。
首先,智能门锁不能普及的最重要原因是技术门槛比较高,大多数产品售价都非常高,对于普通家庭来说较难承受。参照个人计算机、智能手机、汽车等行业的发展历程,这些高科技产品问世初期,使用此产品的用户数量非常有限。随着技术攻关、批量生产,现在这些商品都已经普及到很多家庭,这是大多数高科技产品普及过程的规律。相信随着智能门锁技术的不断进步,其价格也会越来越亲民。
其次,我国的智能门锁与西方发达国家的智能门锁相比还是有一定的差距。由于技术原因,还存在开发成本较高、功能单一的问题。
针对以上问题,现在国内一些企业已经开始积极攻克技术难题,发展前景非常乐观。本文就智能门锁平民化、普适化为出发点,使用性价比更高的树莓派作主板,用python语言开发智能门锁服务器端,成本不足300元,约等于一个普通门锁加上一个无线路由器的价格,而且这两样东西几乎每个家庭都有,相当于花较少的钱,升级了门锁,对于普通老百姓来说都可以消费得起。
2 智能密码锁的设计与实现
2.1 设计概述与原理
目前,电子技术和计算机技术发展日新月异,市场上出现了类似树莓派这样成本低、体积小的卡式电脑,而且具有强大的运算能力。与传统的单片机相比,功能更多,非常适合作为家居控制服务器,负责与互联网的通讯,处理数据量大的工作,还能方便使用手机控制家具。本设计主要是利用树莓派搭建服务器,与手机端相连接,通过快速的数据处理实现门锁的开关以及完成其他功能。与传统的单片机相比,卡式电脑的树莓派功能多,更适合作为智能家居的控制服务器。
设计原理是手机端应用加树莓派主板控制舵机驱动锁芯来实现开锁功能。本智能门锁开发主要是运用了python服务端的开发与java安卓应用的综合示例。为了降低成本,使用了当前性价比较高的树莓派主板,通过树莓派主板控制舵机驱动锁芯,达到开锁的目的。通过网络连接手机与门锁设备,输入开锁账号及密码,实现远程操控门锁开与关。智能门锁与移动端的连接,如图1所示。
2.2 服务器端代码
2.2.1 建立socket链接
本系统是利用socket网络编程实现的,第一步需要创建socket服务。为了提高效率与并发量,这里采取了即用即创建即销毁的概念。即每一次App向服务器端发送信息,都会创建一次连接,服务器端接收到信息马上处理,做出响应后立即断开连接,提高系统使用效率。这里的做法是通过while循环接受App端发来的消息,每次创建新的scoket_tcp来准备接受用户传递的信息。
一般监听个数只能是一个,如果增加监听数量,会降低安全性,比如两个人同时到家,没有必要两个人都同时连接这个设备,一个人开门即可。如果出现监听数量大于1,则可能有人破解门锁,要及时发出预警。
以下为部分相关源代码:
while True:
print ( "服务准备完毕,等待创建 . . . " )
socket_tcp = socket . socket ( socket . AF_INET, socket . SOCK_STREAM ) #创建socket
print ( "监听IP以及端口 @ %s:%d!" % ( HOST_IP, HOST_PORT ) )
#程序会在这里停止,当有设备进行连接的时候,才继续运行下面的代码。
host_addr = ( HOST_IP, HOST_PORT )
socket_tcp . bind ( host_addr ) #绑定用户树莓派的ip地址和端口号。
socket_tcp . listen( 1 ) #listen函数的参数是监听客户端的个数,这里只监听一个增加安全性能。
2.2.2 客户端连接并接收客户端发送数据
接下来客户端连接本服务,并发送客户端传送过来的信息,通过每一个命令创建一次连接,同时断开连接,达到多用户同时使用开锁系统。用户创建连接成功之后,可以接收客户端发送的信息,将接收的信息定义为data保存起来。
以下为部分相关源代码:
while True:
print ( '等待信号 . . . ')
#等待接收客户端的请求。
socket_con, ( client_ip, client_port ) = socket_tcp . accept ( )
#這里显示发送命令的用户。
print ( "连接成功 客户ip为:%s . " %client_ip )
data=socket_con . recv ( 1024 ) #接收数据。
2.2.3 通过正则表达式转化接收数据
App客户端传输过来的信息数据是一串有着特殊含义的字节流转化的字符串,要从中获取到需要的信息,就需要使用正则表达式将这一串特殊的字符串转化为指定字符串。例如,App端传输的是字节流文件,字符串格式为“b功能选择$事件选择#密码”,显而易见这个b是没有实际作用的,只是字节流转化为字符串的时候产生的没用信息,于是需要将b去除,得到后面有用的信息。如下文,得到的data_s就是通过正则表达式过滤的特定字符串功能选择$事件选择#密码。
以下为部分相关源代码:
#这里通过定义string来定义筛选规则,过滤出传输过程中没用的信息。
string = "b' ( . * ) '"
#这里将之前得到的客户端信息进行字符串转化,通过string自定义的规则来分离data。
data_s = re . compile ( string ) . findall ( str ( data ) ) [0]
接下来依次分离出各个字符串。之前分离出来了密码字符串,接下来将密码字符串去掉,如下文第一第二行,现在的data_s就是功能选择$事件选择这样的字符串。接下来将功能选择提取出来赋值给data_g,再将事件选择提取出来赋值给data_j。
以下为部分相关源代码:
#这里通过定义string来定义筛选规则,python新的赋值将会替换之前的复制。
string = " ( . * ) #"
#这里使用之前分离出来的data_s,通过替换之后的string自定义的规则来分离data_s。
data_s = re . compile ( string ) . findall ( data_s ) [0]
#以下代码原理同上,这里不作过多的阐述。
string = " ( . * ) \$"
data_g = re . compile ( string ) . findall ( str ( data_s ) ) [ 0 ]#功能选择字符
string = "\$ ( . * ) "
data_j = re . compile ( string ) . findall ( str ( data_s ) ) [0]#事件选择字符
2.2.4 身份认证
为了门锁的安全使用,传输过程中,字符串中包含的密码是通过base64编码进行加密,并且在服务器端进行密码还原。需要再一次使用正则表达式提取出字符串功能选择$事件选择#密码其中的加密密码信息,下一步将密码通过base64算法进行解密,将App客户端传输过来解密后的密码与服务器端密码比较,判断是否一致,不一致直接断开连接,提示App客户端密码输入错误。密码正确继续执行命令的语句。
以下为部分相关源代码:
string = "# ( . * ) "
data_p = re . compile ( string ) . findall ( data_s ) [0]
#此语句为对data_p密码进行解密,解密之后与原密码进行比较
data_p = base64 . b64decode ( data_p )
if data_p ! = password:
#此语句为服务器反馈给用户的信息
socket_con . send ( bytes ( '密码错误!请重新输入', 'utf-8' ) )
break
2.2.5 开锁样例
上面确定了密码正确可以执行开锁操作。通过功能选择和事件选择执行下方的语句,服务器系统即可回应用户响应的开锁操作。
以下为部分相关源代码:
if data_g = = '1':
if data_j = = '1':
#此语句为服务器反馈给用户的信息
socket_con . send ( bytes ('开锁成功,5s后自动上锁', 'utf-8' ) )
#服务器开闭锁动作,此动作开锁5秒钟之后自动闭锁。
#参数open_close为开闭锁动作,1为执行一遍。
SSR . thread_runActing (' open_close ', 1 )
elif data_j = = '2':
if ( ppz ! = data_j ) :
ppz = data_j
#此语句为服务器反馈给用户的信息
socket_con . send ( bytes ('开锁成功,别忘记上锁', 'utf-8' ) )
#服务器开锁动作,此动作开锁之后不自动闭锁。
#参数open为开锁动作,1为执行一遍。
SSR . thread_runActing ( ' open', 1 )
else:
#此语句為服务器反馈给用户的信息
socket_con . send ( bytes ( '主人,已经开锁啦,别点我啦', 'utf-8' ) )
……
2.2.6 其他附属功能
一些其他的辅助功能,需要识别符data_g进行选择。如需修改门锁密码,只需要发送data_g的值为2,服务器端即可识别需要操作的动作是修改密码。
以下为部分相关源代码:
elif data_g = = '2':
string = " ( . * ) #"
data_j1 = re . compile ( string ) . findall ( data_s ) [0]
string = "# ( . * ) "
data_j2 = re . compile ( string ) . findall ( data_s ) [0]
if ( data_j1 = = password ) :
password = data_j2
socket_con . send ( bytes ( '密碼修改成功,请妥善保存密码', 'utf-8' ) )
else:
socket_con . send ( bytes ( '与原密码不一致,修改失败', 'utf-8' ) )
2.3 App端数据收发核心代码
App客户端核心代码主要为与服务器之间的交互,在加载页面主页的时候,需要加载一下内容。第一步需要声明成员变量,通过initView( )方法并且对调用send( )点击事件进行监听。
2.3.1 部分相关源代码
private EditText et_send ;
private Button bt_send1 ;
private Button bt_send2 ;
private Button bt_send3 ;
private Button bt_send4 ;
private Button bt_send5 ;
private TextView tv_recv ;
protected void onCreate ( Bundle savedInstanceState ) {
super . onCreate ( savedInstanceState ) ;
setContentView ( R . layout . activity_main);
initView ( ) ;
handler = new Handler ( ) ;
send ( ) ; }
private void initView ( ) {
et_send = ( EditText ) findViewById ( R . id . et_send ) ;
bt_send1 = ( Button ) findViewById ( R . id . bt_send1 ) ;
bt_send2 = ( Button ) findViewById ( R . id . bt_send2 ) ;
bt_send3 = ( Button ) findViewById ( R . id . bt_send3 ) ;
bt_send4 = ( Button ) findViewById ( R . id . bt_send4 ) ;
bt_send5 = ( Button ) findViewById ( R . id . bt_send5 ) ;
tv_recv = ( TextView ) findViewById ( R . id . tv_recv ) ; }
2.3.2 向服务器端发送消息
本代码片段为send( )方法的部分片段。当需要开锁操作,可触发send( )事件,首先要创建与服务器之间的socket连接,将指定的信息与用户输入的密码一起以一个特殊的字符串格式发送给服务器,但是在发送之前需要先将字符串转化为字节流,在通过OutputStream将数据发送到服务器端。为了保证用户密码信息安全,在发送密码的时候采用的base64加密,将加密后的信息发送到服务器端,如下文第七行,将密码进行加密。执行完一次操作之后,调用recv( )接收方法,来接收服务器返回的数据。
以下为部分相关源代码:(这里只举例bt_send1方法)
private void send ( ) {
bt_send1 . setOnClickListener ( new View . OnClickListener ( ) {
public void onClick ( View v ) {
new Thread ( new Runnable ( ) {
public void run ( ) { try { socket = new Socket ( host , port ) ;
} catch ( Exception e ) { e . printStackTrace ( ) ; }
send_buff = et_send . getText ( ) . toString ( ) ;
//向服务器端发送消息
OutputStream outputStream=null ;
Reader read = null ;
try { outputStream = socket . getOutputStream ( ) ;
} catch ( IOException e ) { e . printStackTrace ( ) ; }
if ( outputStream! =null ) { try {
outputStream . write ( ( "1$1#" + send_buff ) . getBytes ( ) ) ;
outputStream . flush ( ) ;
} catch ( IOException e ) { e . printStackTrace ( ) ; } }
recv ( ) ; } } ) . start ( ) ; } } ) ;
bt_send2 . setOnClickListener ( new View . OnClickListener ( ) {…} ) ;
bt_send3 . setOnClickListener ( new View . OnClickListener ( ) {…} ) ;
bt_send4 . setOnClickListener ( new View . OnClickListener ( ) {…} ) ;
bt_send5 . setOnClickListener ( new View . OnClickListener ( ) {…} ) ; }
2.3.3 接收來自服务器端的消息
首先定义一个方法名叫recv用来接收数据。创建一个InputStream字节流对象,来接收服务器端socket发送的数据。
socket通信传输的是byte类型,需要转为String类型,将接收到的数据显示在TextView上。因为textView是主线程建立的,所以可以在子线程中刷新UI。
执行完毕跳转到监听端口,以此来反复循环地向服务器发送数据。
以下为部分相关源代码:
private void recv ( ) {
//单开一个线程循环接收来自服务器端的消息
InputStream inputStream = null ;
try { inputStream = socket . getInputStream ( ) ;
} catch ( IOException e ) { e . printStackTrace ( ) ; }
if ( inputStream! =null ) { try {
byte[] buffer = new byte[1024] ;
int count = inputStream . read ( buffer); //count是传输的字节数
System . out . println ( buffer ) ;
recv_buff = new String ( buffer ) ; //socket通信传输的是byte类型
} catch ( IOException e ) { e . printStackTrace ( ) ; } }
//将受到的数据显示在TextView上
if ( recv_buff! =null ) { handler . post ( runnableUi ) ; } send ( ) ; }
//不能在子线程中刷新UI,应为textView是主线程建立的
Runnable runnableUi = new Runnable ( ) {
public void run ( ) { tv_recv . setText ( recv_buff ) ; } } ;
2.4 测试
测试过程是配置一套简单的智能门锁。为了保证实验的准确性,通过手机远程对智能门锁的开关进行了上百次的开锁测试。测试结果表明开锁信息可以准确、稳定的传输,并且测试其他附加功能如:修改密码、完成指定的事件动作等功能,全部可以正常完成。通过测试,可以达到出门只带手机,不带钥匙,方便出行的预期效果,而且具有较好的实时性和稳定性。
3 智能门锁的功能及使用说明
(1)远程解锁。通过网络编程,可以用手机远程控制家里门锁的开关。出门在外,能做到一个手机就淘汰了钱包和钥匙。还可以处理一些突发情况,比如家里没人的时候发生意外事件时,可以操控远程开锁,方便紧急事件处理。
(2)防盗拍照。高配版智能门锁配有摄像头人脸识别功能,摄像头可以检测长较时间停留在家门口观望的人,拍照后可发送到主人的手机端,同时还可以检测到陌生人是否触碰大门,未开锁触碰大门时摄像头同样会拍下照片,实现门锁监控功能,增强安全性。
(3)门铃呼叫。门上置有智能门铃,例如当家中孩子放学回家,但是家里没有人进不去屋,也没有手机,不能呼叫家长,这时可以按动智能门铃,可以将按门铃人的现场照片发送到主人手机,由主人决定是否远程开门。
(4)超次锁定。为了提高系统安全性,本系统增加了超次锁定功能。当密码输入错误超过一定次数,门锁将会死锁,并且记录手机标识码,本手机永远不能打开此门锁,防止他人恶意破解。在配置门锁时,可设置超级管理员,其手机号码不会被超次锁定,同时可以解除其他手机的超次锁定。如果本人忘记密码,可通过超级管理员手机进行密码重置,恢复为系统默认密码。
(5)智能家居。在为家庭设置智能门锁的同时,还可以将传统家居升级至智能家居系统,利用智能门锁的服务器功能,关联房间的其他家具,实现智能窗帘,智能冰箱,智能LED灯等功能。
4 结束语
本文介绍的智能门锁是在传统门锁的基础上通过互联网与传统门锁的结合,设计出一款新型的智能门锁。充分利用智能手机客户端加智能门锁服务端的系统设计,解决了出门不用带一大串钥匙的问题。利用手机开锁和闭锁,代替传统钥匙开锁,既增加安全性,又方便了出行,最后在实物系统测试中取得成功。
——————————
参考文献:
〔1〕孙松林.5G时代[M].北京:中信出版集团,2019.
〔2〕赵大伟.互联网思维独孤九剑[M].北京:机械工业出版社,2015.
〔3〕赵英杰.完美图解物联网loT实操[M].北京:电子工业出版社,2018.
〔4〕Wolfram Donate. Python树莓派编程[M].北京:机械工业出版社,2016.
〔5〕刘耕,苏郁.5G赋能行业应用与创新[M].北京:人民邮电出版社,2020.
〔6〕罗建标,陈岳武.通信线路工程设计、施工与维护[M].北京:人民邮电出版社,2018.
〔7〕廖建尚.面向物联网的CC2530與传感器应用开发[M].北京:电子工业出版社,2018.
〔8〕中国移动通信有限公司政企客户分公司.5G落地应用融合与创新[M].北京:机械工业出版社,2019.
〔9〕Rushi Gajjar.树莓派+传感器:创建智能交互项目的实用方法、工具及最佳实践[M].北京:机械工业出版社,2016.
〔10〕吴军.智能时代大数据与智能革命重新定义未来[M].北京:中信出版社集团股份有限公司,2016.