使用微信+树莓派+ Arduino+服务器构建你的看门狗 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
pjhubs
V2EX    程序员

使用微信+树莓派+ Arduino+服务器构建你的看门狗

  •  
  •   pjhubs
    windstormeye 2018 年 6 月 21 日 3758 次点击
    这是一个创建于 2802 天前的主题,其中的信息可能已经有所发展或是发生改变。

    使用微信+树莓派+ Arduino +服务器构建智能家庭小助手

    前言

    这是我去年的大创项目《一种基于微信的主动式家庭智能监测系统设计与实现》,因为时间关系,一直都没有好好的梳理一遍应该如何去复现它,最近时间较为充裕,我会较为仔细的描述清楚该项目的核心难点(其实并没有难点)。当初报这个项目是为了学习一些硬件的简单相关知识,再结合一下前年( 17 年的项目要在 16 年年末申报)的社会热点问题,当时大家都比较热衷于“智能家庭”的概念。当时的小米“家庭智能”套件火的是一塌糊涂,甚至还出了贺岁版礼包(如果我没记错的话),再结合当时对自己的技术路线的一个定位,需要弥补一些关于硬件的知识,遂有了这个项目。

    因为时间间隔的比较久远,不保证复现过程中 100%正确,如果你有跟着走,出现了问题请务必告知,我们一起完善!大部分都是PythonArduino代码,建表 SQL 因为没法保证大家的物料跟我是一致的,而且大家也不一定会做的跟我完全一样,这块就保留了吧。当然,如果你喜欢论文严谨的格式,也可以到知网 down 下与本项目相关的渣作

    物料准备

    我将使用微信公众号、树莓派、Arduino 和一台乞丐版配置的云服务器构建一个智能家庭小助手,用于协助我们对室内环境有一个较好的把控。如果你什么都没有可以参考以下清单先行购买物料(所有的必须物料下来,勉强三百多一些?):

    1. 一块树莓派。版本随意,如果你资金比较充裕,可以购买最新型号的树莓派,毕竟最新的 3B 型号 wifi 模块信号更好,整体的处理速度更快。¥ 150~300
    2. 一套 Arduino 开发套件。注意,是开发套件而不是 Arduino 这一块板子,我们需要开发套件中的其它元器件。¥ 150~300
    3. 一台云服务器。如果你要用自己的电脑也可以,在校园网、小区、公司内记得先做内网穿透,不过一台乞丐版的服务器也没多少钱,能省很多事。¥ 0~10
    4. 微信公众号 如果你之前没申请过的话,貌似开通审核得等两三天?¥ 0

    信息配置

    如果一切顺利,现在你的手上应该有一块树莓派、一套 Arduino 开发套件、一台云服务器、一个微信公众号。微信提供了一套公众号开发 SDK,可以使用它,虽然官方提供开发文档已经非常成熟了,但还是觉得不够简洁。在此推荐大家使用itchatmp

    微信公众号: 进入微信公众平台在左下角找到“开发”-“基本配置”,

    在该页面中填写相关信息,

    1. 服务器地址( URL ):填写 IP 地址。但必须是公网 IP 或者已经做了内网穿透的 IP 地址,也可解析好域名后填入对应域名。
    2. 令牌( Token ):用于微信公众号和服务器进行双向交互时的验证。
    3. 消息加解密密钥:随意。 所有内容都填写完毕后,别着急提交。进行下一步,

    服务器

    登录服务器后,先检查是否安装了 Python 环境(可直接上 Python3 )。安装完成后,使用 pip 下载 itchatmp,

    $ pip install itchatmp 

    下载完成后,新建一个.py 文件(此处以 mp.py 为例),在文件中写下,

    import itchatmp itchatmp.update_config(itchatmp.WechatConfig( # 填写上一步在微信公众号的配置内容 token='yourToken', appId = 'yourAppId', appSecret = 'yourAppSecret')) @itchatmp.msg_register(itchatmp.content.TEXT) def text_reply(msg): return msg['Content'] itchatmp.run() 

    此时执行,(需要 root 权限)

    $ python mp.py 

    看到下边这句话后就可以去微信公众号点击确认啦~

    itchatmp started! press Ctrl+C to exit. 

    效果: 进入到对应的微信公众号中,你输入任何内容,它都会给你返回相同的内容。如果微信公众平台告诉你 Token 验证失效估计就是你的 IP 地址不对。

    数据库

    使用数据库是为了存储数据(完全可以使用 txt 文件来维护),在此为了简化手拼 SQL 易出错以及本项目并不需要进行多少性能优化的情况下,直接采用ORM (对象关系映射技术)。 P.S.我将采用sqlalchemy这个框架进行,在廖雪峰的博客上有较为细致的讲解,大家可以先自行研究一番到底是个什么东西。

    这是定义好的硬件类,其实也就是硬件表,

    # 硬件表 class Hardware(Base): __tablename__ = 'hardware' id = Column(Integer, primary_key=True) name = Column(String(64), nullable=False) status = Column(Integer, nullable=False) num = Column(Integer, nullable=False) 

    新建一个 py 文件(以 test.py 为例),在其中写下,

    from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, String, Integer from sqlalchemy.orm import sessionmaker # '数据库类型+数据库驱动名称://用户名:口令 @机器地址:端口号 /数据库名' engine = create_engine('mysql+mysqldb://root:mimamima@localhost:3306/restful?charset=utf8') Base = declarative_base() Base.metadata.create_all(engine) Session = sessionmaker(bind = engine) session = Session() 

    到这一步为止,就完成了使用 ORM 进行 MySQL 数据库操作的铺垫。接下来,我们将进行数据库的增删改查方法的编写。

    1. 增加一个元器件:
    # 添加电子原件方法 # 原件 name 及针脚 num 需要配置 # 原件状态默认关闭 def addNewUnit(hardwareName, status, num): Base.metadata.create_all(engine) Session = sessionmaker(bind=engine) session = Session() unit = Hardware( name = hardwareName, status = status, num = num) session.add(unit) session.commit() 
    1. 修改一个元器件的状态:
    # 执行 write 操作 def writeHardware(hardwarename, status, num): unit = readHardware(hardwarename) unit = session.query(Hardware).get(unit.id) if unit: unit.status = status if 'Unit' in hardwarename: unit.num = num; session.add(unit) session.commit() return '操作成功' return '操作失败,请联系管理员' 
    1. 读取一个元器件的状态:
    # 执行 read 操作 def readHardware(hardwarename): Base.metadata.create_all(engine) Session = sessionmaker(bind = engine) session = Session() unit = session.query(Hardware).filter_by(name=hardwarename).first() return unit 
    1. 稍微做了点封装的 update 方法:
    # 电子原件执行 read 或 write 筛选方法 def updateStatusWithHardware(tableName, operatorStatus, hardwarename, status): if tableName == 'hardware': if operatorStatus == 1: return writeHardware(hardwarename, status, 0) else: return readHardware(hardwarename) 

    现在我们完成了 test.py 的编写,主要完成了使用 ORM 技术编写了操作数据库的各种方法。接下来,我们要使用微信公众号对数据库进行修改。

    上位机配置

    在这个环节中,我们要做到用户发送“开灯”、“关灯”、“开风扇”、“温度”等消息给公众号后,能够在数据库中看到状态被修改并且反馈。

    简单的来概括一下要做的工作:首先要让服务器接收到公众号发送而来的消息;其次要对发送者进行筛选,不能谁都可以操作这套系统;接着匹配消息,执行不同的方法;最后给公众号反馈回消息。

    服务器接收公众号发送的消息我们已经在第一步中完成了,现在要对接收到的消息体进行解析,根据 userID 来筛选谁能对这套系统进行操作。我的做法非常简单,用一个"pjhubs.txt"文件保存了能够操作这套系统的用户 ID。每次接收到消息时,都先从消息体中取出 fromUserName 字段数据与 txt 文件中的数据进行比对,如果在 txt 文件中才允许接着进行操作。

    import itchatmp import test # 配置微信公众号信息 itchatmp.update_config(itchatmp.WechatConfig( token='你的 token', appId = '你的 appId', appSecret = '你的 appSecret')) # 接收用户消息 @itchatmp.msg_register(itchatmp.content.TEXT) def text_reply(msg): toUserName = msg['FromUserName'] cOntent= msg['Content'] isCOntain= 0 # pjhubs.txt 为有权限的用户列表 f = open("pjhubs.txt","r") lines = f.readlines() for line in lines: if line[:-1] == toUserName: isCOntain= 1; if isCOntain== 0: return '该系统并未对您开放,请联系 PJ 进行配置' else: if cOntent== '添加': # test.addNewUnit('tempUnit', 1, 2) return '操作成功!' elif cOntent== '开灯': return test.updateStatusWithHardware('hardware', 1, 'redLED', 1) elif cOntent== '关灯': return test.updateStatusWithHardware('hardware', 1, 'redLED', 0) elif cOntent== '温度': unit = test.updateStatusWithHardware('hardware', 0, 'tempUnit', 1) returnString = '当前温度为:' + str(unit.num) + '°' return returnString eif cOntent== '开风扇': return test.updateStatusWithHardware('hardware', 1, 'tempUnit', 1) elif cOntent== '关风扇': return test.updateStatusWithHardware('hardware', 1, 'tempUnit', 0) # 新用户关注公众号时 @itchatmp.msg_register(itchatmp.content.EVENT) def user_management(event): if(event['Event']=='subscribe'): return u'欢迎来到 PJHubs,如果你想试用室内环境智能监测系统,请联系 PJ' itchatmp.run() 

    执行,

    $ python mp.py 

    在微信公众号中发送“开灯”、“关灯”、“开风扇”、“温度”等指令都会对数据库进行操作。此时可以 select 对应表查看数据是否一致再进行下一步。

    API 编写

    这是知乎上一些关于 API 的内容讲解。我们在此使用Flask轻量级的 web 框架进行 API 编写。主要是给树莓派操作数据库使用的。 通过 pip 安装好 flask 后,我们可以先尝试写一个最简单的 restful 格式的 API:

    from flask import Flask from flask_restful import Resource, Api from flask import jsonify, request from flask import abort from flask import make_response import test app = Flask(__name__) api = Api(app) @app.route('/') def index(): return 'Get out!' if __name__ == '__main__': app.run(host='0.0.0.0',debug=True) 

    此时我们去浏览器中输入 ip 地址或域名,即可看到“ Get out!”这句话。现在我们要接着编写几个资源访问路径以便树莓派访问。

    # 获取所有硬件信息(求快可以这么写) @app.route('/dachuang/api/v1/allHardware') def get_allHardware(): LED = test.readHardware('redLED') UNIT= test.readHardware('tempUnit') LEDres = { 'id' : LED.id, 'name' : LED.name, 'status' : LED.status, 'num' : LED.num } UNITres = { 'id' : UNIT.id, 'name' : UNIT.name, 'status' : UNIT.status, 'num' : UNIT.num } return jsonify([LEDres, UNITres]) # 更新固定元器件(求快用了 GET,最好是 POST ) @app.route('/dachuang/api/v1/updateHardware', methods=['GET']) def get_updateHardware(): hardwarename = request.args.get('hardwarename') status = request.args.get('status') num = request.args.get('num') if status == '3': unit = test.readHardware(hardwarename) test.writeHardware(hardwarename, unit.status, num) else: test.writeHardware(hardwarename, unit.status, num) return jsonify({'code' : '1'}) 

    我们只需要起两个 API 服务即可满足要求。此时我们可以根据写好的 API 访问规则到浏览器中验证一番。

    下位机配置树莓派

    树莓派是整套系统的灵魂所在,对上承载着数据库的更新,对下负担着 Arduino 的操作。当然,如果不考虑性能你可以直接用 Arduino 的 wifi 模组,直接对 API 发起请求。

    树莓派首先要去在固定时间间隔内轮询特定 API,根据 API 反馈回来的数据对固定串口发送特定字符,接收 Arduino 传递上来的数据,拼接 API 更新数据库。

    serial 是对树莓派上的串口进行操作库,urllib2 是网络请求库,json 是解析和发送 JSON 格式库。

    import serial import urllib2 import json hostname = 'http://你的地址 /dachuang/api/v1/allHardware' # /dev/ttyACM0 是树莓派上编号为 0 的 USB 口(可以在 /dev 目录下通过观察拔插对应的 USB 口找到对应的编号) ser = serial.Serial('/dev/ttyACM0', 9600, timeout = 4) while 1: r = urllib2.Request(hostname) r = urllib2.urlopen(r) res = r.read() result = json.loads(res) print result send = '' # 通过 json 库解析完后的数据就是字典 if result[0]['status'] == 1: send += 'a' else: send += 'A' if result[1]['status'] == 1: send += 'b' else: send += 'B' # 从下位机 Arduino 上读取到的数据拼接 URL 发送回服务器,更新数据库 ser.write(send) respOnse= ser.readall() if '' != response: respOnse= response[0:2] ret = urllib2.Request("http://你的地址 /dachuang/api/v1/updateHardware?hardwarename=tempUnit&status=3" + '&num=' + response) ret = urllib2.urlopen(ret) 

    我在此重新定义了一套操作流程, a -> “开灯” A -> “关灯” b -> “开风扇” B -> “关风扇” 因为受到 Arduino 本身性能的影响,如果你还给它发一长串的字符串比如“ open light ”等,那估计单单就解析并匹配,分时操作已经过了。。因此我才想重新定义一套 ASCII 码关系映射,并且限制树莓派每次轮询的时间为 4 秒一次,可根据用户所搭建的下位机硬件系统复杂适度增减轮询时间。

    下位机配置 Arduino

    Arduino 要做的事情只有接收串口数据,解析串口数据,根据数据分别操作不同的硬件。Arduino 用 C 写的,定义了一套规则,用起来非常顺手亲切。

    #define yellowLED 13 #define REDled 12 #define Buzzer 8 #define fanPin 2 void setup() { Serial.begin(9600); // 9600 bps pinMode(yellowLED, OUTPUT); pinMode(Buzzer,OUTPUT); pinMode(REDled,OUTPUT); pinMode(fanPin,OUTPUT); } void loop() { //读取 A0 口的电压值,温度传感器所在串口 int n = analogRead(A0); //使用浮点数存储温度数据,温度数据由电>压值换算得到 float vol = n * (5.0 / 1023.0*100); if ( Serial.available() ) { // 向串口写入温度 Serial.println(vol); // 读取树莓派写入串口的数据 int res = Serial.read(); // 根据 ASCII 码执行不同硬件操作函数 if (res == 97) { digitalWrite(yellowLED, HIGH); } if (res == 65){ digitalWrite(yellowLED, LOW); } if (res == 98) { digitalWrite(fanPin, HIGH); } if (res == 66) { digitalWrite(fanPin, LOW); } } // 超过 30°后开启高温预警,蜂鸣器奏响和风扇打开 if (vol > 30) { buzzerBegin(); } } // 蜂鸣器响铃 void buzzerBegin() { digitalWrite(fanPin, HIGH); digitalWrite(REDled, HIGH); //频率从 200HZ 增加到 800HZ,模拟警报声 for(int i=200;i<=800;i++) { tone(Buzzer,i); delay(5); } delay(100); for(int i=800;i>=200;i--) { tone(Buzzer,i); delay(5); } digitalWrite(REDled, LOW); digitalWrite(fanPin, LOW); } 

    上下位机联调

    至此我们完成了全部的基础工作,在联调的过程中,当初我也发生了非常多的问题,这无法避免,稍不注意电路连错了以后就全盘皆输了,在此我只能祝大家好运,Arduino 连接各个元器件的方式并没有展开,因为我相信大家的电路设计一定比我强!

    在联调的过程中,你需要做的是,

    1. 运行 restful.py ,把整套 API 服务跑起来;
    2. 运行 mp.py ,让公众号和服务器打通;
    3. Arduino 通过 USB 与树莓派相连后,树莓派再通电;
    4. 在公众号上发送指令,观察 Arduino 上元器件状态变化。

    结果:

    github 地址:https://github.com/windstormeye/watchDog

    7 条回复    2018-10-03 08:54:56 +08:00
    d0m2o08
        1
    d0m2o08  
       2018 年 6 月 21 日
    占楼 mark
    zjgsamuel
        2
    zjgsamuel  
       2018 年 6 月 21 日
    Mark~
    pjhubs
        3
    pjhubs  
    OP
       2018 年 6 月 22 日
    如果大家感兴趣,项目在 github 上,地址在文末~可以跟我一起完善它
    shuperjolly
        4
    shuperjolly  
       2018 年 6 月 22 日
    严重支持。。。。。非常感兴趣,只是最近太忙了,对这块有非常多大胆的想法
    pjhubs
        5
    pjhubs  
    OP
       2018 年 6 月 22 日 via iPhone
    @shuperjolly 啊哈哈哈。好!我等着您的想法,我们一起完善它!
    idaliang
        6
    idaliang  
       2018 年 6 月 22 日 via Android
    插眼
    pjhubs
        7
    pjhubs  
    OP
       2018 年 10 月 3 日 via iPhone
    统一回复,谢谢大家关注
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     650 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 22:01 PVG 06:01 LAX 14:01 JFK 17:01
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86