<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      前端程序員是怎么做物聯網開發的

      前端程序員是怎么做物聯網開發的

      image-20230104162825029

      上圖是我歷時一周做的在線的溫濕度可視化項目,可以查看截至目前往前一天的溫度、濕度變化趨勢,并且實時更新當前溫濕度

      本文可能含有知識詛咒

      概述和基礎講解

      該項目用到的技術有:

      • 前端:jq、less、echarts、mqtt.js
      • 后端:eggjs、egg-emqtt
      • 數據庫:mysql
      • 服務器:emqx(mqtt broker)
      • 硬件:
        • 板子:wemos D1 R2(設計基于 Arduino Uno R3 , 搭載esp8266 wifi模塊)
      • 調試工具:mqttx、Arduino IDE v2.0.3 使用Arduino C開發

      必備知識:

      • nodejs(eggjs框架)能面向業務即可
      • mysql 能寫基本插入查詢語句即可
      • C語言的基本語法了解即可
      • 知道mqtt協議的運作方式即可
      • arduino 開發板或任何其他電路板的初步了解即可

      簡單介紹一下上面幾個的知識點:

      1. 從來沒有后端學習經驗的同學,推薦一個全棧項目供你參考:vue-xmw-admin-pro ,該項目用到了 前端VUE、后端eggjs、mysql、redis,對全棧學習很有幫助。

      2. mysql 只需要知道最簡單的插入和查詢語句即可,在本項目中,其實使用mongodb是更合適的,但是我為了方便,直接用了現成的mysql

      3. 即使你不知道C語言的基本語法,也可以在一小時內快速了解一下,知道簡單的定義變量、函數、返回值即可

      4. MQTT(消息隊列遙測傳輸)是一種網絡協議(長連接,意思就是除了客戶端可以主動向服務器通信外,服務器也可以主動向客戶端發起),也是基于TCP/IP的,適用于算力低下的硬件設備使用,基于發布\訂閱范式的消息協議,具體示例如下:

        image-20230104170333941

        當某客戶端想發布消息時,圖大概長這樣:

        image-20230104171235368

        由上圖可知,當客戶端通過驗證上線后,還需要訂閱主題,當某客戶端向某主題發布消息時,只有訂閱了該主題的客戶端會收到broker的轉發。

        舉一個簡單的例子:你和我,還有他,我們把自己的名字、學號報告給門衛大爺(broker),門衛大爺就同意我們在警衛室玩一會,警衛室有無數塊黑板(topic),我們每個人都可以向門衛請求:如果某黑板上被人寫了字,請轉告給我。門衛會記住每個人的要求,比如當你向一塊黑板寫了字(你向某topic發送了消息),所有要求門衛告訴的人都會被門衛告知你寫了什么(如果你也要求被告知,那么也包括你自己)。

      5. 開發板可以被寫入程序,程序可以使用簡單的代碼控制某個針腳的高低電平,或者讀取某針腳的數據。

      開始

      1. 購買 wemos d1開發板、DHT11溫濕度傳感器,共計19.3元。
      2. 使用arduino ide(以下簡稱ide) 對wemos d1編程需要下載esp8266依賴 參見:Arduino IDE安裝esp8266 SDK
      3. 在ide的菜單欄選擇:文件>首選項>其他開發板管理器地址填入:http://arduino.esp8266.com/stable/package_esp8266com_index.json,可以順便改個中文
      4. 安裝ch340驅動參見: win10 安裝 CH340驅動 實測win11同樣可用
      5. 使用 micro-usb 線,連接電腦和開發板,在ide菜單中選擇:工具>開發板>esp8266>LOLIN(WEMOS) D1 R2 & mini
      6. 選擇端口,按win+x,打開設備管理器,查看你的ch340在哪個端口,在ide中選擇對應的端口
      7. 當ide右下角顯示LOLIN(WEMOS) D1 R2 & mini 在comXX上時,連接就成功了
      8. 打開ide菜單欄 :文件>示例>esp8266>blink,此時ide會打開新窗口,在新窗口點擊左上角的上傳按鈕,等待上傳完成,當板子上的燈一閃一閃,就表明:環境、設置、板子都沒問題,可以開始編程了,如果報錯,那么一定是哪一步出問題了,我相信你能夠根據錯誤提示找出到底是什么問題,如果實在找不出問題,那么可能買到了壞的板子(故障率還是蠻高的)

      wemos d1 針腳中有一個 3.3v電源輸出,三個或更多的GND接地口,當安裝DHT11傳感器元件時,需要將正極插入3.3v口,負極插入GND口,中間的數據線插入隨便的數字輸入口,比如D5口(D5口的PIN值是14,后面會用到)。

      使用DHT11傳感器,需要安裝庫:DHT sensor library by Adafruit , 在ide的左側欄中的庫管理中直接搜索安裝即可

      下面是一個獲取DHT11數據的簡單示例,如果正常的話,在串口監視器中,會每秒輸出溫濕度數據

      #include "DHT.h"  //這是依賴或者叫庫,或者叫驅動也行
      #include "string.h"
      #define DHTPIN 14      // DHT11數據引腳連接到D5引腳 D5引腳的PIN值是14
      #define DHTTYPE DHT11  // 定義DHT11傳感器
      DHT dht(DHTPIN, DHTTYPE);  //初始化傳感器
      
      void setup() {
        Serial.begin(115200);
        //wemos d1 的波特率是 115200
        pinMode(BUILTIN_LED, OUTPUT); //設置一個輸出的LED
        dht.begin();  //啟動傳感器
      }
      
      char* getDHT11Data() {
        float h = dht.readHumidity();  //獲取濕度值
        float t = dht.readTemperature(); //獲取溫度值
        static char data[100];
        if (isnan(h) || isnan(t)) {
          Serial.println("Failed to read from DHT sensor!");
          sprintf(data, "Temperature: %.1f, Humidity: %.1f", 0.0, 0.0); //如果任何一個值沒有值,直接返回兩個0.0,這樣我們就知道傳感器可能出問題了
          return data;
        }
        sprintf(data, "Temperature: %.1f, Humidity: %.1f", t, h); //正常就取到值,我這里拼成了一句話
        return data;
      }
      
      void loop() {
        char* data = getDHT11Data(); //此處去取傳感器值
        Serial.println("got: " + String(data));  // 打印主題內容
        delay(1000); //每次循環延遲一秒
      }
      

      繼續

      到了這一步,如果你用的是普通的arduino uno r3板子,就可以結束了。

      取到數據之后,你就可以根據數據做一些其他的事情了,比如打開接在d6引腳上的繼電器,而這個繼電器控制著一個加濕器。

      如果你跟我一樣,使用了帶wifi網絡的板子,就可以繼續跟我做。

      我們繼續分步操作:

      設備端:

      1. 引入esp8266庫(上面已經提到安裝過程)

        1. #include "ESP8266WiFi.h"
          
      2. 安裝mqtt客戶端庫 ,直接在庫商店搜索 PubSubClient ,下載 PubSubClient by Nick O'Leary 那一項,下載完成后:

        1. #include "PubSubClient.h"
          
      3. 至此,庫文件已全部安裝引入完畢

      4. 設置 wifi ssid(即名字) 和 密碼,如:

        1. char* ssid = "2104";
          char* passwd = "13912428897";
          
      5. 嘗試連接 wifi

        1. WiFiClient espClient;
          int isConnect = 0;
          void connectWIFI() {
            isConnect = 0; 
            WiFi.mode(WIFI_STA);  //不知道什么意思,照著寫就完了
            WiFi.begin(ssid, passwd); //嘗試連接
            int timeCount = 0;  //嘗試次數
            while (WiFi.status() != WL_CONNECTED) { //如果沒有連上,繼續循環
              for (int i = 200; i <= 255; i++) {
                analogWrite(BUILTIN_LED, i);
                delay(2);
              }
              for (int i = 255; i >= 200; i--) {
                analogWrite(BUILTIN_LED, i);
                delay(2);
              }
              // 上兩個循環共計200ms左右,在控制LED閃爍而已,你也可以不寫
              Serial.println("wifi connecting......" + String(timeCount));
              timeCount++;
              isConnect = 1; //每次都需要把連接狀態碼設置一下,只有連不上時設置為0
              // digitalWrite(BUILTIN_LED, LOW);
              if (timeCount >= 200) {
                // 當40000毫秒時還沒連上,就不連了
                isConnect = 0; //設置狀態碼為 0
                break;
              }
            }
            if (isConnect == 1) {
              Serial.println("Connect to wifi successfully!" + String("SSID is ") + WiFi.SSID());
              Serial.println(String("mac address is ") + WiFi.macAddress());
              // digitalWrite(BUILTIN_LED, LOW);
              analogWrite(BUILTIN_LED, 250); //設置LED常亮,250的亮度對我來說已經很合適了
              settMqttConfig();  //嘗試連接mqtt服務器,在下一步有詳細代碼
            } else {
              analogWrite(BUILTIN_LED, 255); //設置LED常滅,不要問我為什么255是常滅,因為我的燈是高電平熄滅的
              //連接wifi失敗,等待一分鐘重連
              delay(60000);
            }
          }
          
      6. 嘗試連接 mqtt

        1. const char* mqtt_server = "larryblog.top";  //這里是我的服務器,當你看到這篇文章的時候,很可能已經沒了,因為我的服務器還剩11天到期
          const char* TOPIC = "testtopic";            // 設置信息主題
          const char* client_id = "mqttx_3b2687d2";   //client_id不可重復,可以隨便取,相當于你的網名
          PubSubClient client(espClient);
          void settMqttConfig() {
            client.setServer(mqtt_server, 1883);  //設定MQTT服務器與使用的端口,1883是默認的MQTT端口
            client.setCallback(onMessage);  //設置收信函數,當訂閱的主題有消息進來時,會進這個函數
            Serial.println("try connect mqtt broker");
            client.connect(client_id, "wemos", "aa995231030");  //后兩個參數是用戶名密碼
            client.subscribe(TOPIC); //訂閱主題
            Serial.println("mqtt connected");  //一切正常的話,就連上了
          }
          //收信函數
          void onMessage(char* topic, byte* payload, unsigned int length) {
            Serial.print("Message arrived [");
            Serial.print(topic);  // 打印主題信息
            Serial.print("]:");
            char* payloadStr = (char*)malloc(length + 1);
            memcpy(payloadStr, payload, length);
            payloadStr[length] = '\0';
            Serial.println(payloadStr);  // 打印主題內容
            if (strcmp(payloadStr, (char*)"getDHTData") == 0) {
              char* data = getDHT11Data();
              Serial.println("got: " + String(data));  // 打印主題內容
              client.publish("wemos/dht11", data);
            }
            free(payloadStr);  // 釋放內存
          }
          
      7. 發送消息

        1. client.publish("home/status/", "{device:client_id,'status':'on'}");
          //注意,這里向另外一個主題發送的消息,消息內容就是設備在線,當有其他的客戶端(比如web端)訂閱了此主題,便能收到此消息
          

      至此,板子上的代碼基本上就寫完了,完整代碼如下:

      #include "ESP8266WiFi.h"
      #include "PubSubClient.h"
      #include "DHT.h"
      #include "string.h"
      #define DHTPIN 14      // DHT11數據引腳連接到D5引腳
      #define DHTTYPE DHT11  // DHT11傳感器
      DHT dht(DHTPIN, DHTTYPE);
      
      char* ssid = "2104";
      char* passwd = "13912428897";
      const char* mqtt_server = "larryblog.top";
      const char* TOPIC = "testtopic";            // 訂閱信息主題
      const char* client_id = "mqttx_3b2687d2";
      int isConnect = 0;
      WiFiClient espClient;
      PubSubClient client(espClient);
      long lastMsg = 0;
      void setup() {
        Serial.begin(115200);
        // Set WiFi to station mode
        connectWIFI();
        pinMode(BUILTIN_LED, OUTPUT);
        dht.begin();
      }
      char* getDHT11Data() {
        float h = dht.readHumidity();
        float t = dht.readTemperature();
        static char data[100];
        if (isnan(h) || isnan(t)) {
          Serial.println("Failed to read from DHT sensor!");
          sprintf(data, "Temperature: %.1f, Humidity: %.1f", 0.0, 0.0);
          return data;
        }
        sprintf(data, "Temperature: %.1f, Humidity: %.1f", t, h);
        return data;
      }
      void connectWIFI() {
        isConnect = 0;
        WiFi.mode(WIFI_STA);
        WiFi.begin(ssid, passwd);
        int timeCount = 0;
        while (WiFi.status() != WL_CONNECTED) {
          for (int i = 200; i <= 255; i++) {
            analogWrite(BUILTIN_LED, i);
            delay(2);
          }
          for (int i = 255; i >= 200; i--) {
            analogWrite(BUILTIN_LED, i);
            delay(2);
          }
          // 上兩個循環共計200ms左右
          Serial.println("wifi connecting......" + String(timeCount));
          timeCount++;
          isConnect = 1;
          // digitalWrite(BUILTIN_LED, LOW);
          if (timeCount >= 200) {
            // 當40000毫秒時還沒連上,就不連了
            isConnect = 0;
            break;
          }
        }
        if (isConnect == 1) {
          Serial.println("Connect to wifi successfully!" + String("SSID is ") + WiFi.SSID());
          Serial.println(String("mac address is ") + WiFi.macAddress());
          // digitalWrite(BUILTIN_LED, LOW);
          analogWrite(BUILTIN_LED, 250);
          settMqttConfig();
        } else {
          analogWrite(BUILTIN_LED, 255);
          //連接wifi失敗,等待一分鐘重連
          delay(60000);
        }
      }
      void settMqttConfig() {
        client.setServer(mqtt_server, 1883);  //設定MQTT服務器與使用的端口,1883是默認的MQTT端口
        client.setCallback(onMessage);
        Serial.println("try connect mqtt broker");
        client.connect(client_id, "wemos", "aa995231030");
        client.subscribe(TOPIC);
        Serial.println("mqtt connected");
      }
      void onMessage(char* topic, byte* payload, unsigned int length) {
        Serial.print("Message arrived [");
        Serial.print(topic);  // 打印主題信息
        Serial.print("]:");
        char* payloadStr = (char*)malloc(length + 1);
        memcpy(payloadStr, payload, length);
        payloadStr[length] = '\0';
        Serial.println(payloadStr);  // 打印主題內容
        if (strcmp(payloadStr, (char*)"getDHTData") == 0) {
          char* data = getDHT11Data();
          Serial.println("got: " + String(data));  // 打印主題內容
          client.publish("wemos/dht11", data);
        }
        free(payloadStr);  // 釋放內存
      }
      void publishDhtData() {
        char* data = getDHT11Data();
        Serial.println("got: " + String(data));  // 打印主題內容
        client.publish("wemos/dht11", data);
        delay(2000);
      }
      void reconnect() {
        Serial.print("Attempting MQTT connection...");
        // Attempt to connect
        if (client.connect(client_id, "wemos", "aa995231030")) {
          Serial.println("reconnected successfully");
          // 連接成功時訂閱主題
          client.subscribe(TOPIC);
        } else {
          Serial.print("failed, rc=");
          Serial.print(client.state());
          Serial.println(" try again in 5 seconds");
          // Wait 5 seconds before retrying
          delay(5000);
        }
      }
      void loop() {
        if (!client.connected() && isConnect == 1) {
          reconnect();
        }
        if (WiFi.status() != WL_CONNECTED) {
          connectWIFI();
        }
        client.loop();
        publishDhtData();
        long now = millis();
        if (now - lastMsg > 2000) {
          lastMsg = now;
          client.publish("home/status/", "{device:client_id,'status':'on'}");
        }
        // Wait a bit before scanning again
        delay(1000);
      }
      

      服務器

      剛才的一同操作很可能讓人一頭霧水,相信大家對上面mqtt的操作還是一知半解的,不過沒有關系,通過對服務端的設置,你會對mqtt的機制了解的更加透徹

      我們需要在服務端部署 mqtt broker,也就是mqtt的消息中心服務器

      在網絡上搜索 emqx , 點擊 EMQX: 大規模分布式物聯網 MQTT 消息服務器 ,這是一個帶有可視化界面的軟件,而且畫面特別精美,操作特別絲滑,功能相當強大,使用起來基本上沒有心智負擔。點擊立即下載,并選擇適合你的服務器系統的版本:

      image-20230223102450653

      這里拿 ubuntu和windows說明舉例,相信其他系統也都大差不差

      在ubuntu上,推薦使用apt下載,按上圖步驟操作即可,如中途遇到其他問題,請自行解決

      1. sudo ufw status 查看開放端口,一般情況下,你只會看到幾個你手動開放過的端口,或者只有80、443端口
      2. udo ufw allow 18083 此端口是 emqx dashboard 使用的端口,開啟此端口后,可以在外網訪問 emqx看板控制臺

      image-20230223103352676 當你看到如圖所示的畫面,說明已經開啟成功了

      windows下直接下載安裝包,上傳到服務器,雙擊安裝即可

      1. 打開 “高級安全Windows Defender 防火墻”,點擊入站規則>新建規則
      2. 點擊端口 > 下一步
      3. 點擊TCP、特定本地端口 、輸入18083,點擊下一步
      4. 一直下一步到最后一步,輸入名稱,推薦輸入 emqx 即可

      image-20230223103810837

      當你看到如圖所示畫面,說明你已經配置成功了。

      完成服務端程序安裝和防火墻端口配置后,我們需要配置服務器后臺的安全策略,這里拿阿里云舉例:

      如果你是 ESC 云主機,點擊實例>點擊你的服務器名>安全組>配置規則>手動添加

      添加這么一條即可:

      image-20230223104139442

      如果你是輕量服務器,點擊安全>防火墻>添加規則 即可,跟esc設置大差不差。

      完成后,可以在本地瀏覽器嘗試訪問你的emqx控制臺

      image-20230223104408482

      直接輸入域名:18083即可,初始用戶名為admin,初始密碼為public,登錄完成后,你便會看到如下畫面

      image-20230223104559151

      接下來需要配置 客戶端登錄名和密碼,比如剛剛在設備中寫的用戶名密碼,就是在這個系統中設置的

      點擊 訪問控制>認證 > 創建,然后無腦下一步即可,完成后你會看到如下畫面

      image-20230223104906488

      點擊用戶管理,添加用戶即可,用戶名和密碼都是自定義的,這些用戶名密碼可以分配給設備端、客戶端、服務端、測試端使用,可以參考我的配置

      image-20230223105013597

      userClient是準備給前端頁面用的 ,server是給后端用的,995231030是我個人自留的超級用戶,wemos是設備用的,即上面設備連接時輸入的用戶名密碼。

      至此,emqx 控制臺配置完成。

      下載 mqttx,作為測試端嘗試連接一下

      image-20230223105505838

      點擊連接,你會發現,根本連接不上......

      因為,1883(mqtt默認端口)也是沒有開啟的,當然,和開啟18083的方法一樣。

      同時,還建議你開啟:

      • 1803 websocket 默認端口
      • 1804 websockets 默認端口
      • 3306 mysql默認端口

      后面這四個端口都會用到。

      當你開啟完成后,再次嘗試使用mqttx連接broker,會發現可以連接了

      image-20230223105957929

      這個頁面的功能也是很易懂的,我們在左側添加訂閱,右側的聊天框里會出現該topic的消息

      image-20230223110105586

      你是否還記得,在上面的設備代碼中,我們在loop中每一秒向 home/status/ 發送一條設備在線的提示,我們現在在這里就收到了。

      當你看到這些消息的時候,就說明,你的設備、服務器、emqx控制臺已經跑通了。

      前后端以及數據庫

      前端

      前端不必多說,我們使用echarts承載展示數據,由于體量較小,我們不使用任何框架,直接使用jq和echarts實現,這里主要講前端怎么連接mqtt

      首先引入mqtt庫

      <script src="https://cdn.bootcdn.net/ajax/libs/mqtt/4.1.0/mqtt.min.js"></script>
      

      然后設置連接參數

        const options = {
          clean: true, // true: 清除會話, false: 保留會話
          connectTimeout: 4000, // 超時時間
          clientId: 'userClient_' + generateRandomString(),
          //前端客戶端很可能比較多,所以這里我們生成一個隨機的6位字母加數字作為clientId,以保證不會重復
          username: 'userClient',
          password: 'aa995231030',
        }
         function generateRandomString() {
          let result = '';
          let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
          let charactersLength = characters.length;
          for (let i = 0; i < 6; i++) {
            result += characters.charAt(Math.floor(Math.random() * charactersLength));
          }
          return result;
        }
       
      

      連接

        // const connectUrl = 'mqtt://larryblog.top/mqtt' 當然你可以使用mqtt協議,但是有可能會遇到 ssl 跨域的問題,如果你不使用 https 可以忽略這一項,直接使用mqtt即可
        const connectUrl = 'wss://larryblog.top/mqtt' //注意,這里使用了nginx進行轉發,后面會講
        const client = mqtt.connect(connectUrl, options)
      

      因為前端代碼不多,我這里直接貼了

      html:

      index.html

      <!DOCTYPE html>
      <html lang="en">
      
      <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet/less" href="./style.less">
        <link rel="stylesheet" >
        <script src="https://cdn.bootcdn.net/ajax/libs/less.js/4.1.3/less.js"></script>
        <title>wemos d1 test</title>
      </head>
      
      <body>
        <div class="app" id="app">
          <div id="deviceStatus">
            <span class="statusLight"></span>
            <span id="statusText">Loading device status</span>
            <!-- <span class="iconfont icon-xinxi"></span> -->
          </div>
          <div class="container">
            <div class="Temperature">
              <div id="echartsViewTemperature"></div>
              <span>Current temperature:</span>
              <span id="Temperature">loading...</span>
            </div>
            <div class="Humidity">
              <div id="echartsViewHumidity"></div>
              <span>Current humidity:</span>
              <span id="Humidity">loading...</span>
            </div>
          </div>
        </div>
      </body>
      <script src="./showTip.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>
      <script src="https://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
      <script src="https://cdn.bootcdn.net/ajax/libs/mqtt/4.1.0/mqtt.min.js"></script>
      <script src="https://cdn.staticfile.org/echarts/4.7.0/echarts.js"></script>
      <script src="./echarts.js?v=1.0.0"></script>
      <script src="./mqttController.js"></script>
      
      </html>
      

      mqttController.js

      // const mqtt = require('mqtt')
      $(document).ready(() => {
        // Welcome to request my open interface. When the device is not online, the latest 2000 pieces of data will be returned
        $.post("https://larryblog.top/api", {
          topic: "getWemosDhtData",
          skip: 0
        },
          (data, textStatus, jqXHR) => {
            setData(data.res)
            // console.log("line:77 data==> ", data)
          },
        );
        // for (let i = 0; i <= 10; i++) {
        //   toast.showToast(1, "test")
        // }
        const options = {
          clean: true, // true: 清除會話, false: 保留會話
          connectTimeout: 4000, // 超時時間
          // Authentication information
          clientId: 'userClient_' + generateRandomString(),
          username: 'userClient',
          password: 'aa995231030',
          // You are welcome to use my open mqtt broker(My server is weak but come on you). When connecting, remember to give yourself a personalized clientId to prevent being squeezed out
          // Topic rule:
          // baseName/deviceId/events
        }
        // 連接字符串, 通過協議指定使用的連接方式
        // ws 未加密 WebSocket 連接
        // wss 加密 WebSocket 連接
        // mqtt 未加密 TCP 連接
        // mqtts 加密 TCP 連接
        // wxs 微信小程序連接
        // alis 支付寶小程序連接
        let timer;
        let isShowTip = 1
        const connectUrl = 'wss://larryblog.top/mqtt'
        const client = mqtt.connect(connectUrl, options)
        client.on('connect', (error) => {
          console.log('已連接:', error)
          toast.showToast("Broker Connected")
          timer = setTimeout(onTimeout, 3500);
          // 訂閱主題
          client.subscribe('wemos/dht11', function (err) {
            if (!err) {
              // 發布消息
              client.publish('testtopic', 'getDHTData')
            }
          })
          client.subscribe('home/status/')
          client.publish('testtopic', 'Hello mqtt')
      
        })
        client.on('reconnect', (error) => {
          console.log('正在重連:', error)
          toast.showToast(3, "reconnecting...")
        })
      
        client.on('error', (error) => {
          console.log('連接失敗:', error)
          toast.showToast(2, "connection failed")
        })
        client.on('message', (topic, message) => {
          // console.log('收到消息:', topic, message.toString())
          switch (topic) {
            case "wemos/dht11":
              const str = message.toString()
              const arr = str.split(", "); // 分割字符串
              const obj = Object.fromEntries(arr.map(s => s.split(": "))); // 轉化為對象
      
              document.getElementById("Temperature").innerHTML = obj.Temperature + " ℃"
              optionTemperature.xAxis.data.push(moment().format("MM-DD/HH:mm:ss"))
              optionTemperature.xAxis.data.length >= 100 && optionTemperature.xAxis.data.shift()
              optionTemperature.series[0].data.length >= 100 && optionTemperature.series[0].data.shift()
              optionTemperature.series[0].data.push(parseFloat(obj.Temperature))
              ChartTemperature.setOption(optionTemperature, true);
      
              document.getElementById("Humidity").innerHTML = obj.Humidity + " %RH"
              optionHumidity.xAxis.data.push(moment().format("MM-DD/HH:mm:ss"))
              optionHumidity.xAxis.data.length >= 100 && optionHumidity.xAxis.data.shift()
              optionHumidity.series[0].data.length >= 100 && optionHumidity.series[0].data.shift()
              optionHumidity.series[0].data.push(parseFloat(obj.Humidity))
              ChartHumidity.setOption(optionHumidity, true);
              break
            case "home/status/":
              $("#statusText").text("device online")
              deviceOnline()
              $(".statusLight").removeClass("off")
              $(".statusLight").addClass("on")
              clearTimeout(timer);
              timer = setTimeout(onTimeout, 3500);
              break
      
          }
      
        })
      
        function deviceOnline() {
          if (isShowTip) {
            toast.showToast(1, "device online")
          }
          isShowTip = 0
        }
      
        function setData(data) {
          // console.log("line:136 data==> ", data)
          for (let i = data.length - 1; i >= 0; i--) {
            let item = data[i]
            // console.log("line:138 item==> ", item)
            optionTemperature.series[0].data.push(item.temperature)
            optionHumidity.series[0].data.push(item.humidity)
            optionHumidity.xAxis.data.push(moment(item.updateDatetime).format("MM-DD/HH:mm:ss"))
            optionTemperature.xAxis.data.push(moment(item.updateDatetime).format("MM-DD/HH:mm:ss"))
          }
          ChartTemperature.setOption(optionTemperature);
          ChartHumidity.setOption(optionHumidity);
        }
      
        function onTimeout() {
          $("#statusText").text("device offline")
          toast.showToast(3, "device offline")
          isShowTip = 1
          document.getElementById("Temperature").innerHTML = "No data"
          document.getElementById("Humidity").innerHTML = "No data"
          $(".statusLight").removeClass("on")
          $(".statusLight").addClass("off")
        }
      
        function generateRandomString() {
          let result = '';
          let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
          let charactersLength = characters.length;
          for (let i = 0; i < 6; i++) {
            result += characters.charAt(Math.floor(Math.random() * charactersLength));
          }
          return result;
        }
      });
      
      

      showTip.js 是我發布在npm上的一個包,如果有需要可以自行npm下載

      style.less

      * {
        padding: 0;
        margin: 0;
        color: #fff;
      }
      
      .app {
        background: #1b2028;
        width: 100vw;
        height: 100vh;
        display: flex;
        flex-direction: column;
        overflow: hidden;
      
        #deviceStatus {
          display: flex;
          align-items: center;
          gap: 10px;
          padding: 20px;
      
          .statusLight {
            display: block;
            height: 10px;
            width: 10px;
            border-radius: 100px;
            background: #b8b8b8;
      
            &.on {
              background: #00a890;
            }
      
            &.off {
              background: #b8b8b8;
            }
          }
        }
      
        .container {
          width: 100%;
          height: 0;
          flex: 1;
          display: flex;
      
          @media screen and (max-width: 768px) {
            flex-direction: column;
          }
      
          >div {
            flex: 1;
            height: 100%;
            text-align: center;
      
            #echartsViewTemperature,
            #echartsViewHumidity {
              width: 80%;
              height: 50%;
              margin: 10px auto;
              // background: #eee;
            }
          }
        }
      }
      

      echarts.js 這個文件是我自己寫的,別學我這種命名方式,這是反例

      let optionTemperature = null
      let ChartTemperature = null
      $(document).ready(() => {
        setTimeout(() => {
          // waiting
          ChartTemperature = echarts.init(document.getElementById('echartsViewTemperature'));
          ChartHumidity = echarts.init(document.getElementById('echartsViewHumidity'));
          // 指定圖表的配置項和數據
          optionTemperature = {
            textStyle: {
              color: '#fff'
            },
            tooltip: {
              trigger: 'axis',
              // transitionDuration: 0,
              backgroundColor: '#fff',
              textStyle: {
                color: "#333",
                align: "left"
              },
            },
            xAxis: {
              min: 0,
              data: [],
              boundaryGap: false,
              splitLine: {
                show: false
              },
              axisLine: {
                lineStyle: {
                  color: '#fff'
                }
              }
            },
            yAxis: {
              splitLine: {
                show: false
              },
              axisTick: {
                show: false // 隱藏 y 軸的刻度線
              },
              axisLine: {
                show: false,
                lineStyle: {
                  color: '#fff'
                }
              }
            },
            grid: {
              // 為了讓標尺和提示框在圖表外面,需要將圖表向外擴展一點
              left: '10%',
              right: '5%',
              bottom: '5%',
              top: '5%',
              containLabel: true,
            },
            series: [{
              // clipOverflow: false,
              name: '溫度',
              type: 'line',
              smooth: true,
              symbol: 'none',
              data: [],
              itemStyle: {
                color: '#00a890'
              },
              areaStyle: {
                color: {
                  type: 'linear',
                  x: 0,
                  y: 0,
                  x2: 0,
                  y2: 1,
                  colorStops: [{
                    offset: 0,
                    color: '#00a89066' // 0% 處的顏色
                  }, {
                    offset: 1,
                    color: '#00a89000' // 100% 處的顏色
                  }],
                  global: false // 缺省為 false
                }
              },
              hoverAnimation: true,
              label: {
                show: false,
              },
              markLine: {
                symbol: ['none', 'none'],
                data: [
                  {
                    type: 'average',
                    name: '平均值',
                  },
                ],
              },
            }]
          };
          optionHumidity = {
            textStyle: {
              color: '#fff'
            },
            tooltip: {
              trigger: 'axis',
              backgroundColor: '#fff',
              textStyle: {
                color: "#333",
                align: "left"
              },
            },
            xAxis: {
              min: 0,
              data: [],
              boundaryGap: false,
              splitLine: {
                show: false
              },
              axisTick: {
                //x軸刻度相關設置
                alignWithLabel: true,
              },
              axisLine: {
                lineStyle: {
                  color: '#fff'
                }
              }
            },
            yAxis: {
              splitLine: {
                show: false
              },
              axisTick: {
                show: false // 隱藏 y 軸的刻度線
              },
              axisLine: {
                show: false,
                lineStyle: {
                  color: '#fff'
                }
              }
            },
            grid: {
              // 為了讓標尺和提示框在圖表外面,需要將圖表向外擴展一點
              left: '5%',
              right: '5%',
              bottom: '5%',
              top: '5%',
              containLabel: true,
            },
            // toolbox: {
            //   feature: {
            //     dataZoom: {},
            //     brush: {
            //       type: ['lineX', 'clear'],
            //     },
            //   },
            // },
            series: [{
              clipOverflow: false,
              name: '濕度',
              type: 'line',
              smooth: true,
              symbol: 'none',
              data: [],
              itemStyle: {
                color: '#ffa74b'
              },
              areaStyle: {
                color: {
                  type: 'linear',
                  x: 0,
                  y: 0,
                  x2: 0,
                  y2: 1,
                  colorStops: [{
                    offset: 0,
                    color: '#ffa74b66' // 0% 處的顏色
                  }, {
                    offset: 1,
                    color: '#ffa74b00' // 100% 處的顏色
                  }],
                  global: false // 缺省為 false
                }
              },
              hoverAnimation: true,
              label: {
                show: false,
              },
              markLine: {
                symbol: ['none', 'none'],
                data: [
                  {
                    type: 'average',
                    name: '平均值',
                  },
                ],
              },
            }]
          };
      
          // 使用剛指定的配置項和數據顯示圖表。
          ChartTemperature.setOption(optionTemperature);
          ChartHumidity.setOption(optionHumidity);
        }, 100)
      });
      
      

      當你看到這里,你應該可以在你的前端頁面上展示你的板子發來的每一條消息了,但是還遠遠做不到首圖上那種密密麻麻的數據,我并不是把頁面開了一天,而是使用了后端和數據庫存儲了一部分數據。

      后端

      后端我們分為了兩個部分,一個是nodejs的后端程序,一個是nginx代理,這里先講代理,因為上一步前端的連接需要走這個代理

      nginx

      如果你沒有使用https連接,那么可以不看本節,直接使用未加密的mqtt協議,如果你有自己的域名,且申請了ssl證書,那么可以參考我的nginx配置,配置如下

      http {
      	sendfile on;
      	tcp_nopush on;
      	tcp_nodelay on;
      	keepalive_timeout 65;
      	types_hash_max_size 2048;
      	include /etc/nginx/mime.types;
      	default_type application/octet-stream;
      
      	##
      	# SSL Settings
      	##
      	server {
          listen 80;
          server_name jshub.cn;
          #將請求轉成https
          rewrite ^(.*)$ https://$host$1 permanent;
      	}
      	server {
              listen 443 ssl;
      				server_name jshub.cn;
      				location / {
      					root /larryzhu/web/release/toolbox;
      					index index.html index.htm;
      					try_files $uri $uri/ /index.html;
      				}
      	 location /mqtt {
                 proxy_pass http://localhost:8083;
                 proxy_http_version 1.1;
                 proxy_set_header Upgrade $http_upgrade;
                 proxy_set_header Connection "upgrade";
            	 }
              # SSL 協議版本
              ssl_protocols TLSv1.2;
              # 證書
              ssl_certificate /larryzhu/web/keys/9263126_jshub.cn.pem;
              # 私鑰
              ssl_certificate_key /larryzhu/web/keys/9263126_jshub.cn.key;
              # ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
              # ssl_ciphers AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256;
      
              # 與False Start沒關系,默認此項開啟,此處減少抓包的干擾而關閉
              # ssl_session_tickets off;
      
              # return 200 "https ok \n";
        }
      

      注意這只是部分配置,切不可全部覆蓋你的配置。

      如果你不會使用nginx,說明你無需配置 ssl ,直接使用 mqtt協議即可。

      后端程序部分

      這里以egg.js框架為例

      首先需要下載egg.js的插件 egg-emqtt ,直接使用npm下載即可,詳細配置和啟用方法 參見 MQTT系列實踐二 在EGG中使用mqtt

      上面教程的方法并不全面,可以下載我的示例,仿照著寫一下,因為內容相對復雜,地址:https://gitee.com/zhu_yongbo/mqttineggjs

      其中還包含了 mysql 數據庫的連接方法,內有我服務器的地址、mysql開放端口,用戶名以及密碼,我服務器還剩不到十天到期,有緣人看到我的文章可以對我的服務器為所欲為,沒有什么重要數據。

      mysql

      mysql方面,只需要一個庫,一個表即可完成全部工作

      image-20230223114608671

      如圖所示,不復雜,仿照我的建庫即可

      有一點,比較重要,因為mysql本身不適用于存儲量級太大的數據,我們的數據重復的又比較多,可以考慮一下壓縮算法,或者添加一個事件(每次插入時檢查數據量是否超過一定值)。像我的板子大概正常累計運行了幾天的時間(每兩秒一條數據),到目前可以看到已經累計了七十萬條數據了,如果不是因為我設置了插入事件,這個數據量已經可以明顯影響查詢速度了。

      可以仿照我的事件,語句如下:

      DELIMITER $$
      CREATE TRIGGER delete_oldest_data
      AFTER INSERT ON wemosd1_dht11
      FOR EACH ROW
      BEGIN
          -- 如果數據量超過43200(每兩秒插入一條,這是一天的量)條,調用存儲過程刪除最早的一條數據
          IF (SELECT COUNT(*) FROM wemosd1_dht11) > 43200 THEN
              CALL delete_oldest();
          END IF;
      END$$
      DELIMITER ;
      
      -- 創建存儲過程
      CREATE PROCEDURE delete_oldest()
      BEGIN
          -- 刪除最早的一條數據
          delete from wemosd1_dht11 order by id asc limit 1
          
      END$$
      DELIMITER ;
      
      

      BTW:這是chatGPT教我的,我只進行了一點小小的修改。

      這樣做會刪除id比較小的數據,然后就會導致,id會增長的越來越大,好處是可以看到一共累計了多少條數據。但是如果你不想讓id累計,那么可以選擇重建id,具體做法,建議你咨詢一下chatGPT

      結語

      至此,我們已經完成了前端、后端、設備端三端連通。

      我們整體梳理一下數據是怎么一步一步來到我們眼前的:

      首先wemos d1開發板會在DHT11溫濕度傳感器上讀取溫濕度值,然后開發板把數據通過mqtt廣播給某topic,我們的前后端都訂閱了此topic,后端收到后,把處理過的數據存入mysql,前端直接使用echarts進行展示,當前端啟動時,還可以向后端程序查詢歷史數據,比如前8000條數據,之后的變化由在線的開發板提供,我們就得到了一個實時的,并且能看到歷史數據的溫濕度在線大屏。

      如果你覺得牛逼,就給我點個贊吧。

      posted @ 2023-02-23 13:06  神王·德萊厄斯  閱讀(819)  評論(1)    收藏  舉報
      主站蜘蛛池模板: 国产成人一卡2卡3卡四卡视频| 丰宁| 亚洲av日韩av中文高清性色| 欧洲码亚洲码的区别入口| 久9视频这里只有精品| 巨大黑人极品videos精品| 国产精品无码av在线一区| 久久精品不卡一区二区| 伊在人间香蕉最新视频| 中年国产丰满熟女乱子正在播放| 精品久久精品久久精品久久| 国产一区二区内射最近更新| 国产精品午夜精品福利| 亚洲免费人成视频观看| 亚洲欧美日韩综合久久久| 国产一区二区三区乱码在线观看| 中文字幕乱码人妻综合二区三区 | XXXXXHD亚洲日本HD| 免费看黄色亚洲一区久久| 欧美一区二区三区在线观看 | 高清中文字幕一区二区| 人人玩人人添人人澡超碰| 成全高清在线播放电视剧| 粉嫩国产一区二区三区在线| 免费VA国产高清大片在线| 蜜桃久久精品成人无码av| 98日韩精品人妻一二区| 国产一区二区三区不卡在线看| 欧美激情内射喷水高潮| 国产片av在线观看国语| 亚洲中文字幕无码av永久| 亚洲a人片在线观看网址| 国产日本一区二区三区久久| 不卡国产一区二区三区| 久久亚洲av成人无码软件| 日韩乱码卡一卡2卡三卡四| 日本中文字幕乱码免费| 国产精品白浆免费视频| 日本无人区一区二区三区| 国产亚洲精品自在久久| 亚洲国产成人午夜在线一区 |