物聯(lián)網(wǎng)(五)---搭建自己的云平臺[ESP8266+Django]

物聯(lián)網(wǎng)(一)---快速上手[STM32+OneNET+ESP8266]
物聯(lián)網(wǎng)(二)---原理分析[STM32+OneNET+ESP8266]
物聯(lián)網(wǎng)(三)---WEB下發(fā)命令控制單片機(jī)[STM32+OneNET+ESP8266]
物聯(lián)網(wǎng)(四)---搭建自己的TCP服務(wù)器[ESP8266]
物聯(lián)網(wǎng)(五)---搭建自己的云平臺[ESP8266+Django]

下面的這些文章寫的都比我好很多,但:
希望你在點(diǎn)擊完下面的連接后,最終能回歸文章,繼續(xù)搭建自己的物聯(lián)網(wǎng)平臺。

本節(jié)所用到的程序源碼下載地址:https://github.com/keep1234quiet/IOT

使用到的工具:
  • Redis 數(shù)據(jù)庫
  • Channels-redis
  • 將ESP8266變成Arduino的官方庫

這篇文章來之不易,總共花費(fèi)了我整整6天的時間,其中5天摸索,1天寫文章,還望好好閱讀。

其中未使用STM32+ESP8266的方式是因?yàn)槲也恢涝撊绾位貞?yīng)服務(wù)器端Ping ESP8266Pong,這里應(yīng)該是要自己手動去實(shí)現(xiàn)WebSocketPing、Pong的,然后再發(fā)送給ESP8266的串口,后期需要的話再看下能自己實(shí)現(xiàn)不。

socket斷開的原因分析

在摸索過程中只發(fā)現(xiàn)了ESP8266Arduino 庫實(shí)現(xiàn)了Ping 、Pong,故采用之。

本文總共對以下幾個部分進(jìn)行講解:

  • 如何將使用Arduino 來開發(fā) ESP8266;
  • 使用python的 WEB 框架 Django 來開發(fā)自己的云平臺。

一、將使用Arduino 來開發(fā) ESP8266

1.1 ESP8266硬件連接

這里我是用的是ESP-12S,算是ESP8266的升級版本,還有一個名字叫做NodeMCU,當(dāng)然,使用普通的ESP8266也是一樣的,ESP8266的版本將不會對本節(jié)實(shí)驗(yàn)產(chǎn)生影響,任選一款即可。

ESP-12引腳定義
推薦接線方式

對于上圖的接線方式,左上角的復(fù)位開關(guān)建議加上,右下角的輕觸開關(guān)建議換成撥動開關(guān)。

我的實(shí)物圖
1.2 將使用開發(fā)Arduino的方式去開發(fā)ESP8266

這里有兩篇文章進(jìn)行了介紹,可以參考他的方式去給Arduino添加ESP8266的包管理器。

  • [如何安裝ESP8266的Arduino開發(fā)環(huán)境]
  • [arduino-esp8266.readthedocs.io->Boards Manager]

這里本來沒什么難度,但要注意的是:

  • 插入鏈接https://arduino-esp8266.readthedocs.io/en/latest/installing.html#boards-manager至Arduino IDE后,要重啟Arduino IDE才行;
  • 下載包的時候網(wǎng)絡(luò)問題,由于總所周知的原因這里不展開介紹;
  • 程序下載的時候需要選好參數(shù),否則會下載不成功,這里每一款模塊都是不一樣的,我的是ESP-12S的,僅供參考。
ESP8266 Arduino core 程序下載參數(shù)配置

當(dāng)上面的工作完成后,應(yīng)該就能夠用Arduino IDE給ESP8266下載程序了,這里自己下載程序驗(yàn)證一下,推薦使用串口程序驗(yàn)證,看看是否生效。

二、云平臺開發(fā)

2.1 為什么需要用WEB框架來進(jìn)行開發(fā)

你可能會問,為什么需要用WEB框架來進(jìn)行開發(fā),如果是小型項(xiàng)目,確實(shí)可以不用,但是當(dāng)你要做一個大項(xiàng)目,比如[OneNET云平臺]、[機(jī)智云]、[貝殼物聯(lián)] 這樣的稍大型平臺,甚至要做體量更大的平臺,沒有一個好的WEB框架,想要完成開發(fā)是十分困難的。

