前言
上回在 树莓派上的温湿度环境监控 里提及过,Open-Falcon 也是可以直接装在树莓派上的。所以其实可以使用树莓派在家里做一个小型的监控中心,把相应的监控指标采集过来推给树莓派上的 Open-Falcon 就好了嘛
根据这个思路,我尝试了温湿度,PM2.5 和路由器这几块的监控,这里做个分享
环境
树莓派:RaspberryPi 3
温湿度传感器:DHT11,DS18B20
PM2.5 传感器:攀藤 PMS3003(G3)
路由器:华硕 RT-AC68U,华硕 RT-AC66U
安装 Open-Falcon
说到底,树莓派就是个 CPU 是 ARMv6 的 Linux 而已,所以我们只要准备好 ARMv6 上的 Golang 环境然后编译就好了
获取 golang 的二进制包
wget https://storage.googleapis.com/golang/go1.9.linux-armv6l.tar.gz
解压
tar -C /usr/local -xzf go1.9.linux-armv6l.tar.gz
配置环境(永久生效应该写道 profile 里去)
export PATH=$PATH:/usr/local/go/bin
export GOPATH=/usr/local/gopath
# go version
go version go1.9 linux/arm
然后编译 Open-Falcon 就好了~
环境准备,Raspbian 是基于 Debain 魔改的,因此是 apt-get 系统
apt-get install mysql-server
apt-get install redis-server
mkdir -p $GOPATH/src/github.com/open-falcon
cd $GOPATH/src/github.com/open-falcon
git clone https://github.com/open-falcon/falcon-plus.git
cd $GOPATH/src/github.com/open-falcon/falcon-plus/scripts/mysql/db_schema/
mysql -h 127.0.0.1 -u root -p < 1_uic-db-schema.sql
mysql -h 127.0.0.1 -u root -p < 2_portal-db-schema.sql
mysql -h 127.0.0.1 -u root -p < 3_dashboard-db-schema.sql
mysql -h 127.0.0.1 -u root -p < 4_graph-db-schema.sql
mysql -h 127.0.0.1 -u root -p < 5_alarms-db-schema.sql
cd $GOPATH/src/github.com/open-falcon/falcon-plus/
# make all modules
make all
# make specified module
make agent
# pack all modules
make pack
export WorkDir="$HOME/open-falcon"
mkdir -p $WorkDir
tar -xzvf open-falcon-vx.x.x.tar.gz -C $WorkDir
cd $WorkDir
cd $WorkDir
./open-falcon start
当然还有 Dashboard
export HOME=/home/work/
mkdir -p $HOME/open-falcon/
cd $HOME/open-falcon && git clone https://github.com/open-falcon/dashboard.git
cd dashboard;
apt-get install -y python-virtualenv
apt-get install -y slapd ldap-utils
apt-get install -y libmysqld-dev
apt-get install -y build-essential
apt-get install -y python-dev libldap2-dev libsasl2-dev libssl-dev
cd $HOME/open-falcon/dashboard/
virtualenv ./env
./env/bin/pip install -r pip_requirements.txt -i https://pypi.douban.com/simple
是不是很亲切?
环境监控
温湿度监控
温湿度监控用 DHT11 ,我觉得这个精度已经够用了。我这里放了一个 DS18B20 作为对比,可以看一下两者的温度精度差异。
温湿度监控的具体实现可以看 树莓派上的温湿度环境监控 。
PM 2.5 监控
空气中的颗粒度浓度也是我们很关心的指标。这里用了攀藤的 G3 来做。
针脚说明
PIN | 定义 | 说明 |
---|---|---|
PIN1 | VCC | 电源,5V |
PIN2 | GND | 电源负 |
PIN3 | SET | 待机设置 |
PIN4 | RXD | 串口接收管脚 |
PIN5 | TXD | 串口发送管脚 |
PIN6 | RESET | 模块复位信号 |
PIN7/8 | NC | 悬空 |
连接
再次祭出树莓派针脚图
连接就很简单了
PMS3003 | Raspbian Pi 3 |
---|---|
Pin1 | 接 5V ,例如 Pin2 |
Pin2 | 接地,例如 Pin6 |
Pin3 | 设置口,随便接个,例如 Pin11(GPIO17),或者悬空。 |
Pin5 | 数据接收口,接 Pin8 |
串口
PMS3003 的数据是通过串口来传输的,因此我们需要配置一下树莓派,把串口功能打开,由于树莓派的串口略坑,稍微扯点题外话:
大家知道 Unix 一切为文件,因此串口的驱动会创建对应的字符设备(文件)以供读写。通常这些设备是以类型+序号来命名。
例如:x86 架构下串口通常命名为 /dev/ttyS0(Serial 0)
,大家可以找台 x86 的 Linux 服务器去看看。 Raspbian 是 ARM 架构的因此其串口命名为 /dev/ttyAMA0(TeleTyper ARM UART 0)
。
然而在 Raspbian Pi 3 上,这里情况就不太一样了。Pi3 的 /dev/ttyAMA0
被蓝牙给默认占用了, 于是它给出一个 /dev/ttyS0 设备用于软串口的 Mini UART
。 然后为了使得新老 Raspbian Pi 之间的兼容性 ,又把 3 代之前版本 的 /dev/ttyAMA0
和 Raspberry Pi 3 的 /dev/ttyS0
软链接到同一个 /dev/serial0
。这样子绕了一圈,然而Minu UART
的性能又是相当令人捉急的。。。
因此我们得调整一下配置,把默认的 /etc/ttyAMA0
给搞回来。
启用 UART,修改/boot/config.txt
,增加(新版 Raspbian 已经默认开启了)
enable_uart=1
关掉默认的 console
sudo systemctl disable serial-getty@ttyS0.service
在启动脚本/boot/cmdline.txt
里干掉 console=ttyAMA0
或者 console=ttyS0
的配置,类似这样
dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline ...
关掉蓝牙
dtoverlay=pi3-disable-bt
重启之
数据传输
PMS3003 的数据格式如下所示
数据 | 功能 |
---|---|
起始符1 | 0x42(固定) |
起始符2 | 0x4d(固定) |
帧长度:高八位,低八位 | 帧长度=20 (数据:9x2+校验位:2) |
数据1:高八位,低八位 | 数据1表示PM1.0浓度(CF=1,标准颗粒物)单位ug/m3 |
数据2:高八位,低八位 | 数据2表示PM2.5浓度(CF=1,标准颗粒物)单位ug/m3 |
数据3:高八位,低八位 | 数据3表示PM10浓度(CF=1,标准颗粒物)单位ug/m3 |
数据4:高八位,低八位 | 数据4表示PM1.0浓度(大气环境下)单位ug/m3 |
数据5:高八位,低八位 | 数据5表示PM2.5浓度(大气环境下)单位ug/m3 |
数据6:高八位,低八位 | 数据6表示PM10浓度(大气环境下)单位ug/m3 |
数据7:高八位,低八位 | 数据7(保留) |
数据8:高八位,低八位 | 数据8(保留) |
数据9:高八位,低八位 | 数据9(保留) |
数据校验和:高八位,低八位 | 校验码=前 22 位字节累加 |
可以看到,一个完整的数据包长度一共是 24 个字节,我们不妨先看一下他的数据到底长什么样子。
写一段简单的串口读取代码
#!/usr/bin/python
import serial
import time
def pm():
ser = serial.Serial("/dev/ttyAMA0", 9600)
while True:
time.sleep(1) # 等个 1 秒钟等数据进来
count = ser.inWaiting()
if count >= 24: # 由于一个完整的数据包长度是24个字节,因此等到缓冲区字节数超过 24 个了,开始读取。
recv = ser.read(count) # 读取数据
# 这里如果直接 print recv 肯定是一堆无意义的乱码,即便 print repr(recv),
# 由于 python 默认的 ACSII 编码,他还是会把其中一部分字符给编码成字符打印出来,
# 所以要看到原始的 16 进制报文,得额外处理一下
print map(lambda c: hex(ord(c)), recv) # 按 16 进制格式打印接受到的数据
ser.flushInput() #读完了清掉缓冲区
ser.close()
return
if __name__ == '__main__':
pm()
执行之
# python pm25_test.py
['0x42', '0x4d', '0x0', '0x14', '0x0', '0x4e', '0x0', '0x6d', '0x0', '0x78', '0x0', '0x33', '0x0', '0x48', '0x0', '0x50', '0x10', '0x44', '0x0', '0x30', '0x91', '0x0', '0x3', '0xb6']
可以看到,起始符分别为 0x42
和0x4d
,然后是高低位的帧长度 = 20(24 个字节去掉 2 个字节起始符和高低位 2 个字节的帧长度)。接着是 2 个字节一组的数据,一共 9 个。最后是校验和,高位0x3,低位0xb6,也就是校验和为 0x3b6。
我们来尝试计算一下校验和
root@raspberrypi:/opt/falcon-scripts# python
Python 2.7.9 (default, Sep 17 2016, 20:26:04)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a = 0x42 + 0x4d + 0x0 + 0x14 + 0x0 + 0x4e + 0x0 + 0x6d + 0x0 + 0x78 + 0x0 + 0x33 + 0x0 + 0x48 + 0x0 + 0x50 + 0x10 + 0x44 + 0x0 + 0x30 + 0x91 + 0x0
>>> print hex(a)
0x3b6
>>>
校验和正确,所以这组数据是正确的,可以被接收的。
因此 PM2.5 等的数据采集大体是这个逻辑。由于我们是每分钟定期采集一次数据,没有必要实时的去处理串口的数据流(实际上 PMS3003 也是间隔 200ms ~ 800ms 定期往串口发数据)。只要在开始采集的时候等待上 1 秒钟,等到有至少 24 个字节输入进入后进行处理即可。
对于数据异常,例如起始符不正确,校验和不正确等数据,直接抛弃等下一轮就好了。
完整代码
#!/usr/bin/python
#encoding=utf-8
import serial
import time
import json
import requests
import copy
def pm():
ser = serial.Serial("/dev/ttyAMA0", 9600)
while True:
time.sleep(1)
count = ser.inWaiting()
if count >= 24:
recv = ser.read(count)
data = map(lambda c: ord(c), recv) # 上面写 hex() 是为了打印出来方便人看,实际运行就不需要了,不然后面还得转换
if check_data(data) != True: # 数据校验不合法,抛弃之等下一轮
ser.flushInput()
continue
return get_data(data) # 读到数据了直接返回退出循环
ser.flushInput()
ser.close()
return 0,0,0
def check_data(data):
sum = 0x00
if data[0] != int('0x42',16): # 起始符校验
return False
if data[1] != int('0x4d',16):
return False
for d in data[0:21]:
sum = sum + d
check_sum_high = data[22]<<8 # 校验和校验,位移 8 位到高位
check_sum_low = data[23]
check_sum = check_sum_high | check_sum_low # 按位或运算,高低位合并
if sum == check_sum:
return True
return False
def get_data(data):
pm1 = (data[10] << 8) | data[11] # 高位位移,按位或运算合并高低位
pm2_5 = (data[12] << 8) | data[13]
pm10 = (data[14] << 8) | data[15]
return pm1,pm2_5,pm10
def push_falcon(value_name,value):
ts = int(time.time())
push_url = "http://127.0.0.1:1988/v1/push"
payload = [{"endpoint":"home","metric":"room.air_quality","timestamp":ts,"step":60,"value":value,"counterType":"GAUGE","tags":"module="+value_name}]
r = requests.post(push_url, data=json.dumps(payload))
if __name__ == '__main__':
pm1, pm2_5,pm10 = pm()
push_falcon("pm1",pm1)
push_falcon("pm2.5",pm2_5)
push_falcon("pm10",pm10)
效果如下所示
如果要控制 PMS3003 的工作状态的话,可以通过调节他的 Pin3,也就是 set 口。可以让他处于待机状态。但注意转入工作状态之后,要至少工作 30 秒以上,读取的数据才比较准确。
import RPi.GPIO as GPIO
import time
pin = 17 # 11管脚对于的BCM管脚号码
GPIO.setmode(GPIO.BCM)
GPIO.setup(pin, GPIO.OUT) # 写入 GPIO
GPIO.output(pin, GPIO.HIGH) # 工作
GPIO.output(pin, GPIO.LOW) # 待机
路由器监控
监控路由器,那肯定是走 snmp 了。虽然在路由器的环境里(例如 RC-66U,openwrt 系统, cpu MIPS 指令集) 应该也能够编译出 golang 来 go-mips32 ,但是考虑到易用性和减少路由器的资源占用,肯定还是开启路由器的 net-snmp 比较合适。
以华硕的 RT-AC66U 和 RT-AC68U 为例。
开启 snmp —— 官版系统
华硕的官版固件默认并没有开启 snmp,如果要开启的话需要有一个曲折的过程~
- 首先找一个 U 盘插上路由器
- 在路由器后台里开启 Download Master (为了激活 ipkg 包管理器),并开启 telnet
- 安装 net-snmp
ipkg install net-snmp
app_set_enabled.sh net-snmp yes
这一步过程中,有很大概率遭遇不可描述的网络故障导致网络连接异常下载失败
可能需要通过一些不可描述的方法来规避网络连接的异常
- 如果成功了,就可以
snmpwalk -v 2c -c public 192.168.1.1
测试了
开启 snmp —— 梅林
更省事的办法是用 梅林 的系统。话说都买了华硕的路由器了,不刷个梅林好意思么?
刷完梅林之后,直接在路由器 web 后台就能够开启 snmp
监控 - swcollector
要在 open-falcon 下的通过 snmp 监控路由器的话,自然考虑用 swcollector 了。这篇文章也可以提供参考 Open-Falcon 中的交换机监控
在编译 swcollector 之前,我们不妨先 snmpwalk 测试一下我们的路由器,以防踩坑,比如:
1号坑:
root@raspberrypi:/home/pi# snmpwalk -v 2c -c public 192.168.2.2 1.3.6.1.2.1.31.1.1.1.15
iso.3.6.1.2.1.31.1.1.1.15.1 = Gauge32: 10
iso.3.6.1.2.1.31.1.1.1.15.2 = Gauge32: 10
iso.3.6.1.2.1.31.1.1.1.15.3 = Gauge32: 0
iso.3.6.1.2.1.31.1.1.1.15.4 = Gauge32: 0
iso.3.6.1.2.1.31.1.1.1.15.5 = Gauge32: 10
iso.3.6.1.2.1.31.1.1.1.15.6 = Gauge32: 10
iso.3.6.1.2.1.31.1.1.1.15.7 = Gauge32: 0
root@raspberrypi:/home/pi# snmpwalk -v 2c -c public 192.168.2.2 1.3.6.1.2.1.2.2.1.5
iso.3.6.1.2.1.2.2.1.5.1 = Gauge32: 10000000
iso.3.6.1.2.1.2.2.1.5.2 = Gauge32: 10000000
iso.3.6.1.2.1.2.2.1.5.3 = Gauge32: 0
iso.3.6.1.2.1.2.2.1.5.4 = Gauge32: 0
iso.3.6.1.2.1.2.2.1.5.5 = Gauge32: 10000000
iso.3.6.1.2.1.2.2.1.5.6 = Gauge32: 10000000
iso.3.6.1.2.1.2.2.1.5.7 = Gauge32: 0
好嘛,无论是 ifSpeed 还是 ifHighSpeed,这货返回的结果都根本不对嘛。这有点像是 net-snmp 的一个 bug。但是在 asuswrt-merlin 里,snmpd 已经是以 admin ,也就是 root 权限在跑了。所以具体原因也吃不太准,我在 github 上给 asuswrt-merlin 提了个 issue,希望能有个答案吧。
总之,这样的话我们就没办法愉快的监控了。因为 swcollector 自 4.0 版本起,就会根据 ifSpeed 的值对采集到的流量进行合法性判断,如果采集到的流量计算出来超过了接口的速率,讲被视作异常值而抛弃的。你这里给我来个统统 10M,这岂不是一开下载监控就要断流的节奏嘛。。。
这样只好人工设置 speedlimit 为 1G 了
"speedlimit":1000000000,
除了常规的 cpu,内存,接口各种流量,包数之外。我们还可以通过自定义监控的方式来拿一些更多的信息,比如系统的 load 信息
{
"metrics":
[
{
"ipRange":[
"192.168.2.1-192.168.2.3"
],
"metric":"switch.system.load",
"tag":"",
"type":"GAUGE",
"oid":"1.3.6.1.4.1.2021.10.1.3.1"
}
]
}
现在我们可以去 Open-Falcon 里看看我们监控的路由器了。欸?路由器的内存好像有点不太对?
没错,此处就是 2号坑,net-snmp 在这里有内存泄露,如果你这么就一直跑下去,路由器倒是不会死球,snmpd 肯定是妥妥的会死球无响应的。
解决的办法只能是在路由器上起一个定时任务,每天半夜重启一下 snmpd 咯
admin@RT-AC66U-F5C8:/usr/sbin# crontab -e
0 5 * * * service restart_snmpd
同时把我们监控的采集周期调高到 5 分钟,避免一个晚上他内存就炸了,好歹撑到凌晨内存释放
"transfer": {
"enabled": true,
"addr": "127.0.0.1:8433",
"interval": 300,
"timeout": 1000
},
好啦,现在监控木有什么问题了。我们会看到 ASUS 有以下这么些个接口
switch.if.In/ifIndex=1,ifName=lo
switch.if.In/ifIndex=2,ifName=eth0
switch.if.In/ifIndex=3,ifName=eth1
switch.if.In/ifIndex=4,ifName=eth2
switch.if.In/ifIndex=5,ifName=vlan1
switch.if.In/ifIndex=6,ifName=vlan2
switch.if.In/ifIndex=7,ifName=br0
这些接口都是什么意思呢?这张 DD-WRT 的原理图可供参考。 ASUS 与此之的差异在于还有个 eth2 专门跑 WiFi 5G
所以接口的关系是这样的
接口 | 功能 |
---|---|
eth0 | WAN + LAN |
br0 | Bridge |
eth1 | WiFi 2.4G |
eth2 | WiFi 5G |
vlan1 | LAN |
vlan2 | WAN |
在路由器上,可以通过这个命令来进一步自定义配置
admin@RT-AC68U-AB48:/tmp/home/root# robocfg
Broadcom BCM5325/535x/536x/5311x switch configuration utility
Copyright (C) 2005-2008 Oleg I. Vdovikin (oleg@cs.msu.su)
Copyright (C) 2005 Dmitry 'dimss' Ivanov of "Telecentrs" (Riga, Latvia)
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
Usage: robocfg <op> ... <op>
Operations are as below:
show -- show current config
showmacs -- show known MAC addresses
showports -- show only port config
switch <enable|disable>
port <port_number> [state <enabled|rx_disabled|tx_disabled|disabled>]
[stp none|disable|block|listen|learn|forward] [tag <vlan_tag>]
[media auto|10HD|10FD|100HD|100FD|1000HD|1000FD]
[mdi-x auto|on|off] [jumbo off|on]
vlan <vlan_number> [ports <ports_list>]
vlans <enable|disable|reset>
ports_list should be one argument, space separated, quoted if needed,
port number could be followed by 't' to leave packet vlan tagged (CPU
port default) or by 'u' to untag packet (other ports default) before
bringing it to the port, '*' is ignored
Samples:
1) ASUS WL-500g Deluxe stock config (eth0 is WAN, eth0.1 is LAN):
robocfg switch disable vlans enable reset vlan 0 ports "0 5u" vlan 1 ports "1 2 3 4 5t" port 0 state enabled stp none switch enable
2) WRT54g, WL-500g Deluxe OpenWRT config (vlan0 is LAN, vlan1 is WAN):
robocfg switch disable vlans enable reset vlan 0 ports "1 2 3 4 5t" vlan 1 ports "0 5t" port 0 state enabled stp none switch enable
运行截图
树莓派自身
当然还有树莓派自己的监控。这个 Open-Falcon 的 agent 就已经基本解决问题。我们再加一个小脚本来采一下树莓派的 cpu 温度就好了
树莓派的 cpu 温度可以在 /sys/class/thermal/thermal_zone0/temp
这里读到
root@raspberrypi:/opt/falcon-scripts# cat /sys/class/thermal/thermal_zone0/temp
52615
写个脚本推给 Open-Falcon
#!/usr/bin/python
# -*- coding: utf-8 -*-
import requests
import time
import json
def get_Pitemp():
file = open("/sys/class/thermal/thermal_zone0/temp")
temp = float(file.read()) / 1000
file.close()
return temp
if __name__ == '__main__':
temp = get_Pitemp()
ts = int(time.time())
push_url = "http://127.0.0.1:1988/v1/push"
payload = [{"endpoint":"raspberrypi","metric":"cpu.temperature","timestamp":ts,"step":60,"value":temp,"counterType":"GAUGE","tags":""}]
r = requests.post(push_url, data=json.dumps(payload))
~
以上
参考文档
Falcon+
Falcon-Dashboard
Raspbian Jessie 的 GPIO 串口配置
Raspberry Pi 读取攀藤G3 PM2.5
PM2.5传感器PMS3003,同时能测PM1.0 PM2.5 PM10
Install and Configure SNMP on the Asus RT-AC66U Router
DD-WRT leading two separate networks (Asus RT-AC68)