自己寫微信小程序MQTT模擬器

自己寫微信小程序MQTT模擬器

陳拓 [email protected] 2019.10.6/2019.10.11

1. MQTT模擬器體驗

在自己寫MQTT模擬器之前先從網上安裝一個現成的體驗一下,這可以先看看我之前寫的文章《微信小程序MQTT模擬器阿里雲物聯網平臺測試》,在下面的網址可以找到這篇文章:

https://mp.csdn.net/postedit/102216865

https://zhuanlan.zhihu.com/p/84810734

下面我們自己寫一個MQTT模擬器實現這些功能。

2. 創建新項目

2.1 打開微信開發者工具,新建項目

填寫你的AppID,新建。

在默認情況下,項目路徑爲C:\Users\Administrator\WeChatProjects。

2.2 準備圖片

放在pages下的images目錄中。

2.3 改寫系統生成的代碼

2.3.1 改寫app.json

{
  "pages": [
    "pages/index/index",
    "pages/logs/logs"
  ],
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "我的MQTT模擬器",
    "navigationBarTextStyle": "black"
  },
  "sitemapLocation": "sitemap.json"
}

只改“我的MQTT模擬器”這裏。

2.3.2 下載支持MQTT協議的js庫和支持sha1加密的庫

  • 下載mqtt.js

https://github.com/mqttjs/MQTT.js

或者下載mqtt.min.js

https://unpkg.com/[email protected]/dist/mqtt.min.js

mqtt.min.js小一些。

  • 下載hex_hmac_sha1.js

https://github.com/xihu-fm/aliyun-iot-client-sdk/tree/master/lib

兩個js文件都放在utils目錄下:

2.3.3 改寫index.wxml

<!--index.wxml-->
<view>
  <view class="main-center">
    <image src="{{imageUrl}}" class="ledinfo-avatar"></image>
    <view class="ledinfo-values">
      <text>溼度:</text><text>{{humidity}}</text><text>%</text>
      <text>溫度:</text><text>{{temperature}}</text><text>℃</text>
    </view>
  </view>
  <text class='subheading'>設備身份三元組</text>
  <view style='margin-top: 20rpx;'>
    <view class='connect-info background-white'>
      <text class='text'>productKey:</text> 
      <input class='input' name='productKey' placeholder='替換'
      bindinput='productKeyInput'/>
    </view>
    <view class='connect-info background-white'>
      <text class='text'>deviceName:</text> 
      <input class='input' name='deviceName' placeholder='替換'
      bindinput='deviceNameInput'/>
    </view>
    <view class='connect-info background-white'>
      <text class='text'>deviceSecret:</text> 
      <input class='input' name='deviceSecret' placeholder='替換'
      bindinput='deviceSecretInput'/>
    </view>
  </view>
  <view class="buttons">
    <view  class="button-container" bindtap='online'>
      <text class="button">設備上線</text>
    </view>
    <view  class="button-container" bindtap='publish'>
      <text class="button">上報數據</text>
    </view>
    <view  class="button-container" bindtap='event'>
      <text class="button">告 警</text>
    </view>
    <view  class="button-container" bindtap='service'>
      <text class="button">訂閱主題</text>
    </view>
    <view  class="button-container" bindtap='offline'>
      <text class="button">設備下線</text>
    </view>
  </view>
  <text class='subheading'>設備日誌</text>
  <view style='margin-top: 20rpx;'>
    <view class='deviceState background-white'>
      <text class='text'>{{deviceState}}</text>
    </view>
  </view> 
  <view class='devicelog'>
    <text>{{deviceLog}}</text>
  </view>
</view>

2.3.4 改寫index.wxss

/**index.wxss**/
page {
  background-color: rgb(240, 240, 240);
  font-size: 26rpx;
}

