毕业季又要到了,而这次我加入了毕业季的大军,在外面租了房子。但是不知道为什么,每天上班我都会担心家里会不会遭小偷?有没有哪个插头没拔会不会引起火灾啊?听说这是病,只可惜我没钱治,不过好在我有树莓派。
好吧,我们要达到的目标是树莓派通过传感器判断是否有人闯入或者是否有火焰,并将数据发送到服务器,手机再从服务器获取数据。 于是流程大概就是这样的:
硬件准备:
树莓派
人体红外感应模块
火焰传感器
10K电阻
面包板和杜邦线
人体红外感应模块和火焰传感器都是通过树莓派的 GPIO 口与树莓派连接的,我们使用 11 号和 12 号引脚。树莓派的 GPIO 有多种编号方式,为了防止懵逼我们这里就只说一种,看图,下面那个迷之突起就是代表它是 1 号引脚,别人还友好的标出了 P1 。
我们用 12 号引脚连接人体红外感应模块, 11 号引脚连接火焰传感器。
硬件连接很简单,人体红外感应模块虽然供电要求 5V 但是信号输出却是 3.3V ,而且电流很小,所以信号输出 OUT 直接连接 12 号引脚就行了。人体红外感应模块也就是一个热释电模块,人体辐射的红外线中心波长为9~10–um,若探头接收到人体 辐射的 红外线则输出高电平。简单的说就是有人时输出高电平,没人时输出低电平。当然,这么一来弱点也很明显,第一就是容易受到其他热源的影响,第二就是 人体的红外辐射容易被遮挡, 小偷如果用很奇怪的东西把自己包裹的很好,那么就无能为力了。记得小时候看科普节目,有人用一面玻璃骗过了红外感应报警器。
火焰传感器就是探测有没有火焰嘛,它自身的电阻值会随着火焰的增大而变大。那么如何采集信号呢?我们让它与10K电阻串联,初中学过串联分压,电阻值越大分得的电压也就越大。蛋似 ,它是输出模拟信号, 而树莓派没有自带 ADC ,怎么办呢?当然我们可以直接买一个数字信号输出的火焰传感器模块,也可以自己买个 ADC 芯片,而我试了试,当火焰靠近传感器时输出电压能达到 3V ,也就是说满足了树莓派对高电平的定义。当然了,这样的话只有火焰足够大才能报警,但也比没人知道发生火灾的情况好。火焰传感器的连接方式如图,不过要注意的是, 树莓派的 GPIO 电平是 3.3V ,所以这里的 VCC 也应该是 3.3V , 信号输出就直接连到 11 号引脚就好啦。
然后附上引脚的编号图,大家对照着图给人体红外传感器和火焰传感器连接上VCC和GND就好了。
比如我是1号引脚给火焰传感器供电,9号引脚作为火焰传感器的GND,11号引脚作为火焰的信号线;2号引脚给人体红外感应模块供电,6号作为人体红外感应模块的GND,12号引脚是信号线。
连接好后大概就是这样:
然后就是编程了,树莓派上我们当然用 python , python 可以很方便的操作 GPIO 。我们读取 GPIO 的电平情况然后发送到服务器就行了。
# -*- coding:utf-8 -*-
import urllib
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BOARD)# 设置 GPIO 编号的方式
GPIO.setwarnings(False)#取消警告
GPIO.setup(11,GPIO.IN)# 设置 11 号引脚为输入
GPIO.setup(12,GPIO.IN)# 设置 12 号引脚为输入
if __name__ == '__main__':
while True:
i = GPIO.input(12)# 读取 12 号引脚的电平,低电平为 0 ,高电平为 1
f = GPIO.input(11)# 读取 11 号引脚的电平
url = 'http://xxx.xxx.xxx.xxx:5000/record/?i='+str(i)+'&f='+str(f)
print url
web = urllib.urlopen(url)
print web.read()
time.sleep(5)#每5秒钟发送一次数据
在服务器这边,我们就简单的用 flask 写一个 api 吧。
首先要给树莓派提供一个发送信息的 api ,为了方便我们使用 GET请求的 方式发送数据,参数 i 表示否是有人闯入,参数 f 表示是否有火焰。之后要给手机留一个获取信息的 api ,返回 json 格式的数据,当然为了方便我们也是使用GET请求数据。其实这种对实时性有要求的项目还是用socket比较靠谱。代码大概就是长这个样子,我承认我偷了个懒 。
# -*- coding : utf-8 -*-
from flask import Flask,request,jsonify
app = Flask(__name__)
isFire = 0
isInvaded = 0
@app.route('/record/',methods=['GET'])
def record():
if request.method == 'GET':
global isInvaded
global isFire
try:
isInvaded = int(request.args.get('i'))
isFire = int(request.args.get('f'))
except:
return 'error'
return 'ok'
@app.route('/state/',methods=['GET'])
def state():
global isInvaded
global isFire
return jsonify({'isInvaded':isInvaded, 'isFire':isFire})
if __name__ == '__main__':
app.run(host='0.0.0.0')
再之后就是安卓手机这边了,其实功能简单到我都不知道界面该怎么做,就是显示有没有人闯入和有没有火焰。当然,我们在有人闯入和有火焰时也要有报警提示才行。
安卓这边也比较简单。首先是发送GET请求从服务器获取数据。貌似现在有新的方法做GET请求了,我很久没写过安卓app了所以方法有点老旧还请大家指教。
public class Webconn {
public static String getDev() {
String url = "http://XXX.XXX.XXX.XXX:5000/state/";
try {
HttpGet httppost = new HttpGet(url);
HttpResponse httpResponse = new DefaultHttpClient().execute(httppost);
if (httpResponse.getStatusLine().getStatusCode() == 200) {
String strResult = EntityUtils.toString(httpResponse.getEntity());
return strResult;
}
} catch (Exception e) {
Log.e("log_tag", "Request failed" + e.toString());
}
return "null";
}
}
当然不能在主线程里面直接做网络访问,我们另起一个线程,每5秒钟请求一次数据,并且通过handler与主线程通信。另外当发现有人入侵或者有火焰时,在通知栏弹出消息提示。
public class MainActivity extends Activity {
private Handler mHandler;
private TextView fire, people;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new Handler();
mHandler.post(new TimerProcess());
fire = (TextView) findViewById(R.id.fire);
people = (TextView) findViewById(R.id.people);
}
@SuppressLint("HandlerLeak")
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
String ret = msg.obj.toString();
switch (msg.what) {
try {
case 1:
StringBuilder builder = new StringBuilder();
JSONObject jsonObject = new JSONObject(ret);
Log.i("json", "" + jsonObject);
if (jsonObject.getInt("isFire") == 0) {
fire.setText("没有发现火焰");
fire.setTextColor(android.graphics.Color.GREEN);
} else {
fire.setText("发现火焰");
fire.setTextColor(android.graphics.Color.RED);
mes("火焰传感器探测到火焰!");
}
if (jsonObject.getInt("isInvaded") == 0) {
people.setText("安全");
people.setTextColor(android.graphics.Color.GREEN);
} else {
people.setText("发现入侵者");
people.setTextColor(android.graphics.Color.RED);
mes("发现有人进入房间!");
}
} catch (JSONException e) {
e.printStackTrace();
}
break;
default:
break;
}
}
};
private void getDevInformation() {
new Thread(new Runnable() {
@Override
public void run() {
String ret = Webconn.getDev();
System.out.println("get return:");
System.out.println(ret);
handler.sendMessage(handler.obtainMessage(1, ret));
}
}).start();
}
private class TimerProcess implements Runnable {
public void run() {
mHandler.postDelayed(this, 5000);
getDevInformation();
}
}
public void mes(String m) {
//定义NotificationManager
String ns = Context.NOTIFICATION_SERVICE;
NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);
//定义通知栏展现的内容信息
int icon = R.drawable.ic_launcher;
CharSequence tickerText = "我的通知栏标题";
long when = System.currentTimeMillis();
Notification notification = new Notification(icon, tickerText, when);
//定义下拉通知栏时要展现的内容信息
Context context = getApplicationContext();
CharSequence contentTitle = "树莓派安防";
CharSequence contentText = m;
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
//用mNotificationManager的notify方法通知用户生成标题栏消息通知
mNotificationManager.notify(1, notification);
}
}
这个主要还是对我的心里安慰,不过现在上班的地方离住的地方走路只要10分钟,所以即使有事我也能跑回来。这样想想,我都不用去治病了(雾)。
全部的东西都在这里:
http://pan.baidu.com/s/1pLy6vGn
*本文原创作者:zhangtory,本文属FreeBuf原创奖励计划,未经许可,禁止转载。