前段时间发现有个电动门,可以用遥控器来进行控制,但是这个门一般都是室内的人进行控制,用来给室外的人开门,感觉比较麻烦,于是就琢磨着能不能做一个装置,这样能够通过手机在门外操控,就可以方便开门
遥控器的外观如下
射频应用的工作原理
首先,在淘宝上看到了类似的遥控器模块,发现了基本都是差不多的原理,网上的一些原理图如下,基本上都是利用的433M的射频芯片,如EV1527,PT2262等芯片,专门用在一些低成本的遥控开关上
接着继续搜索了一下一般射频应用的数据信号调制方式,下面是一个文献里面的说明
结合笔者以前的开发经验,这种射频芯片的编码也就是简单的串行编码(主要是调制方式比较低级,也只能进行这种编码方式),然后具体的编码内容也就是一个同步头,然后跟着N个数据,每个byte的8个bit从低位开始传输,顶多最后再附加一个校验码(一般为CRC或者简单地进行异或处理)
既然知道了工作原理,就开始进行大概的系统设计了。由于手头上有一个树莓派,那么一部分工作就可以基于树莓派来完成。整体方案由4个部分组成
利用树莓派通信
树莓派:自带蓝牙模块(支持BLE,Bluetooth Low Energy),用来作为BLE Peripheral(GATT Server),通过手机连接BLE,手机APP给树莓派发送命令,然后树莓派再控制其他设备发射射频信号
树莓派板子上有4个USB口,可以作为USB Host使用,挂载各种USB设备,还有一个有线的网口和一个WIFI、蓝牙二合一的芯片。(树莓派是一个国外的开源硬件,经历了很多代的发展,具体可以参考https://www.raspberrypi.org)
- MCU:由于树莓派运行的是Linux系统,虽然有IO口,但是实时性很差,需要用到一个MCU来调制射频信号,MCU接收树莓派的命令进行工作
- 射频发射模块:通过MCU控制射频发射模块,来发射无线信号
- 射频接收模块:捕获遥控器的射频信号,分析遥控数据,便于模拟发射
框图如下
自掏腰包,在淘宝了买了两个小模块,一个是用于发射另外一个是接收的
前面两个字节都是一样的,为了保密,没有显示出来。这几个按键的编码数据,区别主要在于第三个字节的高4位,每个bit代表一个按键。
其中,接收模块是5V供电,而发射模块的供电范围是3~12V,根据发射功率的要求,可以选择合适的供电电压。
STM32
MCU选择了比较简单的STM32,也是自掏腰包购买的,一个简单的开发板,带USB功能,可以直接接入树莓派的USB上,不仅仅可以取电,还可以进行USB通信,真的是一举两得
硬件买到手,把各个模块接好,通过杜邦线来连接STM32板子与射频发射和接收模块,整体的框架如下
开始着手软件的开发了,首要目标是破解门禁的遥控器的编码
利用STM32的两个IO口,一个用来接收数据,一个用来发送数据,分别连接到接收模块和发射模块对应的PIN脚,接收数据的IO口配置为外部电平跳变中断(EDGE FALLING下降沿和EDGE RISING上升沿),这样的目的是为了保存各个电平信号的时间,用一个内置的定时器来计时(计时周期为10us)。
由于空中的各种电磁波的影响,就算在没有发射器在433M频段中发送数据,接收器也会有各种杂波,而IO接收模块的输出IO口电平也会一直杂乱地跳变,生成很多垃圾数据,所以只能在内存中用一个循环的Buffer去不断地存储两次电平跳变之间的时间,分析的时候需要剔除垃圾数据,找到有效的数据。
代码编写完后,通过JLINK调试器进行在线调试,为了抓到需要的数据,需要在手动按下遥控器的按钮后,保证数据发射完成(一般1S内都可以发射完毕),马上通过调试器停住MCU,然后去分析BUFFER里面的时间数据
通过watch窗口查看buffer里面的数据,很容易就找到了同步头,然后后面的数据也很容易分析出来。找到一个同步头以及下一个同步头,就可以得出一帧的数据,结果发现,两个同步头之间一共有24个bit的数据,也正好就是3个字节的数据。
就按照这种方式,抓到了4个按键的编码数据,分别为
名称 | 编码数据 |
自动 | 0xXX, 0xXX, 0x17 |
全锁 | 0xXX, 0xXX, 0x87 |
常开 | 0xXX, 0xXX, 0x27 |
半开 | 0xXX, 0xXX, 0x47 |
捕获到按键的编码数据,那么就开始写发送代码了,由于接收器的灵敏度的原因,接收器不一定会在第一次接收到有效数据就识别出来,所以开始的一些数据可能会被丢失。于是,每次发射信号时,需要把完整的编码发送5次,然后最后加上一个同步头,这样保证门禁的接收器每次都可以稳定接收成功。
同步头 | 数据1 | 数据2 | 数据3 | …… | 同步头 | 数据1 | 数据2 | 数据3 | 同步头 |
1 | …… | N=3,4,5 |
经过漫长的coding,终于完成了发送代码的编写,然后测试了一下,效果还不错,如果发射天线的位置摆放好,20米内的传输不成问题。
但是由于遥控器上没有一次性开门并关门的按键,所以每次需要发送“常开”的命令,再发送“自动”的命令,这样才能保证开门后,还能自动关门(用遥控器也得这样操作)。所以接收到开门指令,那么MCU会先发射一个“常开”的命令,然后等待5秒钟后,自动再发送一个“自动”的命令。
(虽然这种操作很蠢,但是门禁只支持这种操作,我也没有办法)
接下来就是完成USB的通信了,由于需要与树莓派进行通信,最简单的方式就是用STM32实现一个USB串口的功能,这样树莓派上面就可以直接操作TTY串口进行数据收发。
STM32工程里面直接加入ST官方的Virtual Com Port(VCP)的USB Middleware和对应的Driver,编译顺利通过,然后修改一下USB数据传输的接口代码(VCP除了USB都有的一个控制断点EP0,还有一个EP1,可以双向传输,配置的是Buck传输模式,stm32的USB支持Full Speed,也就是USB2.0,每次Buck传输最多可以传输64个字节)。
设计了一个简单的通信协议:
起始码 | 0xAA |
命令 | CMD |
数据长度 | N |
数据内容 | DATA(N bytes) |
校验和 | SUM(0xAA+CMD+N+DATA+SUM=0) |
正确应答,回复相同数据,错误应答,CMD置0。
为了简单编码,限制了每次传输的数据长度,一个USB Buck传输的64个字节,可以传输一整个packet,这样树莓派那边不用做状态机保存临时数据。
把写好的程序烧写到STM32里面,然后通过USB插入到树莓派上,查看树莓派的dmesg
树莓派能够识别STM32的模拟串口,并挂载了串口设备/dev/ttyACM0,但是编码的时候还是不能把串口名字写死,需要通过枚举每个串口然后比较对应USB设备的PID和VID才能正确匹配(STM32的VCP里面的默认PID为22336,VID为1155)。
测试脚本
在树莓派上面,利用Python写了一个测试脚本,
其中SpController是自己写的一个class,主要是来进行串口的读写操作的。执行脚本,STM32在收到正确的数据后,能够正确地解析,接着发射“常开”按键,门禁那边滴一声响,门缓缓打开,然后5秒后,STM32发送“自动”按键,又是滴的一声,门自动关上。
到这里为止,工作基本上完成一半了。剩下的就是在树莓派上开发蓝牙的BLE Peripheral,然后再开发一个APP来与之对接了。
利用蓝牙连接APP
Linux系统上面主要用的是Bluez(http://www.bluez.org/)来作为蓝牙的协议栈,一般都是通过串口来与蓝牙芯片进行通信,有的蓝牙模块是USB接口,但是也是一个USB转串口的模块,所以对于蓝牙协议栈来说是一样的。而Bluez分为两部分,一部分是用户层的库,单独发布的,另一部分是Kernel的驱动,是随着Linux内核一起发布的。我们需要关心的是用户端的Library。
首先查看树莓派自带bluez的版本,通过bluetoothd –version得到当前的版本是43,而官网上最新的是5.50,所以在树莓派上面wget把source code下载下来,解压,然后./configure –enable-experimental –enable-debug –enable-deprecated –enable-testing,其中–enable-experimental至关重要,因为默认bluez不启动experimental选项,只有经典蓝牙的功能,而BLE的功能都在experimental里面。所以要使用BLE必须带上experimental选项。configure完就是简单的make和make install了,安装成功后,查看版本,为5.50,说明安装成功
Bluez有一个核心服务就是Bluetoothd,它能够管理设备接入和验证、蓝牙广播、服务注册和管理等功能,而与客户端是通过DBUS进行通信,通过一些对象的代理,去调用对应的方法。Bluetootd作为一个service启动,默认是不启用experimental功能的,所以需要在service文件里面加入-E选项,这样,Bluetoothd启动的时候才会加载BLE相关的功能。
由于以前从来没使用过Bluez,又是漫长的学习和研究。Bluez源代码里面提供了一些example,大部分都是基于python的示例,然后通过DBUS与bluetoothd来通信,实现一些服务的注册和使用。
不过最后还是利用C语言完成了程序的编写,虽然python基于DBUS也可以完成类似的功能,但是DBUS的接口无法操作广播的interval,默认广播的interval是1280ms,这个广播周期太长,无法快速让手机搜索到,只能调用C语言的接口,手动发送HCI命令来把interval设置到30ms,所以参考了bluetoothctl这个工具的代码,完成了所有代码的编写。程序运行起来后,会生成一个名字叫nsfocus_door的蓝牙设备,利用手机上的一个BLE工具(nRF Connect)可以进行BLE扫描和设备连接等操作,可以搜索到对应的设备
在BLE的GATT里面自定义了一个service和一个characteristics,如下图(由于UUID是随便给的,没有遵循SIG的规范,所以显示的是Unknown,但是不影响使用)
只要往这个characteristics写入一个任意的数据,那么就会触发开门的操作。
最后就剩下写一个安卓app了,这个也是一个重任,虽然看起来原理比较简单,就是搜索一个BLE设备,然后connect,读取所有service,并向目标characteristics写入一个数据。难点在于不同安卓版本对蓝牙的支持不太一样,新系统已经放弃了一些老版本接口的使用,所以在app代码里面,需要判断当前安卓系统的版本来调用不同的方法,否则,很容易出现问题。
这里推荐大家用两个BLE的SDK,是由Nordic公司制作的,源代码在github上面可以下载,在Android Studio也可以直接引用jcenter里面的库。
- Android-BLE-Library
- Android-Scanner-Compat-Library
这两个SDK能够隐藏了一些复杂的BLE操作,而且能够兼容从4.4到8.0的安卓系统,用起来比较方便,开发者可以把主要的精力花在业务代码上。不过这两个库用的语法需要JDK 1.8版本才支持(主要是用了一些Lambda表达式),而Android Studio默认用的是1.7的版本,需要在build.grade里面加入一个compileOptions
APP开发的效果展示
以下是APP的图标
以下是APP的界面,比较简单,但是功能足够用
App进入后,无需用户任何操作,会自动去搜索蓝牙并进行相应的操作,开门完成后,默认会自动退出,整个过程不超过5秒,最短2秒就可以完成,开门是相当的方便。如果第一次操作不成功,用户可以点击中间的开门按钮,会重新来过。
存在的问题
- 射频的安全性:由于电动门遥控的数据没有加密也没有滚码防止重放攻击,所以一旦被外部盗取,就很危险
- 命令的丢失,由于先要发送“常开”命令,再发送“自动”命令,万一“自动”命令没有收到,那么门就不会自己关上了。必须再开一次门,听到两个滴声,才能离开。射频是单向通信,无法做到闭环控制,无法保证每次发命令都能接收到,特别是在干扰比较大的情况下。
- BLE的数据写入未加密,没有防止重放攻击。APP没有做加固,代码没有混淆,安全机制不够,如果能够与云端结合管理加密秘钥就最好了。
经验的总结
- 整个项目做下来,还是花了相当多的精力,主要是bluez的开发那一块,的确非常多的坑,而且也没有什么文档可以参考,主要就是研究bluez的source code,中途由于碰到了很多问题,一度想过放弃,但最终还是坚持下来了,说多了都是泪。
- 由于要做到应用开机启动,还要在bluetoothd启动后再启动,而bluetoothd依赖于hciattach,但是hciattach初始化很慢,所以在应用启动的时候需要不断去查询hciattach是否初始化完毕,然后不断轮询,否则后面的操作都会失败。这种问题在正常开发的时候从来不会遇到,真正部署才能发现问题。
- 平时加班做做,也不影响正常上班的时间,对提升自己的能力还是挺有帮助的。至少学了很多方面的知识。
- 淘宝采购的一些物料和模块,也不贵,加起来不到20块钱,所以学习成本还是很低的,至少没有大出血。
- 如果有人能帮我开发一个IOS版本就更好了