Build smart home assistant with wechat + Raspberry PI +Arduino+ server

preface

This is my last year’s big “project” a WeChat based active family intelligent monitoring system design and implementation, because time relationship, has been without a good comb again should how to reconstruct it, time is relatively abundant, recently I will more careful description of the project’s core difficulties (there is no difficulty 🙄). At the beginning, I applied for this project to learn some simple knowledge about hardware, combined with the social hot issues of the year before last (the project of 2017 should be applied at the end of 2016). At that time, everyone was quite keen on the concept of “smart family”. At that time, Xiaomi’s “home intelligence” kit was in a complete mess, and even issued a New Year’s edition gift package (if I remember correctly). Combined with a positioning of their own technical route at that time, they needed to make up some knowledge about hardware, so this project came into being.

Since the time interval is quite long, it is not guaranteed that the reproduction process is 100% correct. If you follow along and have any problems, please be sure to inform us, and we will improve together! Most of them are Python and Arduino code, and SQL table is built because there is no guarantee that everyone’s materials are the same as mine, and everyone may not be able to do exactly the same as mine, so I will keep this part. Of course, if you like the rigorous format of the paper, you can also go to the relevant works of this project on THE WEBSITE of CNKI Down

Material preparation

I will use the wechat official account, raspberry PI, Arduino and a beggar version of the configuration of the cloud server to build a smart home little assistant, to help us have a better control of the indoor environment. If you don’t have anything, you can refer to the following list to purchase materials first (all necessary materials down, barely more than 300?). :

  1. A piece of raspberry pie. The version is optional, if you have more money, you can buy the latest model of raspberry PI, after all, the latest MODEL 3B wifi module better signal, overall faster processing speed. RMB 150 ~ 300
  2. An Arduino development kit. Note that we are developing the kit, not the Arduino board, and we need to develop other components in the kit. RMB 150 ~ 300
  3. A cloud server. If you want to use their own computer can also, in the campus network, community, the company remember to do Intranet penetration, but a beggar version of the server also did not have much money, can save a lot of things. 0 ~ 10 RMB per month
  4. If you have not applied for wechat public account before, it seems that you have to wait two or three days for opening and approval? RMB 0

Configuration information

If all goes well, you should now have a raspberry PI, an Arduino development kit, a cloud server, and a wechat account. Wechat provides a set of public number development SDK, you can use it, although the official development document has been very mature, but still feel that it is not simple enough. Itchatmp is recommended.

Wechat public account: Enter wechat public platform and find “development” – “Basic configuration” in the lower left corner.

Fill in the relevant information on this page,

  1. Server ADDRESS (URL) : Enter the IP address. The IP address must be a public IP address or one that has been penetrated through the Intranet. You can also enter the corresponding domain name after resolving the domain name.
  2. Token: used to verify two-way interaction between the wechat official account and the server.
  3. Message encryption and decryption key: optional. Don’t be in a hurry to submit it when everything is filled out. To go to the next step,

The server

After logging in to the server, check to see if the Python environment is installed (Python3 can be installed directly). Once installed, use PIP to download itchatMP,

$ pip install itchatmp
Copy the code

Once the download is complete, create a.py file (for example, mp.py) and write,

import itchatmp

itchatmp.update_config(itchatmp.WechatConfig(
  Fill in the configuration content of wechat public account in the previous step
   token='yourToken',
   appId = 'yourAppId',
   appSecret = 'yourAppSecret'))
   
@itchatmp.msg_register(itchatmp.content.TEXT)
   def text_reply(msg):
     return msg['Content']

itchatmp.run()
Copy the code

Execute at this point (root permission required)

$ python mp.py
Copy the code

After reading this sentence, you can go to the wechat official account and click confirm ~

itchatmp started! press Ctrl+C to exit.
Copy the code

Effect: Enter the corresponding wechat official account, you enter any content, it will return to you the same content. If the wechat public platform tells you that the Token verification is invalid, it probably means that your IP address is wrong.

The database

The use of database is to store data (can use TXT file to maintain), in order to simplify the manual SQL prone to error and this project does not need to carry out much performance optimization, directly using ORM (object relational mapping technology). For P.S., I will use the framework of SQLAlchemy, which will be explained in detail on Liao Xuefeng’s blog. You can first study what it is.

This is the defined hardware class, which is essentially the hardware table,

# hardware table
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)
Copy the code

Create a new py file (for example, test.py) and write,

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, String, Integer
from sqlalchemy.orm import sessionmaker