2.2 繼續(xù)閱讀下去需要什么基礎(chǔ)

如果看完了前面的預(yù)讀文章,這里應(yīng)該對Django的一些概念有一定的了解了,比如路由urls、模型models、視圖views這些概念都應(yīng)該要知道了。當(dāng)然,對其他類型框架了解的同學(xué)也可以使用其他類型的框架來代替Django。

2.3 怎么搭建云平臺

本文使用的云平臺是由Django-channels的聊天室例程改編而來的。原文在此:
[如何搭建一個簡單的網(wǎng)頁聊天室]

文章是原作者寫的,教程也寫的非常詳細(xì),每一步都有詳細(xì)說明。

整個過程可能需要安裝各種pythont的庫,這個Terminal會給出詳細(xì)的說明,可以自己根據(jù)錯誤提示去安裝需要的庫,如果要我提供的程序里已經(jīng)將需要用到的庫寫在了requirements.txt文件中了,直接使用pip3命令安裝即可;這里注意一下asgiref這個庫,需要升級到最新版本才行,否則會因使用了新版本里的模塊而導(dǎo)致無法從舊版本導(dǎo)入而報(bào)錯。

唯獨(dú)Part 2里的Enable a channel layer 這里使用Redis數(shù)據(jù)庫建立了一個通信管道,只告訴了要安裝channels_redis庫,并沒有說明如何安裝Redis數(shù)據(jù)庫,這里在前面的使用到的工具里提供了安裝方法【Redis 數(shù)據(jù)庫安裝方法:[Redis安裝]】,里面有詳細(xì)的安裝教程,也對Redis數(shù)據(jù)庫進(jìn)行了相應(yīng)的介紹與講解。

需要注意的是,Redis數(shù)據(jù)庫安裝完成后,若要服務(wù)器中要使用到管道通信,則必須保持Redis數(shù)據(jù)庫在后臺運(yùn)行,否則將無法完成多對多的通信。

下面的教程只需要完成前三個就好了,第四個自動化測試可以不用。

實(shí)現(xiàn)一個聊天室的例子

如果完成了上面channels教程里如何搭建一個聊天服務(wù)器,應(yīng)該能實(shí)現(xiàn)聊天功能了。

2.4 啟動服務(wù),開始測試

首先使用redis-server命令啟動數(shù)據(jù)庫服務(wù):

redis-server

然后再使用python3 manage.py runserver 0.0.0.0:8000命令開啟服務(wù)器,可使局域網(wǎng)內(nèi)都能訪問網(wǎng)站。

python3 manage.py runserver 0.0.0.0:8000

完成上面的Tutorial之后應(yīng)該是下面這種效果的,可以完成同通信功能。
1.先啟動redis數(shù)據(jù)庫:redis-server
2.啟動django自帶的服務(wù)器:python3 manage.py runserver 0.0.0.0:8000
3.查看自己的ipipconfig
4.訪問自己的聊天室:http://ip:8000/chat/ChatRoom/
5.發(fā)送消息,服務(wù)器對消息進(jìn)行廣播,各個客戶端都會收到消息

聊天室功能開啟與展示.gif
2.5 適當(dāng)修改ESP8266的WebSocketClient程序和服務(wù)端的程序

當(dāng)前面的工作做完之后,接下來的工作就簡單了,只要給ESP8266燒入好程序即可。ESP8266自帶的例程里面有一個WebSocketClient.ino的例程,只要對這個例程稍加修改即可,下面是我修改好的ESP8266的程序。

注:其中主要是要注意ESP8266發(fā)送數(shù)據(jù)的格式,由于服務(wù)端聊天室只對JSON的數(shù)據(jù)進(jìn)行了解析,所以ESP8266的發(fā)送的數(shù)據(jù)必須以 JSON的格式發(fā)送,否則會造成服務(wù)端崩潰。即應(yīng)以這種形式發(fā)送才能在網(wǎng)頁上正常顯示出來:
webSocket.sendTXT("{\"message\":\"heartbeat\"}");

ESP8266的程序較為簡短,我就直接貼了,程序閱讀沒有什么難點(diǎn),應(yīng)該不會看不懂,如果有缺少的庫就自己去裝一下就好了。

