一直想要米家那个温度湿度计,等到准备入手的时候才发现原来还需要配合小米网关使用,而两个的价钱又和一台Raspberry Pi差不多。所以想来想去还是Raspberry Pi比较经得起折腾,便从v2ex上收了一台Raspberry Pi 3B,准备再买个DHT11温湿度传感器来测温度。

关于硬件

Raspberry Pi一台,一张Class 10的TF卡,DHT11传感器,电源线一根,over。

  • + SR501传感器一枚
  • + 小爱同学Mini一台

初始化Raspberry Pi

系统选择了官方推荐的Raspbian,除了官网的推荐方法NOOBS之外,还可以使用Etcher把IMG烧录到TF卡中,然后插上TF卡就可以启动了。

因为没有显示器所以准备直接连接SSH,后来发现一些问题:

  • 因为没有显示器无法在机器上进行WIFI配置

  • 周围没有网线所以不能直接连接到路由器中

  • 路由器管理密码不知道所以无法查看所有设备的IP信息

所以就卡在了那里,查了一下官网的介绍,新固件默认是不开启SSH的,开启的方式也很简单:在TF的boot/目录下新建一个ssh文件就可以了;而进行WIFI配置的话同样可以在boot/目录下新建wpa_supplicant.conf文件,然后将WIFI信息配置一下就可以了:

1
2
3
4
5
6
7
8
9
10
country=CN
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
ssid="WiFi"
psk="12345678"
key_mgmt=WPA-PSK
priority=1 #优先级
}

不知道路由器的Admin密码所以使用了IP Scanner这个软件来扫描局域网下所有设备的IP信息,这样就可以愉快的连接SSH啦!

输入缺省用户名pi和缺省密码raspberry就可以像连接服务器一样连接到Raspberry Pi了!

Raspbian默认是关闭Root账户的,如果想要更高权限的话需要解锁Root账户:

1
2
sudo passwd root #设置root密码
sudo passwd --unlock root #解锁root账户

再将root登录的权限打开:

1
sudo vim /etc/ssh/sshd_config

PermitRootLogin without-password改成PermitRootLogin yes就可以root账户登录了!

自动登录

打开root账号权限后就可以使用root账号自动登录,使用vim:

1
vim /etc/systemd/system/getty.target.wants/getty\@tty1.service

将ExecStar那一行改称为:

1
ExecStart=-/sbin/agetty --autologin root --noclear %I $TERM

重启之后就可以了。

DHT11传感器

选择什么样的温度传感器就不太了解了,后来了解到一款DHT11传感器:

DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,它应用专用的数字模块采集技术和温湿度传感技术,具有较高的灵敏度和稳定性。传感器内部包括一个电阻式感湿元件和一个NTC测温元件,因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。

简单来说,就是才!5!块!钱!最重要的是网上大量的开源资料,哈哈哈哈。

Raspberry Pi 3B的GPIO口(来源见水印):

对于驱动DHT11模块,在Github(DHT11_Python)上找到了一个DHT11模块库,可以直接引用,炒鸡方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- coding: UTF-8 -*-
import RPi.GPIO as GPIO
import dht11
import time
import datetime

# initialize GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

# read data using pin 17
instance = dht11.DHT11(pin=17)

result = instance.read()
if result.is_valid():
print("Last valid input: " + str(datetime.datetime.now()))
print("Temperature: %d C" % result.temperature)
print("Humidity: %d %%" % result.humidity)

