微信小程序对接阿里飞燕平与智能配网方案
上期我们讨论了如何采用STM32+ESP-01s模组连接阿里飞燕平台的实现过程,那我们实现WIFI模组入网的主要目的就是要实现远程控制,所以自然也需要让移动端连接到阿里飞燕平台,这样才能实现完全的互联。
实现目前移动端连接服务平台的方案一般有APP、网页以及微信小程序等几类,基于微信小程序的免安装以及界面优势,我们决定采用微信小程序的方案。那接下来就让我们来讨论如何采用微信小程序连接阿里飞燕平台来实现对WIFI模组的控制。
基于篇幅,这里就不讨论微信小程序如何注册账号,开发工具如何使用等话题了,大家可以自主参考微信小程序的开发文档:
https://developers.weixin.qq.com/miniprogram/dev/framework/
微信小程序编程是类似于网页编程的HTML + CSS + JS 模式,对于MQTT客户端的实现,网上已有开源的JS代码mqtt.min.js可以直接使用,下载地址如下:
https://unpkg.com/mqtt@2.18.8/dist/mqtt.min.js
这就省去了我们自己编写MQTT客户端的工作,当然如果您觉得需要彻底的了解MQTT的协议,也可以动手自己写一个MQTT客户端。MQTT客户端的实现可以参考:
https://www.cnblogs.com/dongkuo/p/11360419.html
准备工作完成,现在开始小程序编写了:
- 我们先在小程序开发工具中建立一个Libraries目录用于放置MQTT.js。
- 勾选微信开发工具->详情->本地设置->【不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书】选择这个选项后不在微信小程序合法域名配置中的域名也可以打开,但是后期正式上线,还是需要配置微信小程序合法域名才行。
- 由于阿里云MQTT服务器登录需要配置password、username、clientId等,配置方法请参考:
https://help.aliyun.com/document_detail/86706.html?spm=a2c4g.11174283.2.44.74b41668Is1UTA
代码可参考:
const crypto = require('hex_hmac_sha1');
exports.getAliyunIotMqttClient = function (options) {
const deviceSecret = options.deviceSecret;
let secureMode = (options.protocol === 'mqtts') ? 3 : 2;
var opts = {
productKey: options.productKey,
deviceName: options.deviceName,
timestamp: Date.now(),
clientId: Math.random().toString(36).substr(2)
}
let keys = Object.keys(opts).sort();
// 按字典序排序
keys = keys.sort();
const list = [];
keys.map((key) => {
list.push(`${key}${options[key]}`);
});
const contentStr = list.join('');
options.password = crypto.hex_hmac_sha1(deviceSecret, contentStr);
options.clientId = `${opts.clientId}|securemode=${secureMode},signmethod=hmacsha1,timestamp=${options.timestamp}|`;
options.username = `${opts.deviceName}&${opts.productKey}`;
options.port = options.port || 1883;
options.host = options.host || `${ options.productKey}.iot-as-mqtt.${ options.regionId}.aliyuncs.com`;
return (options);
}
其中hex_hmac_sha1.js可下载:
https://github.com/xihu-fm/aliyun-iot-client-sdk/tree/master/lib
接下来是连接MQTT服务器的代码:
let clientOpt = aliyunOpt.getAliyunIotMqttClient({
productKey: app.globalData.productKey,
deviceName: app.globalData.deviceName,
deviceSecret: app.globalData.deviceSecret,
regionId: app.globalData.regionId,
port: 0,
});
//console.log("get data:" + JSON.stringify(clientOpt));
//得到连接域名
let host = 'wxs://' + clientOpt.host;
this.setData({
'options.clientId': clientOpt.clientId,
'options.password': clientOpt.password,
'options.username': clientOpt.username,
})
//console.log("this.data.options host:" + host);
//console.log("this.data.options data:" + JSON.stringify(this.data.options));
//开始连接
app.globalData.client = mqtt.connect(host, this.data.options)
//连接成功回调
app.globalData.client.on('connect', function (connack) {
app.globalData.isConnected = true
wx.showToast({
title: '连接成功'
})
wx.navigateTo({
url: '/pages/mqtt/mqtt',
})
})
//服务器下发消息的回调
app.globalData.client.on("message", function (topic, payload) {
console.log(" 收到 topic:" + topic + " , payload :" + payload)
wx.showModal({
content: " 收到topic:[" + topic + "], payload :[" + payload + "]",
showCancel: false,
});
})
//服务器连接异常的回调
app.globalData.client.on("error", function (error) {
app.globalData.isConnected = false
console.log(" 服务器error的回调" + error)
})
//服务器重连连接异常的回调
app.globalData.client.on("reconnect", function () {
app.globalData.isConnected = false
console.log(" 服务器reconnect的回调")
})
//服务器断开连接连接的回调
app.globalData.client.on("offline", function (errr) {
app.globalData.isConnected = false
console.log(" 服务器offline的回调")
})
let clientOpt = aliyunOpt.getAliyunIotMqttClient({
productKey: app.globalData.productKey,
deviceName: app.globalData.deviceName,
deviceSecret: app.globalData.deviceSecret,
regionId: app.globalData.regionId,
port: 0,
});
//console.log("get data:" + JSON.stringify(clientOpt));
//得到连接域名
let host = 'wxs://' + clientOpt.host;
this.setData({
'options.clientId': clientOpt.clientId,
'options.password': clientOpt.password,
'options.username': clientOpt.username,
})
//console.log("this.data.options host:" + host);
//console.log("this.data.options data:" + JSON.stringify(this.data.options));
//开始连接
app.globalData.client = mqtt.connect(host, this.data.options)
//连接成功回调
app.globalData.client.on('connect', function (connack) {
app.globalData.isConnected = true
wx.showToast({
title: '连接成功'
})
wx.navigateTo({
url: '/pages/mqtt/mqtt',
})
})
//服务器下发消息的回调
app.globalData.client.on("message", function (topic, payload) {
console.log(" 收到 topic:" + topic + " , payload :" + payload)
wx.showModal({
content: " 收到topic:[" + topic + "], payload :[" + payload + "]",
showCancel: false,
});
})
//服务器连接异常的回调
app.globalData.client.on("error", function (error) {
app.globalData.isConnected = false
console.log(" 服务器error的回调" + error)
})
//服务器重连连接异常的回调
app.globalData.client.on("reconnect", function () {
app.globalData.isConnected = false
console.log(" 服务器reconnect的回调")
})
//服务器断开连接连接的回调
app.globalData.client.on("offline", function (errr) {
app.globalData.isConnected = false
console.log(" 服务器offline的回调")
})
小程序编写完成后,我们可以运行一下,看在阿里飞燕平台上显示设备已经上线了:
现在设备和小程序都已经可以连接阿里飞燕平台了,那小程序是否就能控制设备了呢?
答案是还不行,当我们将设备与小程序同时连接阿里飞燕平台时你会发现总有一个会被强制下线,两者不能实现互联。这是因为我们的设备和小程序连接的是平台设置的同一个设备同一个域名。这可怎么办?我们自然会想到既然两个程序不能同时连接平台上的同一个设备,那我在平台上建两个设备可以吗?例如:
建立两个设备当然可以,但它们在平台上怎么实现消息传递呢?
这就要用到阿里飞燕平台提供的规则引擎:
我们以云产品流转为例:
首先建立一个由设备(IntelligentCurtain1)转发到WeChat(IntelligentCurtain2)的流转,然后按照相同方法建立一个由WeChat(IntelligentCurtain2)转发到设备(IntelligentCurtain1)的流转
点击云产品流转进入页面:
创建规则:
填写规则名称选择数据格式点击确认点击前往编辑:
点击编写SQL:
字段填入*,表示选择所有信息,按照我们定义的Topic填入Topic,点击确认。
点击添加操作:
选择操作为发送到另一个Topic,Topic选择接收信息的Topic如上图。
两个云产品流转规则建立好以后,都点击启用。现在再用设备连接IntelligentCurtain1,Wechat连接IntelligentCurtain2。就可以正常通信了。
最后,我们还存在一个问题,那就是如何让设备智能配网的问题。目前常用的配网技术有乐鑫的ESPTouch以及腾讯的AirKiss,相关资料都可在网上找到,按照协议我们可以自己在小程序或者APP中实现,但我找到一个更快捷的方法,由于我们采用的是ESP-01 WIFI模组,厂商安信可已经在固件中实现了ESPTouch和AirKiss的接收端程序,并且也提供了一个网页链接实现了ESPTouch和AirKiss的发送端程序网址如下:
http://wx.ai-thinker.com/api/old/wifi/config
在ESP-01 WIFI模组进入SMARTConfig模式时(通过串口发送AT+CWMODE=1,AT+CWSTARTSMART),通过上述链接的配网流程即可实现智能配网。由于微信小程序的合法域名只支持https://开头的域名所以http://wx.ai-thinker.com/api/old/wifi/config不能在小程序中直接打开,但是其可以添加在微信公众号的页面中,方便用户配网。