.main-center {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.connect-info {
  display: flex;
  flex-direction: row;
  margin-top: 1rpx;
  height: 60rpx;
}

.background-white {
  background-color: #FFF;
}

.buttons {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items:center;
  margin-left: 10rpx;
  margin-right: 10rpx;
  margin-top: 30rpx;
  margin-bottom: 30rpx;
}

.button {
  line-height: 60rpx;
}

.button-container {
  margin-top: 0px;
  border: 1px solid #aaa;
  width: 140rpx;
  height: 60rpx;
  border-radius: 5px;
  text-align: center;
}

.ledinfo-values {
  color: #92ADF0;
  margin: 20rpx;
}

.ledinfo-avatar {
  width: 128rpx;
  height: 128rpx;
  margin: 20rpx;
}

.subheading {
  color: rgb(128, 128, 128);
  margin: 20rpx;
}

.text {
    width: 220rpx;
    height: 30rpx;
    margin-left: 20rpx;
    margin-top: 10rpx;
}

.input {
    width: 100%;
    height: 30rpx;
    margin-left: 20rpx;
    margin-top: 10rpx;
    color: rgb(128, 128, 128);
}

.deviceState {
  color: #1d953f;
}

.devicelog {
  margin-top: 20rpx;
  word-break:break-all;
  color: #426ab3;
}

2.3.5 改寫index.js

//index.js

// 設備身份三元組+區域
const deviceConfig = {
  productKey: "替換",
  deviceName: "替換",
  deviceSecret: "替換",
  regionId: "cn-shanghai"
};

function getPostData() {
  const payloadJson = {
    id: Date.now(),
    params: {
      temperature: Math.floor((Math.random() * 20) + 10),
      humidity: Math.floor((Math.random() * 20) + 60)
    },
    method: "thing.event.property.post"
  }
  return JSON.stringify(payloadJson);
}

function getAlarmPostData() {
  const payloadJson = {
    id: Date.now(),
    params: {
      temperature: Math.floor((Math.random() * 20) + 10)
    },
    method: "thing.event.hotAlarm.post"
  }
  return JSON.stringify(payloadJson);
}

const util = require('../../utils/util.js')
var mqtt = require('../../utils/mqtt.min.js')
const crypto = require('../../utils/hex_hmac_sha1.js')
var client
Page({
  data: {
    temperature: '0',
    humidity: '0',
    imageUrl: '../images/iLED1.png',
    deviceLog: '',
    deviceState: ''
  },

  // 設備身份三元組輸入框事件處理函數
  productKeyInput: function (e) {
    deviceConfig.productKey = e.detail.value
  },
  deviceNameInput: function (e) {
    deviceConfig.deviceName = e.detail.value
  },
  deviceSecretInput: function (e) {
    deviceConfig.deviceSecret = e.detail.value
  },

  // 設備上線 按鈕點擊事件
  online: function (e) {
    this.doConnect()
  },
  doConnect() {
    var that = this;
    const options = this.initMqttOptions(deviceConfig);
    console.log(options)
    client = mqtt.connect('wxs://productKey.iot-as-mqtt.cn-shanghai.aliyuncs.com', options)
    client.on('connect', function () {
      console.log('連接服務器成功')
      let dateTime = util.formatTime(new Date());
      that.setData({
        deviceState: dateTime + ' Connect Success!'
      })
    })
  },

  //IoT平臺mqtt連接參數初始化
  initMqttOptions(deviceConfig) {
    const params = {
      productKey: deviceConfig.productKey,
      deviceName: deviceConfig.deviceName,
      timestamp: Date.now(),
      clientId: Math.random().toString(36).substr(2),
    }

    //CONNECT參數
    const options = {
      keepalive: 60, //60s
      clean: true, //cleanSession不保持持久會話
      protocolVersion: 4 //MQTT v3.1.1
    }

    //1.生成clientId,username,password
    options.password = this.signHmacSha1(params, deviceConfig.deviceSecret);
    options.clientId = `${params.clientId}|securemode=2,signmethod=hmacsha1,timestamp=${params.timestamp}|`;
    options.username = `${params.deviceName}&${params.productKey}`;

    return options;
  },

  /*
    生成基於HmacSha1的password
    參考文檔:https://help.aliyun.com/document_detail/73742.html?#h2-url-1
  */
  signHmacSha1(params, deviceSecret) {
    let keys = Object.keys(params).sort();
    // 按字典序排序
    keys = keys.sort();
    const list = [];
    keys.map((key) => {
      list.push(`${key}${params[key]}`);
    });
    const contentStr = list.join('');
    return crypto.hex_hmac_sha1(deviceSecret, contentStr);
  },

  // 上報數據 按鈕點擊事件
  publish: function (e) {
    var that = this;
    let topic = `/sys/${deviceConfig.productKey}/${deviceConfig.deviceName}/thing/event/property/post`;
    // 注意用`符號,不是' !!!!!
    let JSONdata = getPostData()
    console.log("===postData\n topic=" + topic)
    console.log("payload=" + JSONdata)
    client.publish(topic, JSONdata)
    that.setData({
      deviceLog: 'topic=' + topic + '\n' + 'payload=' + JSONdata
    })
  },

  // 告警 按鈕點擊事件
  event: function (e) {
    var that = this;
    let topic_alarm = `/sys/${deviceConfig.productKey}/${deviceConfig.deviceName}/thing/event/hotAlarm/post`;
    let JSONdata = getAlarmPostData()
    console.log("===postData\n topic=" + topic_alarm)
    console.log("payload=" + JSONdata)
    client.publish(topic_alarm, JSONdata)
    that.setData({
      deviceLog: 'topic=' + topic_alarm + '\n' + 'payload=' + JSONdata
    })
  },

  // 訂閱主題 按鈕點擊事件
  service: function (e) {
    var that = this;
    that.setData({
      deviceLog: '接收消息監聽'
    })
    //接收消息監聽
    let topic = `/sys/${deviceConfig.productKey}/${deviceConfig.deviceName}/thing/event/property/post`;
    client.on('message', function (topic, message) {
      // message is Buffer
      let messageStr = message.toString()
      console.log('收到消息:' + messageStr)
      that.setData({
        deviceLog: '收到消息:\n' + messageStr
      })

      if (messageStr.indexOf('on') > 0) {
        that.setData({
          imageUrl: '../images/iLED2.png',
        })
      }
      if (messageStr.indexOf('off') > 0) {
        that.setData({
          imageUrl: '../images/iLED1.png',
        })
      }
      if (messageStr.indexOf('blue') > 0) {
        that.setData({
          imageUrl: '../images/iLED3.png',
        })
      }
      if (messageStr.indexOf('green') > 0) {
        that.setData({
          imageUrl: '../images/iLED4.png',
        })
      }
    })
  },

  // 設備下線 按鈕點擊事件
  offline: function () {
    var that = this;
    client.end()  // 關閉連接
    console.log('服務器連接斷開')
    let dateTime = util.formatTime(new Date());
    that.setData({
      deviceState: dateTime + ' Disconnect!'
    })
  },

  onLoad: function () {
    var that = this
    setInterval(function () {
      that.setData({
        temperature: Math.floor((Math.random() * 20) + 10),
        humidity: Math.floor((Math.random() * 20) + 60)
      })
    }, 3000)
  },
})

3. 註冊阿里雲賬號

註冊阿里雲賬號,獲得三元組:PublicKey、DeviceName、DeviceSecret。

見《微信小程序MQTT模擬器阿里雲物聯網平臺測試》一文。

https://mp.csdn.net/postedit/102216865

https://zhuanlan.zhihu.com/p/84810734

4. 微信小程序測試

4.1 打開MQTT測試平臺

  • 登錄,點擊“控制檯”,選擇物聯網平臺

4.2 用微信小程序模擬器測試

在微信開發者工具中打開模擬器。

4.2.1 設備上線

  • 輸入設備身份三元組,點擊“設備上線”

  • 回到物聯網平臺

點擊F5刷新設備列表,可以看到設備狀態已經是在線,查看小程序設備日誌和設備列表頁面中的最後上線時間,用模擬器測試慢一秒,用真機測試時間一致。

如果連接參數不正確,或遇到其他連接問題,可以在開發工具的Console中查看,例如productKey輸入錯誤:

4.2.2 上報數據

  • 回到微信開發者開發工具

在設備上線時,點擊“上報數據”,我們看到MQTT模擬器上報了當前溼度溫度值。

上報的溼度73%,溫度29℃。

  • 回到物聯網平臺,在設備詳情的運行狀態看設備上報的數據

和MQTT模擬器上報的數據一致。

4.2.3 告警

  • 在小程序界面

在設備上線時,點擊“告警”,就會生成一條事件告警,並上報當前的溫度。

  • 在物聯網平臺控制檯“設備詳情”-“事件管理”中查看

可以看到“溫度過高報警”。

在實際情況下,報警溫度的閾值在客戶端設定,當溫度超過閾值時發送報警,這裏只是演示報警功能,不用在意溫度值的大小。

4.2.4 訂閱主題

  • 在小程序界面,在設備上線時,點擊“訂閱主題”:

等待接收消息。

  • 在物聯網平臺控制檯“設備管理 > 設備詳情”點擊“在線調試”。

  • 轉到“在線調試”頁面

1) 選擇設備

2) 選擇“調試真實設備”選項卡

3) 選擇“服務調用”

4) 選中功能“開燈(switch)”

5) 輸入參數{"status":"on"}

6) 點擊“發送指令”

查看實時日誌:

  • 回到小程序界面

看,燈亮了!

還可以在物聯網平臺上:

1) 發送{"status":"off"},關燈

2) 發送{"status":"blue"},燈發藍光

3) 發送{"status":" green"},燈發綠光

4.2.5 設備下線

  • 在小程序界面,點擊“設備下線”

  • 在控制檯“設備列表”中可以看到設備已經離線

4.3 用真機測試

見《微信小程序MQTT模擬器阿里雲物聯網平臺測試》一文。

https://mp.csdn.net/postedit/102216865

https://zhuanlan.zhihu.com/p/84810734

5. 源代碼

https://github.com/chentuo2000/MyMQTTsimulator

 

參考資料

  1. 實例:使用MQTT進行交互
    https://www.jianshu.com/p/b452e2cee0d6
  2. 微信小程序佈局display flex佈局介紹https://blog.csdn.net/sinat_17775997/article/details/61428601
  3. 微信小程序以 websocket 連接阿里雲IOT物聯網平臺mqtt服務器,封裝起來使用就是這麼簡單!
    https://blog.csdn.net/xh870189248/article/details/91490697
  4. 阿里雲物聯網平臺數據格式
    https://help.aliyun.com/document_detail/73736.html?spm=a2c4g.11186623.6.630.3d1b58009qvHen#title-7r8-lbe-2m1

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章