# 'database type + database driver name :// user name: password @machine address: port number/database name'
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()
Copy the code

At this point, you have completed the preparation for MySQL database operations using ORM. Next, we will add, delete, change and check the database method.

  1. Add a component:
Method of adding electronic originals
Name and pin num need to be configured
The original state is disabled by default
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()
Copy the code
  1. Modify the state of a component:
# Execute 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 'Operation successful'
     return 'Operation failed, please contact administrator'
Copy the code
  1. Read the state of a component:
# execute the read operation
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
Copy the code
  1. The update method is slightly encapsulated:
Electronic originals perform read or write filtering
def updateStatusWithHardware(tableName, operatorStatus, hardwarename, status):
     if tableName == 'hardware':
         if operatorStatus == 1:
             return writeHardware(hardwarename, status, 0)
         else:
             return readHardware(hardwarename)
Copy the code

Now that we’ve finished writing test.py, we’ve written the various methods to manipulate the database using ORM technology. Next, we will use the wechat public account to modify the database.

Upper computer Configuration

In this link, we want to make the user send “light”, “light”, “fan”, “temperature” and other messages to the public account, can see the state is modified in the database and feedback.

Simple to summarize the work to do: first of all to let the server to receive the public number sent to the message; Secondly, the sender should be screened. No one can operate the system. The message is then matched and a different method is executed; Finally feedback to the public back message.

The server receives the message sent by the public number. We have completed the first step. Now we need to parse the received message body and filter who can operate the system according to the userID. My approach is very simple, with a “pjhubs. TXT “file to save the user ID can operate the system. Each time a message is received, the fromUserName field data is extracted from the message body and compared with the data in the TXT file. If the data in the TXT file is allowed to proceed with the operation.

import itchatmp
import test

# Configure wechat public account information
itchatmp.update_config(itchatmp.WechatConfig(
    token='your token',
    appId = 'your appId',
    appSecret = 'your appSecret'))

Receive user messages
@itchatmp.msg_register(itchatmp.content.TEXT)
def text_reply(msg):
    toUserName = msg['FromUserName']
    content = msg['Content']
    isContain = 0
    # pjhubs. TXT is a list of authorized users
    f = open("pjhubs.txt"."r")
    lines = f.readlines()
    for line in lines:
        if line[:- 1] == toUserName:
            isContain = 1;   
    if isContain == 0:
        return 'This system is not open to you, please contact PJ for configuration'
    else:
        if content == 'add':
            # test.addNewUnit('tempUnit', 1, 2)
            return 'Operation successful! '
        elif content == 'turn on the light':
            return test.updateStatusWithHardware('hardware'.1.'redLED'.1)
        elif content == 'off':
            return test.updateStatusWithHardware('hardware'.1.'redLED'.0)
        elif content == 'temperature':
            unit = test.updateStatusWithHardware('hardware'.0.'tempUnit'.1)
            returnString = 'Current temperature:' + str(unit.num) + '°'
            return returnString
        elif content == 'Fan on':
            return test.updateStatusWithHardware('hardware'.1.'tempUnit'.1)
        elif content == 'Turn off fan':
            return test.updateStatusWithHardware('hardware'.1.'tempUnit'.0)
        
# When new users follow the official account
@itchatmp.msg_register(itchatmp.content.EVENT)
def user_management(event):
    if(event['Event'] = ='subscribe') :return U 'Welcome to PJHubs, if you want to try out an intelligent indoor environment monitoring system, please contact PJ'
itchatmp.run()
Copy the code

To perform,

$ python mp.py
Copy the code

Sending “light on”, “light off”, “fan on”, “temperature” and other commands in the wechat public account will operate the database. In this case, you can select the corresponding table to check whether the data is consistent before going to the next step.

The API to write

This is some content about API on Zhihu. We write the API here using Flask’s lightweight Web framework. It’s mainly for the raspberry PI operation database. With Flask installed via PIP, we can first try to write the simplest API in restful format:

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(a):
    return 'Get out! 🙂 '

if __name__ == '__main__':
    app.run(host='0.0.0.0',debug=True)
Copy the code

If you type in your IP address or domain name, you’ll see “Get out! 🙂. Now we are going to write a few resource access paths for raspberry PI to access.

Get all hardware information
@app.route('/dachuang/api/v1/allHardware')
def get_allHardware(a):
    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])

# Update fixed components (GET, POST)
@app.route('/dachuang/api/v1/updateHardware', methods=['GET'])
def get_updateHardware(a):
    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'})