/*
 * WebSocketClient.ino
 *
 *  Created on: 24.05.2015
 *
 */

#include <Arduino.h>

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>

#include <WebSocketsClient.h>

#include <Hash.h>

ESP8266WiFiMulti WiFiMulti;
WebSocketsClient webSocket;

#define USE_SERIAL Serial

bool isConnected = false;


void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {

    switch(type) {
        case WStype_DISCONNECTED:
            USE_SERIAL.printf("[WSc] Disconnected!\n");
            isConnected = false;
            break;
        case WStype_CONNECTED: {
            USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload);
            isConnected = true;

            // send message to server when Connected
//          webSocket.sendTXT("Connected");//發(fā)送Conneted 導(dǎo)致服務(wù)器拋出 Expecting value: line 1 column 1 (char 0) 異常而斷開連接。//先注釋掉試試
//原因是服務(wù)端只對JSON數(shù)據(jù)進(jìn)行解析,"Connected"是字符串而不是JSON數(shù)據(jù)。
            }
            break;
        case WStype_TEXT:
             USE_SERIAL.printf("[WSc] get text: %s\n", payload);
         // send message to server
       //webSocket.sendTXT(rcvMsg);
             break;
        case WStype_BIN:
            USE_SERIAL.printf("[WSc] get binary length: %u\n", length);
            hexdump(payload, length);

            // send data to server
            // webSocket.sendBIN(payload, length);
            break;
    }

}

void setup() {
    // USE_SERIAL.begin(921600);
    USE_SERIAL.begin(115200);

    //Serial.setDebugOutput(true);
    USE_SERIAL.setDebugOutput(true);

    USE_SERIAL.println();
    USE_SERIAL.println();
    USE_SERIAL.println();

    for(uint8_t t = 4; t > 0; t--) {
        USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
        USE_SERIAL.flush();
        delay(1000);
    }

    WiFiMulti.addAP("YOUR-WIFIF-NAME", "YOUR-PASSWORD");

    //WiFi.disconnect();
    while(WiFiMulti.run() != WL_CONNECTED) {
        delay(100);
    }

    // server address, port and URL
    webSocket.begin("192.168.43.102", 8000, "/ws/chat/M/");

    // event handler
    webSocket.onEvent(webSocketEvent);

    // use HTTP Basic Authorization this is optional remove if not needed
////webSocket.setAuthorization("user", "Password");

    // try ever 5000 again if connection has failed
    webSocket.setReconnectInterval(5000);
  
  // start heartbeat (optional)
  // ping server every 15000 ms
  // expect pong from server within 3000 ms
  // consider connection disconnected if pong is not received 2 times
  webSocket.enableHeartbeat(15000, 3000, 2); 
  //這個時間很準(zhǔn),可以從wireshark里面查看,ESP8266每15秒鐘Ping一次服務(wù)器,期間會收到服務(wù)器的Ping,ESP8266也會Pong回去

}

int count = 0;
String inputString = "";
boolean stringComplete = false;
char sendStr[100];

void loop() {
  webSocket.loop();
  serialEvent();  //發(fā)現(xiàn)ESP8266沒有serialEvent()事件,故在此對該函數(shù)進(jìn)行調(diào)用
  count++;
  if(count == 60*100){
      count = 0;
      if(isConnected){                     //這里并不是心跳,心跳需要抓包才能看到
          webSocket.sendTXT("{\"message\":\"heartbeat\"}"); 
      }
  }
  if (stringComplete) {
    String s1 = "{\"message\":\"";
    String s2 = "\"}";
    webSocket.sendTXT(s1+inputString+s2);
    // clear the string:
    inputString = "";
    stringComplete = false;
    memset(sendStr,0,100);
  }
  delay(10);
}


void serialEvent() {
  while (Serial.available() && !stringComplete) {
    // get the new byte:
    char inChar = (char)Serial.read();
    
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (inChar == '\n') {
      stringComplete = true;
      return;
    }

    // add it to the inputString:
    inputString += inChar;
  }
}

將上面的程序下載至ESP8266中,從燒錄模式切換為正常運(yùn)行模式,復(fù)位或重新上電。若服務(wù)器沒有關(guān)閉,會從服務(wù)器的Debug窗口看到有新的客戶端連接上了。

