使用微信+樹莓派+Arduino+伺服器構建你的看門狗 ?

PJHubs發表於2018-06-21

使用微信+樹莓派+Arduino+伺服器構建智慧家庭小助手

使用微信+樹莓派+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

微信公眾號: 進入微信公眾平臺在左下角找到“開發”-“基本配置”,

使用微信+樹莓派+Arduino+伺服器構建你的看門狗 ?

在該頁面中填寫相關資訊,

使用微信+樹莓派+Arduino+伺服器構建你的看門狗 ?
  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
        elif 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上元器件狀態變化。

結果:

使用微信+樹莓派+Arduino+伺服器構建你的看門狗 ? 使用微信+樹莓派+Arduino+伺服器構建你的看門狗 ?

github地址:github.com/windstormey…

相關文章