Monday, January 22, 2018

Demo 40: Create a Facebook Messenger chat bot for monitoring and controlling home devices using Raspberry/Orange Pi and ESP32/8266

1. Introduction
In this demo, I will show you how to create a Facebook Messenger chat bot for monitoring and controlling home devices using Raspberry/Orange Pi and ESP32/8266. This is not a full solution so you need to improve it by yourself.
Figure: Facebook Messenger chat bot for monitoring and controlling home devices
There are 2 steps to make this demo:
- How to setup a Facebook chat bot
- How to setup local system (Raspberry/Orange Pi, ESP32/8266)
This is the model of the demo:
Figure: The model of the demo
2. Setup
2.1 Create Facebook page
You can refer to this guideline. And choose type of Page is Business, Brand or Place. In my demo, I created a page named IoT Sharing.
2.2 Setup Facebook chat bot
In order to develop or use Facebook services for Software development you need to register a Facebook developer account here.
After created a FB developer account choose My Apps -> Add a new App -> Fill Display name
In Select a Product -> choose messenger -> Set Up
Choose Webhooks -> Setup Webhooks
Note: We do not do this step now. This step will be done after running the Raspbbery/Orange Pi software. Because after filled Callback URL, Verify Token and press Verify and Save, FB will send a GET request with "hub.challenge=value of Verify Token" (in this case Verify Token is "iotsharing.com" string) to Pi. Pi need to check the value of hub.challenge must match the value that we filled in Verify Token field (in this case Verify Token is "iotsharing.com" string). And then send this hub.challenge back to FB to finish the verification.
1
2
3
4
5
6
@app.route('/', methods=['GET'])
def handle_verification():
 if(request.args['hub.challenge'] == 'iotsharing.com'):
  return request.args['hub.challenge']
 else:
  return 'not match'
Fill information like below and choose Verify and Save.
choose the Page that you created in previous step and Subscribe/Unsubscribe.
Finally, extract the ACCESS_TOKEN of the FB app so that our application can authenticate to use FB services.
It is done for Facebook setup step.
2.2 Setup local system (Raspberry/Orange Pi, ESP32/8266)
In this demo, Pi will keep some roles:
- Communicate with Facebook server to receive and respond message.
- Parse Facebook message and then using MQTT protocol to publish the commands to ESP32/8266 clients and subscribe responses from ESP32/8266 clients.
- Refer to this post to setup MQTT for Pi.
- We use Python Paho MQTT for local communication. Refer to this post to install it.
- Besides we need to install some packages (Flask, ngrok) on Raspberry/Orange Pi for our demo. 
+ Flask is a Python web framework. In our demo, It is a web server to handle Facebook https request.
+ ngrok secure introspect-able tunnels to local host web hook development tool and debugging tool. This tool helps Facebook server can see our Flask local web server.
From Pi Terminal running the commands:
+ Install Flask Python server: "sudo pip install Flask"
+ Download ngrok: "wget https://bin.equinox.io/a/26q6mq7ddJR/ngrok-2.2.9-linux-arm.zip"
+ Unzip zip file for executable app: "unzip ngrok-2.2.9-linux-arm.zip"
3. Hardware
- 1 ESP32 or ESP8266
- 1 Raspberry Pi or Orange Pi
- 1 LED connect to ESP
- 1 temperature sensor connect to ESP. 
In this demo I do not use temperature sensor. I used random(min, max) function to generate temperature.
4. Software
The source code of this demo can be found on github.
Define messages:
- Pi send MQTT request topics to ESP:
Temperature topic: "floor1/room1/temp1" to measure temperature
Led topic:               "floor1/room1/led1" to set "0" (off) or "1" (on)
- ESP send MQTT response topics to Pi:
Temperature response topic: "floor1/room1/temp1/res" with temperature value
Led response topic:               "floor1/room1/led1/res" to inform that the request was executed
- The template for Facebook message:
"set floor1/room1/led1 on" -> turn on LED and get response
"set floor1/room1/led1 off" -> turn off LED and get response
"get floor1/room1/temp1" -> request temperature measurement and get response
ESP code I reused the post (esp32chatbot.ino)
  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
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
#include <WiFi.h>
#include <PubSubClient.h>

const char* ssid = "dd-wrt";
const char* password = "0000000000";
//ip address of Raspberry/orange pi
const char* mqtt_server = "192.168.1.106";
char msg[20];

/* create an instance of PubSubClient client */
WiFiClient espClient;
PubSubClient client(espClient);

/*LED GPIO pin*/
const char led = 4;
/* topics */
#define TEMP_TOPIC    "floor1/room1/temp1"
#define LED_TOPIC     "floor1/room1/led1" /* on, off */

void receivedCallback(char* topic, byte* payload, unsigned int length) {
    Serial.print("topic: ");
    Serial.println(topic);
      if(strcmp(topic, LED_TOPIC) == 0){
        Serial.print("payload: ");
        for (int i = 0; i < length; i++) {
          Serial.print((char)payload[i]);
        }
        Serial.println();
        /* we got '1' -> on */
        if ((char)payload[0] == '1') {
          digitalWrite(led, HIGH); 
          snprintf (msg, 20, "%s", "on");
        /* publish the response */
        client.publish(LED_TOPIC "/res", msg);
        } else {
          /* we got '0' -> on */
          digitalWrite(led, LOW);
          snprintf (msg, 20, "%s", "off");
          client.publish(LED_TOPIC "/res", msg);
        }
    }else {
        snprintf (msg, 20, "%d", random(0, 40));
        client.publish(TEMP_TOPIC "/res", msg);
    }
}

