自己写微信小程序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 打开微信开发者工具,新建项目

自己写微信小程序MQTT模拟器

填写你的AppID,新建。

在默认情况下,项目路径为C:\Users\Administrator\WeChatProjects。

2.2 准备图片

自己写微信小程序MQTT模拟器

放在pages下的images目录中。

自己写微信小程序MQTT模拟器

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目录下:

自己写微信小程序MQTT模拟器

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.生成clientIdusernamepassword

    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;

  },

  /*

    生成基于HmacSha1password

    参考文档: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测试平台

自己写微信小程序MQTT模拟器

  • 登录,点击“控制台”,选择物联网平台

自己写微信小程序MQTT模拟器

4.2 用微信小程序模拟器测试

在微信开发者工具中打开模拟器。

4.2.1 设备上线

  • 输入设备身份三元组,点击“设备上线”

自己写微信小程序MQTT模拟器

  • 回到物联网平台

点击F5刷新设备列表,可以看到设备状态已经是在线,查看小程序设备日志和设备列表页面中的最后上线时间,用模拟器测试慢一秒,用真机测试时间一致。

自己写微信小程序MQTT模拟器

如果连接参数不正确,或遇到其他连接问题,可以在开发工具的Console中查看,例如productKey输入错误:

自己写微信小程序MQTT模拟器

4.2.2 上报数据

  • 回到微信开发者开发工具

在设备上线时,点击“上报数据”,我们看到MQTT模拟器上报了当前湿度温度值。

自己写微信小程序MQTT模拟器

上报的湿度73%,温度29℃。

  • 回到物联网平台,在设备详情的运行状态看设备上报的数据

自己写微信小程序MQTT模拟器

和MQTT模拟器上报的数据一致。

4.2.3 告警

  • 在小程序界面

在设备上线时,点击“告警”,就会生成一条事件告警,并上报当前的温度。

自己写微信小程序MQTT模拟器

  • 在物联网平台控制台“设备详情”-“事件管理”中查看

自己写微信小程序MQTT模拟器

可以看到“温度过高报警”。

在实际情况下,报警温度的阈值在客户端设定,当温度超过阈值时发送报警,这里只是演示报警功能,不用在意温度值的大小。

4.2.4 订阅主题

  • 在小程序界面,在设备上线时,点击“订阅主题”:

自己写微信小程序MQTT模拟器

等待接收消息。

  • 在物联网平台控制台“设备管理 > 设备详情”点击“在线调试”。

自己写微信小程序MQTT模拟器

  • 转到“在线调试”页面

自己写微信小程序MQTT模拟器

1) 选择设备

2) 选择“调试真实设备”选项卡

3) 选择“服务调用”

4) 选中功能“开灯(switch)”

5) 输入参数{"status":"on"}

6) 点击“发送指令”

查看实时日志:

自己写微信小程序MQTT模拟器

  • 回到小程序界面

自己写微信小程序MQTT模拟器

看,灯亮了!

还可以在物联网平台上:

1) 发送{"status":"off"},关灯

2) 发送{"status":"blue"},灯发蓝光

3) 发送{"status":" green"},灯发绿光

4.2.5 设备下线

  • 在小程序界面,点击“设备下线”

自己写微信小程序MQTT模拟器

  • 在控制台“设备列表”中可以看到设备已经离线

自己写微信小程序MQTT模拟器

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