Copy the code

We only need two API services to meet the requirements. At this point, we can verify in the browser according to the written API access rules.

Lower configuration – Raspberry PI

Raspberry PI is the soul of the whole system, carrying the update of database on the top and Arduino operation on the bottom. Of course, if you don’t care about performance you can just use Arduino’s wifi module and make requests directly to the API.

Raspberry PI first polls a specific API within a fixed time interval, sends specific characters to a fixed serial port according to the data feedback from the API, receives data from Arduino, and splices the API to update the database.

Serial is a library for handling serial ports on raspberry PI, URllib2 is a network request library, json is a library for parsing and sending JSON formats.

import serial
import urllib2
import json

hostname = 'your address http:// / dachuang/API/v1 / allHardware'
# /dev/ttyACM0 is a USB port 0 on raspberry PI.
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 = ' '
    The data parsed through the JSON library is a dictionary
    if result[0] ['status'] = =1:
        send += 'a'
    else:
        send += 'A'
    if result[1] ['status'] = =1:
    send += 'b'
    else:
    send += 'B'
    # Splicing the URL of the data read from the Arduino of the lower computer and sending it back to the server to update the database
    ser.write(send)
    response = ser.readall()
    if ' '! = response: response = response[0:2]
        ret = urllib2.Request("Http:// your address/dachuang/API/v1 / updateHardware? hardwarename=tempUnit&status=3" + '&num=' + response)
        ret = urllib2.urlopen(ret)
Copy the code

I have redefined a set of operation procedures: A -> “Turn on the light” A -> “Turn off the light” B -> “turn on the fan” B -> “Turn off the fan” Because of the influence of Arduino’s performance, if you send it a long string like “Open light”, it is estimated that it will simply parse and match. Time-sharing has passed. 😂. That’s why I want to redefine a set of ASCII relational mappings, and limit the polling time of raspberry PI to 4 seconds per poll, which can be increased or decreased according to the complexity of the hardware system of the lower computer.

Lower computer configuration — Arduino

All Arduino has to do is receive the serial port data, parse the serial port data, and manipulate the different hardware based on the data. Arduino, written in C, defines a set of rules that are very handy to use.

#define yellowLED 13
#define REDled 12
#define Buzzer 8
#define fanPin 2

void setup(a)  {
  Serial.begin(9600); // 9600 bps
  pinMode(yellowLED, OUTPUT);
  pinMode(Buzzer,OUTPUT);
  pinMode(REDled,OUTPUT);
  pinMode(fanPin,OUTPUT);
}
void loop(a) {
  // Read the voltage of port A0 and the serial port where the temperature sensor is located
  int n = analogRead(A0);    
  // Use floating point to store temperature data. Temperature data is converted from electricity > voltage value
  float vol = n * (5.0 / 1023.0*100);   
  if ( Serial.available() ) {
      // Write temperature to serial port
      Serial.println(vol);
      // Read the data that raspberry PI writes to the serial port
      int res = Serial.read();
      // Perform different hardware operations based on the ASCII code
      if (res == 97) {
        digitalWrite(yellowLED, HIGH);
      }
      if (res == 65){
        digitalWrite(yellowLED, LOW);
      }
      if (res == 98) {
        digitalWrite(fanPin, HIGH);
      }
      if (res == 66) { digitalWrite(fanPin, LOW); }}// When the temperature exceeds 30°, turn on the high temperature warning, buzzer and fan
    if (vol > 30) { buzzerBegin(); }}// The buzzer rings the bell
void buzzerBegin(a) {
  digitalWrite(fanPin, HIGH);
  digitalWrite(REDled, HIGH);
  // Increase the frequency from 200HZ to 800HZ, simulate the alarm sound
  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);
}
Copy the code

Uplink and downlink

So far we finished all the basic work, in the process of alignment, at the beginning I also happen a lot of problems, this is inevitable, slightly do not pay attention to even after the wrong circuit was lost, I can only wish you good luck, the Arduino connection way of all components and no, because I believe that everyone’s circuit design must be better than me! 👍

What you need to do in a joint investigation is,

  1. Run restful. Py to make the whole API service run;
  2. Run mp.py, so that the public number and the server through;
  3. After the Arduino is connected to the Raspberry PI via USB, the raspberry PI is powered on again.
  4. Send instructions on the public account to observe the status changes of components on the Arduino.

Results:


Github address: github.com/windstormey…