當(dāng)連接成功之后,就可以讓ESP8266和服務(wù)器的其他客戶端加入同一個聊天室進(jìn)行聊天了,這里必須是同一個聊天室,不然無法收到信息,聊天室是由/chat/后的字符決定的。

這里我對Web 端的服務(wù)器的consumer.py 文件中的ChatConsumer類里的receive稍加修改,以避免服務(wù)器收到非JSON消息而崩潰,修改如下:

    # Receive message from WebSocket
    def receive(self, text_data):
        print('====' * 10)
        print('rawMsg:'+text_data)
        print('====' * 10)

        try:
            text_data_json = json.loads(text_data)
            message = text_data_json['message']
        except:
            message = text_data
            pass

        # Send message to room group
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message
            }
        )
2.6 讓ESP8266作為客戶端,發(fā)送或接收信息

電腦開啟熱點(diǎn),讓手機(jī)和服務(wù)器處于同一局域網(wǎng)內(nèi),如果沒問題的話應(yīng)該能看到如下現(xiàn)象:

  1. 通過串口助手向ESP8266發(fā)送數(shù)據(jù),以\n結(jié)尾,不要以\r\n結(jié)尾,程序里沒作相應(yīng)的處理;
  2. ESP8266 將從串口接收到的數(shù)據(jù)進(jìn)行解析,封裝成JSON格式的數(shù)據(jù),在通過調(diào)用函數(shù)webSocket.sendTXT(your JSON message)將數(shù)據(jù)發(fā)送至服務(wù)器;
  3. 服務(wù)器收到ESP8266發(fā)來的JSON格式數(shù)據(jù)后,對其進(jìn)行解析,并廣播出去,其中也包括客戶端ESP8266。
  4. 從網(wǎng)頁端發(fā)送數(shù)據(jù),服務(wù)器收到后,也同樣會廣播出去,ESP8266作為客戶端也會收到被廣播出來的數(shù)據(jù),這樣就完成了一次數(shù)據(jù)收發(fā)的過程了。
ESP8266與服務(wù)端通信過程.gif

到這里就完成開發(fā)了一個自己的云平臺了,雖讓功能比較簡陋,但是整個流程已經(jīng)走通了。相比于上一節(jié)所講的,這一節(jié)似乎并沒有太多的變化,但不同的是,我們這一節(jié)引入了WEB框架 Django,有了WEB框架,我們開發(fā)大型項(xiàng)目就方便快速多了,也非常有利于項(xiàng)目管理。

2.7 整體框架梳理
整個通信過程的整體框架

總結(jié):本文內(nèi)容篇幅不算長,但是大量引用了相關(guān)鏈接,對別人已經(jīng)做得比較好的部分沒有進(jìn)行重復(fù)講解,而是給出鏈接,希望讀者能前去學(xué)習(xí)完畢后再來繼續(xù)學(xué)習(xí)節(jié)教程,其中給出了Djangochannels、WebSocket、ESP8266 Arduino core等均給出原作者原文鏈接及官網(wǎng),也都是很好的教程,非常詳細(xì)。

本文只是走通了【搭建自己的云平臺并使用ESP8266成功連接云平臺】這條道路,仍然有諸多問題尚未解決,比如:

  1. 服務(wù)端不應(yīng)該給所有的客戶端進(jìn)行消息廣播,而應(yīng)該是有選擇性的;
  2. 這里是用了ESP8266 Arduino core的庫,利用的是其內(nèi)置的WebSocket 協(xié)議實(shí)現(xiàn),如果加上單片機(jī)呢,換一種連接方式又該怎么實(shí)現(xiàn)呢?
  3. 這里的所有消息都是明文傳輸,未進(jìn)行加解密,是十分不安全的;
  4. 瀏覽器端這么丑,是不是應(yīng)該美化一下呢,功能是不是應(yīng)該繼續(xù)豐富呢?
  5. ...

雖然已經(jīng)做了蠻多的,但是接下來要做的一點(diǎn)也比現(xiàn)在已經(jīng)做的事要少,后續(xù)就留給讀者自己去探尋吧,我此后我會間斷的更新此系列的文章,以盡量使整個過程完整而詳盡。

本系列的物聯(lián)網(wǎng)文章就暫時告一段落了吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容