需要注意的是,pin应该是BCM编码的引脚,一开始配置的物理引脚始终获取不了数据以为买家发的坏的。(逃

简单的温度系统

一开始的构思是:搭建lnmp服务,使用Python驱动DHT11,然后创建一个Crontab定时任务,每30分钟监测一下室内的温度,保存在数据库中。这样可以通过捷径来询问Siri最近的温度是多少,感觉方便极了。

为什么要保存每30分种的数据呢,因为准备后期做一个Dashboard || HUB,显示设备的一些信息、手机话费、室内温湿度等等,有了历史数据完全可以制作一个温度湿度趋势图哈哈哈哈。

搭建lnmp环境

这个可以说是非常熟练了,一行代码就可以搞定,剩下的就交给系统来编译吧:

1
wget http://soft.vpser.net/lnmp/lnmp1.5.tar.gz -cO lnmp1.5.tar.gz && tar zxf lnmp1.5.tar.gz && cd lnmp1.5 && ./install.sh lnmp

然后编译了2个多小时…

保存到数据库

Python连接MySQL数据库使用了MySQLdb模块,但是使用pip安装时出现了EnvironmentError: mysql_config not found错误,然后Google建议需要安装apt-get install libmysqlclient-dev

但是无论是更新了哪个apt的源始终找不到这个包,最后apt-cache search libmysql了所有的lib发现已经更新到了libmysql++-dev库。

再次安装时又提示_mysql.c:29:20: error: Python.h: No such file or directory,这次需要安装python-devel依赖,最后将温湿度数据插入到数据库中就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
t = int(time.time())
conn = MySQLdb.connect(
host='127.0.0.1',
port=3306,
user='root',
passwd='root',
db='homekit',
use_unicode=True,
charset="utf8"
)
cur = conn.cursor()
cur.execute("set names utf8")
cur.execute("INSERT INTO t_temperature(f_temperature,f_humidity,f_time) VALUES ("+str(result.temperature)+","+str(result.humidity)+",'"+str(t)+"')")
cur.close()
conn.commit()
conn.close()

定时任务

定时任务使用的系统级别的Crontab,建议打开/etc/rsyslog.conf下的cron的log方便查看Crontab的执行日志。

内网穿透

为了能在外网访问Raspberry Pi,所以再查看了大量的内网穿透方案后,选择了配置炒鸡简单的花生壳,哈哈哈哈谁让以前曾经付过费,登录就能用。(再逃

搭建HomeBridge

作为一个假果粉既然Raspberry Pi都有了,肯定是要搭建一个HomeBridge来体验一下。

配置环境

安装Node.js和NPM

1
2
sudo apt-get install nodejs
sudo apt-get install npm

安装HomeBridge

直接使用NPM安装即可:

1
sudo npm install -g --unsafe-perm homebridge

安装完成后直接启动就可以在「家庭」中绑定HomeKit设备了。

连接温度

homebridge-http-temperature-humidity是一个可以通过 HTTP 的 GET 请求从服务器获取温度和湿度的HomeBirdge插件,通过这个插件把温度数据可以加载到「家庭」APP中:

只需要在HomeBridge中的config.json中加入请求地址即可,由于是本地服务器,所以直接127.0.0.1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"bridge": {
"name": "Homebridge",
"username": "CC:22:3D:E3:CE:30",
"port": 52126,
"pin": "031-45-154"
},
"description": "This is my homebridge",
"accessories":[
{
"accessory": "HttpTemphum",
"name": "DHT11 Sensor",
"url": "http://127.0.0.1/api/temp2homebridge.php",
"httpMethod": "GET",
"humidity": true,
"cacheExpiration": 60
}
]
}

只要服务端提供这样的格式HomeBridge就可以识别了:

1
2
3
4
{
"temperature": 25.8,
"humidity": 38
}

读取Raspberry Pi温度

cat /sys/class/thermal/thermal_zone0/temp可以直接获取Raspberry Pi的CPU温度,这样就得到了Raspberry Pi的机器温度。

实时数据

数据库中的数据最好情况可能是当时温度,最坏情况就是30分钟前的温度了,所以需要HomeBridge得到实时的温度,因为不是Python搭建的WEB服务所以需要PHP执行Python脚本再获取Python 的打印数据。PHP的shell_exce函数可以执行执行shell命令,使用的时候需要在php.ini中将禁用的函数删除掉,具体哪些被禁用可以使用phpinfo()进行查看。

然后:

1
2
3
4
5
6
7
8
9
$output = shell_exec("/usr/bin/python sensor/tmp2php.py");
if(!isset($output) || $output == '' || empty($output)){
echo json_encode([
'temperature'=>0,
'humidity'=>0
]);
}else{
echo $output;
}

为了可以使用sudo执行,需要将PHP所在的用户组加入到sudoer里:

1
www ALL=(ALL) NOPASSWD:ALL #假设为www用户

同时,Python端提供传感器的数据读取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding: UTF-8 -*-
import RPi.GPIO as GPIO
import dht11
import time
import datetime
import json

# initialize GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

# read data using pin 17
instance = dht11.DHT11(pin=17)

result = instance.read()
if result.is_valid():
print json.dumps({
'temperature':result.temperature,
'humidity':result.humidity
})

这样,HomeBridge就可以访问到实时的室内温度了!

开机自启

一开始使用的nohup命令把进程启动放在了/etc/rc.local中,然后状态就是homebridge进程经常被kill掉,后来想到了node.js的pm2进程守护,所以利用pm2的startup来开机启动,并且还能保护进程:

1
2
3
4
pm2 start homebridge
pm2 save
pm2 startup
pm2 save

最后,启动!开心!

👉这里可以在Github上找到更多关于HomeBridge的信息。

HomeBridge的扩展性可以说是很高了,配合小米网关基本上可以连接所有的小米智能家居,感觉准备要买一堆来玩一玩了。

emmm,折腾了一天,终于可以知道目前房间里多少度了…杭州也开始冷了。

一个简易的NAS

发现手头还有一块闲置的160G的2.5寸硬盘,于是JD上买了个硬盘盒准备搞个简单的NAS过过瘾。

格式化磁盘

新建分区

使用fdisk -l查看磁盘列表,找到未分区的磁盘名称,然后执行fdisk /de/sda进行分区:

1
fdisk /dev/xvdb

分区后,再次使用fdisk -l查看磁盘的标识符,执行格式化分区命令:

1
mkfs.ext4 /dev/sda1

这样,磁盘就被格式化成了EXT4格式了。

挂载分区

格式化完毕后需要将磁盘挂在在系统中,创建一个目录,把这个目录作为挂载点:

1
2
3
mkdir /media/nas
mount -t auto /dev/sda1 /media/nas
mkdir /media/nas/shares #nas 分享目录

自动挂载

当树莓派重启后,需要手动再次挂载。太麻烦了!所以需要系统自动挂载,vim编辑/etc/fstab文件,将刚才的挂载分区写入到文件中:

1
/dev/sda1 /media/nas ext4 defaults 0 0

这样,重启树莓派后,系统就可以自动挂载了,美滋滋哈哈哈哈,最后df -lh查看被挂载的分区。

安装Samba服务

目前来说比较成熟的服务应该就是Samba了,(至少目前找得到的服务。一套apt-get走下去:

1
2
3
apt-get install samba
apt-get install samba-common-bin
cp /etc/samba/smb.conf /etc/samba/smb.conf.bak #备份配置文件

编辑一下conf配置文件,把共享的目录配置一下:

1
2
3
4
5
6
7
vim /etc/samba/smb.conf
[nas]
path = /media/nas/shares #共享的目录
valid users = user #共享的用户名
browswable = yes
public = yes
read only = no

配置完成之后重启Samba服务:

1
/etc/init.d/samba restart

之后给用户配置一个密码就可以了:

1
smbpasswd -a user

最后就可以正常的在电脑和手机访问硬盘中的文件了!

不得不说的速度

经过一下午的配置+实测,和大家的意见一致:上传速度特!别!慢!最快终于达到了1.91MB/s,所以该上群晖的还是得上群晖。(手动捂脸

进阶

连接蓝牙

突然想利用Raspberry Pi做一个类似于Jarvis的管家,在我进门或出门的时候可以语音交互,正好手头有个闲置的蓝牙音响,棒的是Raspberry Pi提供了蓝牙模块,可以通过蓝牙无线连接到小米音响上:

1
bluetoothctl

然后就可以像手机一样连接蓝牙设备那些吧啦吧啦的操作了:

1
2
3
4
5
power on # 打开蓝牙控制器
scan on # 扫描蓝牙设备
pair xxx # 进行设备配对
trust xxx # 信任蓝牙设备
connect xxx # 连接至蓝牙设备

xxx为设备的MAC地址,这时候蓝牙模块基本就可以连接上了,但是需要注意的是,如果连接的是蓝牙音响,还需要安装一些3rd依赖:

1
2
sudo apt-get install pulseaudio-module-bluetooth 
pulseaudio --start

如果PulseAudio启动不成功,还需要安装:

1
sudo pactl load-module module-bluetooth-discover

其他的情况可以查看这个问题:

最后,就可以愉快的连接到小米蓝牙音箱了!

AirPlay

利用shairport-sync可以将Raspberry Pi变成一个AirPlay终端,只需要apt-get一下就可以:

1
apt-get install shairport-sync

然后在/etc/shairport-sync.conf修改一下配置文件,比如修改名称:

1
2
3
4
5
6
7
// General Settings
general =
{
// ...
name = "HomePod2";
// ...
};

之后重启服务:

1
sudo systemctl restart shairport-sync.service

这样iOS / MacOS设备就可以将音频推送到Raspberry Pi了,炒鸡方便。

SR501传感器

为了能够监测到人体是否移动使用了HC-SR501传感器,人走过的时候输出高电平,所以只需要判断GPIO的引脚的高低电平就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
GPIO.setmode(GPIO.BCM)
PIR_PIN = 27
GPIO.setup(PIR_PIN, GPIO.IN)
try:
while True:
if (GPIO.input(PIR_PIN)):
print(msg_body['watch']['message'])
# 高电平时处理
time.sleep(3)

except KeyboardInterrupt:
GPIO.cleanup()
print("User aboard.")

理论上应该没错,可是等到接受信号的时候一直输出高电平,又以为是个坏的时候突然想起来延时时间是不是没有复位,就查看了一下两次变成低电平的时间…没错,延时时间有162秒!(卖家说默认2s的!

调整了延时时间,当有人体走过的时候自动给Pushover发送一条推送,一个简单的人体感应系统就完成了!

目前的缺点是:

  • 不能判断是进门还是出门,也就不能做到进出门执行相应的操作,比如控制小爱同学
  • 感应距离尽管调成了最小还是比较大,距离能在1m之内就好了

在家状态

为了能够判断是否在家,目前想到的解决办法是判断手机是否连接到了家里的WiFi,若手机在线则取消推送,使用arp -a可以打印本局域网下所有的设备信息,再判断MAC地址就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
import os
import sys

mac_list = [
'e4:2a:dc:12:9a:de',
'3c:2e:19:92:32:b3'
]

arp = str(os.popen('arp -a').readlines())
if mac_list[0] in arp or mac_list[1] in arp:
print True
else:
print False

连接至HomeBridge

homebridge-hc-sr501插件可以将SR501连接到HomeBridge中,只需要在config.json加入:

1
2
3
4
5
6
7
"accessories" : [
{
"accessory" : "HC-SR501",
"name" : "Motion Sensor",
"pinId" : 24
}
]

家庭中就出现传感器的状态了,当然也可以设置是否检测到动作推送通知或者实现自动化。

DashBoard

完成了硬件的连接,就可以考虑如何做一个DashBoard了,开始的想法是做一个H5的页面,这样在家外也可以随时查看,但是一想Raspberry Pi只是安装的lite版,所以准备用命令行写一个简单的DashBoard。

首先就是确定布局了,找了很久,发现了一个适用于Python的Terminaltables,可以很方便的就把表格画出来,其中:在测试中,Terminaltables这个库SingleTable这个类有些问题,2*2表格的时候会有错位:

1
pip install terminaltables

安装之后就可以尝试一下Demo:

1
2
3
4
5
6
7
from terminaltables import AsciiTable
data = []
data.append(['Row one column one', 'Row one column two'])
data.append(['Row two column one', 'Row two column two'])
data.append(['Row three column one', 'Row three column two'])
table = AsciiTable(data)
print table.table

和WEB网页一样,整个DashBoard最麻烦的感觉就是布局了,像gotop那种曲线目前还是无法实现,只用最简单的表格展示了系统的一些状态、传感器状态和移动手机卡的剩余流量等状态。

移动卡的数据是前一段时间偶然间抓包抓到的,GET明文就可以查询到剩余流量、剩余话费和剩余通话时间,可以说是很全面了。(逃

为了格式化表格,需要获取终端的窗口大小,目前只是返回了窗口的宽度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def getTerminalSize():
env = os.environ

def ioctl_GWINSZ(fd):
try:
import fcntl
import termios
import struct
import os
cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,
'1234'))
except:
return
return cr
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
if not cr:
try:
fd = os.open(os.ctermid(), os.O_RDONLY)
cr = ioctl_GWINSZ(fd)
os.close(fd)
except:
pass
if not cr:
cr = (env.get('LINES', 25), env.get('COLUMNS', 80))
return int(cr[1])

有了大小,为了能够格式化输出,写了一个方法用来格式化输出每条数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

def get_block_character(row, strs, align):
terminal_width = str((getTerminalSize() / row) - row)
if align == 'r':
width_mat = "{0:>" + terminal_width + "}"
elif align == 'l':
width_mat = "{:<" + terminal_width + "}"
else:
width_mat = "{:^" + terminal_width + "}"
if strs == 'error' or strs == 'offline' or strs == 'invalid':
s = '\033[1;31m' + width_mat.format(strs) + '\033[0m'
elif strs == 'ok' or strs == 'success':
s = '\033[1;32m' + width_mat.format(strs) + '\033[0m'
else:
s = width_mat.format(strs)
return s

其中:str是传入的字符,align是字符的居中方式,其中,一些关键词会加上颜色再返回,其他的颜色可以参考这篇文章

系统状态可以使用shell命令的uptime -s获取Raspberry Pi启动时间,传感器就实时获取就可以了,使用schedule做一个定时任务,每分钟刷新一下:

1
2
3
4
5
6
7
8
9
10
11
def job():
draw_table()

schedule.every(1).minutes.do(job)

if __name__ == "__main__":
print 'init system...'
print 'init system ok.'
while True:
schedule.run_pending()
time.sleep(1)

这样,一个命令行的DashBoard就完成了,然鹅最后一个表格少了1个像素可以说是很难受了,最后把闲置的iPad利用起来,使用WebSSH连接SSH放在床头就可以了!

遇到的坑

0x01号巨坑

把树莓派的首选LANG改为zh_CN.UTF-8之后,发现iTerm2中文字符一直乱码,一开始以为树莓派的语言设置没有设置好,就一直在locale gen,发现中文字符永远都是???。后来用了系统的终端后,显示变正常了?!

然后比较了一下两个终端的字符集,发现系统终端是正常的LANG="zh_CN.UTF-8"而iTerm2的字符集为空…

后来更新了iTerm2,更改了默认字符集,中文显示正常了。

最近更新了Mojave之后,遇到了好多坑。(再次捂脸

0x02号巨坑

昨天接入SR501传感器的时候关了一次机,然后就再也启动不起来了。由于手里没有显示器,周边也借不到就不知道到底哪里出错了。重新换了一张SD卡烧录了系统正常启动,所以排除硬件的问题,最后判断应该是shutdown之后导致文件系统出错了。

把手里的一台Windows电脑装上了Cent OS,插入SD卡,尝试文件修复:

1
fsck -fy /dev/sdb4

系统显示出错,并尝试修复,本以为万事大吉,结果…仍然不能启动。尝试了各种方式均不能修复,最后只能重新烧录了系统,因为源码还没有备份最后只能Rewrite…

所以!一定要记得备份!备份!备份!