本文所使用到的所有技术与对应的版本号如下:
树莓派:Raspberry Pi 4B;温度传感器:DS18B20;Python:3.7.3;数据库:MariaDB-10.0.28;Canal(Server/Client):1.1.3;Java:1.8;Javax mail:1.5.4
项目所在地址:
Gitee:Temperature Alarm System: 基于 树莓派+DS18B20+Canal 实现 温度报警系统 (gitee.com)
1、系统架构
最近突然想到一种温度报警系统的简单实现思路:大概就是通过DS18B20采集室内温度,树莓派循环读取室内温度值,达到温度报警阈值后,向数据库中写入报警信息。同时搭建一台简单服务器并且在其上部署一台Canal实时监听树莓派中数据库的数据变更事件,获取后发送邮件提醒温度预警。
整个系统可以分为采集层、通信层、告警层。系统结构如下图所示:
由于整个过程都是线性处理的,因此整个系统理解起来也非常轻松。
2、系统拆解
有了系统架构和简单实现思路后,让我们来思考下如何将系统拆解成一个个小任务并逐个解决。
2.1、数据采集层
对于数据采集层来说,难点在于树莓派如何读取传感器采集的温度信息。
玩过单片机的都知道,对于模拟信号(温度),我们需要一个AD芯片将模拟信号转换成8位、12位或16位的数字信号以高低电平的形式发送给单片机进行相应处理。温度采样精度取决于AD芯片的位数。这种方式处理非常复杂。
而DS18B20这种常用的温度传感器直接输出数字信号,体积小,抗干扰能力强。并且封装好后只有三根引脚,能够极大节省处理器的IO资源。与树莓派集成使用时,可以通过驱动将温度直接写入树莓派的设备文件中,节省大家的硬件驱动开发成本,也不用困扰在各种时序处理逻辑上。
因此,树莓派与DS18B20集成使用,我们可以使用Python读取设备文件的方式获取温度信息。
2.2、通信层
系统的通信层我选用了Canal监听MySQL数据库变更的方案(不要问我为什么搞这么麻烦 (~ ̄▽ ̄)~ ,问就是最近工作中用到了🙃)。
通信层还有很多其它方案的选择:
- 简单点可以去掉通信层,直接使用Python发送邮件
- 服务端部署Netty,自己实现一个简单的RPC协议
- 通过消息队列的方式发送告警信息
- ...
这里使用写数据库的方式还有一种好处,可以在以后有需要的时候将数据提取出来进行数据分析。(好吧,这就是在强行好处🤣)
方案确定了以后,实现就比较简单了。整个通信层我们需要:1、实现Python写入MySQL数据库;2、搭建Canal服务器监听数据库;3、将监听消息投递出去。
2.3、告警层
告警层的邮件发送没有什么难点,当初我纠结的地方就在于如何获取Canal监听的消息,是使用RockeyMQ还是Kafka,无论使用两个中的其中一个都还需要搭建一台服务器,感觉还是比较麻烦。当初想到这里的时候我其实是有点想放弃Canal了。
后来看到Canal自己也实现一套简单客户端Canal-Client通信,我就直接将其代码搬了过来,然后简单修改了下。
因此告警层技术点在于:1、通过Canal客户端获取服务器投递的消息;2、发送告警邮件。
2.4、小结
总结一下,要实现一个简单的温度报警系统,要完成以下几个步骤:
- 使用Python读取树莓派设备文件,获取温度值信息
- 判断温度值是否达到阈值,达到后写入MySQL数据库
- 搭建Canal服务器,监听树莓派上的MySQL数据库
- 使用Canal-Client读取Canal-Server投递的消息,然后发送邮件告警
3、具体实现
3.1、树莓派连接DS18B20
系统使用的DS18B20如下图所示:
该测温模块总共3Pin,分别位VCC、GND和DQ(Data Pin)。由于DS18B20只通过一根引脚传递数据,因此其使用的是单总线(One Wire)通讯协议传输数据。模块与树莓派的引脚连接如下表所示:
DS18B20 | 树莓派 | 备注 |
---|---|---|
VCC | Pin 1 | 物理引脚(BOARD编码)。本次系统使用的DS18B20支持3~5.5V工作电压,因此树莓派可连接的引脚可以是3.3V,也可以是5V。 |
GND | Pin 6 | 物理引脚(BOARD编码) |
DQ | Pin 7 | 物理引脚(BOARD编码);BCM编码4;功能名GPIO.7。使用该引脚是因为树莓派默认使用Pin 7作为单总线通讯引脚。 |
将DB18B20的引脚与树莓派使用杜邦线连接起来即可(记得最好在断电情况下连接)。
3.2、开启树莓派单总线协议
我一般都是通过SSH连接树莓派的,因此无法通过GUI直接开启树莓派单线总功能。需要通过修改启动配置文件的方式开启。(无屏幕、键盘配置,树莓派启动连接wifi,以及获取树莓派IP地址的方式可以参考这里,获取到IP地址后,便可以通过SSH远程连接了)
开启步骤如下:
1、修改启动配置文件
vim /boot/config.txt
2、在文件中的最后一行加如下配置,树莓派默认使用Pin 7(物理引脚,BOARD编码;该引脚的BCM编码为4)作为单总线引脚,但我们也可以手动指定其它引脚。
dtoverlay=w1-gpio
# 如果想要指定其它引脚,以下表示使用BCM编码为18的引脚
dtoverlay=w1-gpio,gpiopin=18
树莓派的引脚有:物理引脚(即BOARD编码);功能名;BCM编码;wiringPi编码。这里贴一张引脚对照表,防止大家出错。
ps:一定要连接对引脚,否则无法在设备文件中找到对应的测温模块设备文件,像我一开始连接错后在设备文件这种只能找到00-**
开头的设备文件,无法读取温度。
3、重启树莓派
sudo reboot
4、使用如下命令校验单总线协议是否开启。
lsmod
如果能在终端中看到如下输出则代表单总线协议已经开启。
5、在终端中输入如下两行命令,如果没有任何输出,则单总线已经初始化完成,可以进行温度采集了。
modprobe w1-gpio
modprobe w1-therm
3.3、获取温度值
1、进入系统设备目录:
cd /sys/bus/w1/devices
2、如果以上都成功执行了,可以看到当前目录下存在一个28-
开头的文件夹,这个文件夹的名称代表连接的温度传感器设备的编号,不同设备编号可能不一致。
3、进入设备文件夹,文件夹下有一个名字为w1_slave
的文件,这个文件即存储了传感器的采样温度。我们执行以下命令看看文件中写了什么。
cat w1_slave
第一行比较熟悉就是CRC、YSE两个单词了。大概可以猜出使用循环冗余校验后得到的数据是有效的。
第二行t=27437
表示当前的温度值,不过要除以1000来换算成摄氏度。图中表明当前温度值为27.437℃。
3.4、Python循环读取温度值
这一部分比较简单,只需要使用Python的内置os、time模块就行了,这里只贴核心代码,全部代码可以到Gitee/GitHub地址去看:
# 传感器编号
deviceNum = "28-01205b67097c"
# 设备记录数据的文件地址
deviceFile ='/sys/bus/w1/devices/' + deviceNum + '/w1_slave'
# 打开并读取文件数据
def readDeviceFile():
f = open(deviceFile,'r')
lines = f.readlines()
f.close()
return lines
# 解析温度数据
# deviceFile文件中的数据一般如下所示:其中 YES 表明是有效数据,t后面是温度,(t % 1000.0) 就是摄氏度
# b7 01 4b 46 7f ff 0c 10 4b : crc=4b YES
# b7 01 4b 46 7f ff 0c 10 4b t=27437
def readTemp():
lines = readDeviceFile()
# 如果第一行末尾不是 YES,则等待0.2s后重复读取,直至读取有效数据为止
while lines[0].strip()[-3:] != 'YES':
time.sleep(0.2)
# 循环继续读
lines = readDeviceFile()
# 读取温度
tempIndex = lines[1].find('t=')
if tempIndex != -1:
temp = lines[1][tempIndex + 2:]
tempC = float(temp)/1000.0
return tempC
3.5、树莓派安装MySQL
由于树莓派系统 Raspbian OS 是基于 Debian 的,因此我们可以直接使用apt命令直接下载MySQL。
sudo apt install mysql-server
执行上述命令后,终端会打印如下信息。说明树莓派的操作系统不支持MySQL,这也是MySQL被Oracle收购后的痛苦。但提示信息告诉我们可以使用mariadb-server,开源的好处就在这里体现了出来。
Reading package lists... Done
Building dependency tree
Reading state information... Done
Package mysql-server is not available, but is referred to by another package.
This may mean that the package is missing, has been obsoleted, or
is only available from another source
However the following packages replace it:
mariadb-server-10.0
E: Package 'mysql-server' has no installation candidate
接下来我们安装mariadb-server
sudo apt install mariadb-server-10.0
初始安装后我们可以使用如下命令修改root账号的密码(没错,"123456"永远的密码之神)
sudo mysqladmin -u root password "123456"
然后使用登录命令就可以登录到mariabd中了
sudo mysql -u root -p
ps:登录过后,我们可能想尝试使用Navicat或其它客户端远程登录测试,但出现错误码为10061的错误信息
Can't connect to MySQL server on 'address'(10061)
此时我们可以按照如下方式解决:
- 将
/etc/mysql/mariadb.conf.d/50-server.cnf
的bind-address = 127.0.0.1
修改为0.0.0.0
。或直接注释掉bind-address = 127.0.0.1
。 - 重启MySQL,
service mysqld restart
3.6、Python写入MySQL
Python关于MySQL的CRUD比较简单,大家可以参考Python3 MySQL 数据库连接 - PyMySQL 驱动快速上手即可。
这里有点需要注意:PyMySQL是Python3的库,使用pip3安装。而我一开始烧录的树莓派系统只默认携带Python2。因此,大家可以将原系统中的Python2卸载,然后安装Python3,具体可以参考将树莓派内置的 Python2.7 升级成 Python3
掌握了如何用Python对MySQL进行CRUD操作后就可以直接看项目相关的核心代码了:
def testInsertFunction(cursor) :
try :
sql = """INSERT INTO warn(warning_flag, insert_time) values (%s, %s)"""
data_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") #系统时间
result = cursor.execute(sql, (1, data_time)) #添加参数
print(result)
except Exception as e:
print("插入数据失败:", e)
else:
print("插入数据成功:")
def writeDatebase() :
conn = pymysql.connect(
host = "ipaddress",
port = 3306,
user = "root",
passwd = "password",
db = "project1",
charset = 'utf8',
autocommit = True
)
cursor = conn.cursor()
testInsertFunction(cursor)
cursor.close()
conn.close()
3.7、搭建Canal-Server
这里我并未选择在树莓派中搭建Canal-Server,而是另起一台虚拟机进行搭建。
Canal版本我选择的是1.1.3。大家在版本选择的时候也需要注意下,客户端和服务端版本最好一致;高版本MySQL最好选择高版本Canal。否则出现各种问题,比如我就出现了低版本Canal连接高版本MySQL出现密码加密方式错误的问题。
搭建Canal-Server的方式如下:
- 下载Canal-Server
wget https://github.com/alibaba/canal/releases/download/canal-1.1.3/canal.deployer-1.1.3.tar.gz
- 解压缩
mkdir /tmp/canal
tar zxvf canal.deployer-1.1.3.tar.gz -C /tmp/canal
- 解压完成后进入目录,目录下有
bin、conf、lib、logs
四个文件夹
cd /tmp/canal
ls
- 四个目录的作用如下:
- bin:存放了启动和停止Canal-Server的可执行文件
- conf:存放了Canal-Server的可配置文件
- lib:存放了运行时依赖jar包
- logs:存放了日志文件
- 修改配置文件
vim conf/example/instance.properties
- 下面我只将我修改了的配置给列出来,其它的大家可以参考官网查看配置详情
# slaveId只要保证不和masterId一样即可
canal.instance.mysql.slaveId=1234
# 下面都是MySQL的配置
canal.instance.master.address=address:3306
canal.instance.dbUsername=root
canal.instance.dbPassword=password
- 启动
sh bin/startup.sh
- 查看 server 日志
tail -f logs/canal/canal.log
- 查看 instance 日志
tail -f logs/example/example.log
- 关闭
sh bin/stop.sh
3.8、使用Canal-Client并发送邮件
这一部分我使用的是Canal官网自带的example测试用例,并在其上进行一些修改而成。
首先在工程当中引入了如下依赖:
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>com.sun.mail </groupId>
<artifactId>javax.mail </artifactId>
<version>1.5.4 </version>
</dependency>
然后将canal-example中的SimpleCanalClientTest、AbstractCanalClientTest、BaseCanalClientTest
拷贝进工程,然后编写发送邮件的工具类即可完成监听并发送邮件了。详细代码可以参考Gitee或GitHub。
4、成果展示
由下面的结果可以看出:开始时一直在轮询传感器温度值,超过32.5℃后,立马输出已经发送邮件。此时IDEA(即最下面黑色部分为IDEA的控制台)也输出了相应的日志。等待1~2s后,邮箱中的邮件也到达了。