void mqttconnect() {
  /* Loop until reconnected */
  while (!client.connected()) {
    Serial.print("MQTT connecting ...");
    /* client ID */
    String clientId = "ESP32Client";
    /* connect now */
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      /* subscribe topic */
      client.subscribe(LED_TOPIC);
      client.subscribe(TEMP_TOPIC);
    } else {
      Serial.print("failed, status code =");
      Serial.print(client.state());
      Serial.println("try again in 5 seconds");
      /* Wait 5 seconds before retrying */
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  /* set led as output to control led on-off */
  pinMode(led, OUTPUT);

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  /* configure the MQTT server with IPaddress and port */
  client.setServer(mqtt_server, 1883);
  /* this receivedCallback function will be invoked 
  when client received subscribed topic */
  client.setCallback(receivedCallback);
}
void loop() {
  /* if client was disconnected then try to reconnect again */
  if (!client.connected()) {
    mqttconnect();
  }
  /* this function will listen for incomming 
  subscribed topic-process-invoke receivedCallback */
  client.loop();
}
Pi code (chatbot.py):
We use variable history to hold the id of Facebook messenger sender while waiting for the response from ESP. This sender id is used in reply() function to send response back to Facebook messenger sender.
 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
from flask import Flask, request
import requests
import paho.mqtt.publish as publish
import paho.mqtt.client as mqtt
import thread

ACCESS_TOKEN = "your_fb_access token_here";

#hold sender id
history = dict()

set_cmd = 'floor1/room1/led1'
get_cmd = 'floor1/room1/temp1'

#respond to FB messenger
def reply(user_id, msg):
    data = {
        "recipient": {"id": user_id},
        "message": {"text": msg}
    }
    resp = requests.post("https://graph.facebook.com/v2.6/me/messages?access_token=" + ACCESS_TOKEN, json=data)
    print(resp.content)

#MQTT handler
def on_connect(mqttc, obj, flags, rc):
    print("rc: "+str(rc))
def on_message(mqttc, obj, msg):
    print(msg.topic+": "+str(msg.payload))
    if msg.topic in history:
        user_id = history[msg.topic]
  #reply FB messenger
        reply(user_id, msg.payload)
        history.pop(msg.topic, None)

mqttc = mqtt.Client()
mqttc.on_connect = on_connect
mqttc.on_message = on_message
mqttc.connect("localhost", 1883, 60)
mqttc.subscribe("floor1/#", 0)

#MQTT subscribe thread 
def mqtt_thread( threadName, delay):
   mqttc.loop_forever()
try:
   thread.start_new_thread( mqtt_thread, ("mqtt-thread", 0, ) )
except:
   print "Error: unable to start thread"

#Flask web server instance
app = Flask(__name__)

#handle GET request from Facebook
@app.route('/', methods=['GET'])
def handle_verification():
 if(request.args['hub.challenge'] == 'iotsharing.com'):
  return request.args['hub.challenge']
 else:
  return 'not matched'

#handle POST request from Facebook
@app.route('/', methods=['POST'])
def handle_incoming_messages():
    data = request.json
    print(data)
    sender = data['entry'][0]['messaging'][0]['sender']['id']
    message = data['entry'][0]['messaging'][0]['message']['text']
    print(message)
    if(message.startswith('set')):
        arr = message.split()
        l = len(arr)
        if(l == 3 and set_cmd == arr[1]):
            cmd = arr[1]
            val = '1' if arr[2]=='on' else '0'
            publish.single(cmd, val, hostname="localhost")
   #record command with sender id
            history[cmd+'/res'] = sender
            return 'ok'
    elif(message.startswith('get')):
        arr = message.split()
        l = len(arr)
        if(l == 2 and get_cmd == arr[1]):
            cmd = arr[1]
            publish.single(cmd, '', hostname="localhost")
   #record command with sender id
            history[cmd+'/res'] = sender
            return 'ok'
    reply(sender, 'invalid query')
    return "ok"

if __name__ == '__main__':
app.run(debug=True)
5. Steps to deploy
- MQTT broker run on Pi
- Open 2 Terminal on Pi:
   + Run "python chatbot.py" with Flask in it.
   + Run ngrok (5000 is the port Flask listen on): "./ngrok http 5000"

Copy the url in red box to Callback URL field that mention in step 2.2 Setup Facebook chat bot
Note: in case we shutdown ngrok and run it again the new url will be generated. We have to re-register this url to Facebook.
Choose Webhooks -> Edit Subscription
And then Unsubcribe and Subscribe the page again:
Now go to the Page that we created, choose About and Send Message
And type one of commands:
"set floor1/room1/led1 on"
"set floor1/room1/led1 off"
"get floor1/room1/temp1"

6. Result



0 comments: