自己寫微信小程序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測試平臺
- 進入阿里雲產品官網https://aliyun.com/product/iot
- 登錄,點擊“控制檯”,選擇物聯網平臺
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
參考資料
- 實例:使用MQTT進行交互
https://www.jianshu.com/p/b452e2cee0d6 - 微信小程序佈局display flex佈局介紹https://blog.csdn.net/sinat_17775997/article/details/61428601
- 微信小程序以 websocket 連接阿里雲IOT物聯網平臺mqtt服務器,封裝起來使用就是這麼簡單!
https://blog.csdn.net/xh870189248/article/details/91490697 - 阿里雲物聯網平臺數據格式
https://help.aliyun.com/document_detail/73736.html?spm=a2c4g.11186623.6.630.3d1b58009qvHen#title-7r8-lbe-2m1