前面已经简单的实现使用自己建的物联网平台用微信小程序控制STM32,但是还有很多不完善的地方,后来又开始试着使用阿里云物联网平台,所以决定做一个集成自己建的物联网平台和阿里云物联网平台的设备管理平台,这个微信小程序具有用户注册登录,对设备的添加、删除,对设备操作的添加,控制和获取设备数据。
首页负责对私有云设备和阿里云设备的管理,在登录并且已经绑定了云设备的情况下,点击添加按钮弹出输入框,输入对应的设备信息,即可添加设备,暂时还没有校验设备信息是否正确的部分,如果进入这个添加好的设备的操作页面,与真实设备的状态不一致,那表示添加的设备信息不正确。
此页面用来用户的注册和登录,在用户没有注册的情况下点击登录/注册会出现微信用户授权,授权之后自动登录,用户的微信昵称和头像就可以显示出来了,在初次注册的用户,还没有绑定云设备,在点击用户 头像可以进入用户信息页面,在那里面可以绑定EMQ物联网平台云设备,和阿里云物联网平台云设备。只有在绑定且登录之后才可以添加控制设备。
在首页中点击显示的设备然后进入设备操作页面,暂时控制类型有switch和data两个,一个是开关量,一个是显示数据量,点击添加操作按钮弹出添加操作对话框,然后输入要添加的操作,添加操作。如果设备不在线,添加的开关量将不能操作,data数据也不会显示出来。
这个页面做的比较简单,只是简单的添加几个按钮,点击绑定可以绑定对应的云平台设备,绑定了云设备的情况下,才能添加控制设备。如果已经绑定了云设备,则会提醒已经添加云设备,是否修改,选择确定之后可以对已经绑定的设备进行修改,不过这里也没有对设备信息校验的功能。点击注销,可以注销用户登录态,在下次进入页面时,就不会自动登录,否则只要令牌不过期都会自动登录。
此部分主要是调用api操作数据库,实现用户认证和设备的管理。token是一个用户的标识,用户每次调用api时都必须携带token,服务器才能识别用户身份,然后操作相应的数据库。
module.exports={
baseUrl :'https://www.domain.com/***.php/***/', //填写api基地址
token:'',
islogin:0,
priDeviceNum:0,
aliDeviceNum:0,
/**
* 获取token并存入缓存
*/
getToken:function(e) {
var that=this;
wx.login({
success(res) {
//console.log(res.code);
wx.request({
url: that.baseUrl + 'gettoken',
data: {
code: res.code
},
method: 'POST',
success: function (res) {
//wx.removeStorageSync('token');
wx.setStorageSync('token',res.data);
//console.log('token:'+res.data);
},
})
}
})
},
/*
使用获取到的令牌获取数据库中是否注销
*/
isLogOut:function(callback) {
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl + 'islogout',{
token: that.token
}, callback);
},
/**
* 检测本设备是否绑定了私有云端设备
*/
isBindPriCloud: function(callback) {
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl + 'isbindpricloud',{
token: that.token
}, callback)
},
/**
* 检测本设备是否绑定了阿里云端设备
*/
isBindAliCloud: function (callback) {
var that = this;
that.verifyToken();
that.myRequest(that.baseUrl + 'isbindalicloud',{
token: that.token
}, callback)
},
/**
* 绑定私有云端设备
*
*/
bindPriCloud: function(paras,callback) {
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl + 'bindpricloud', {
token: that.token,
clientid: paras.clientId,
username: paras.username,
password: paras.password,
uptopic: paras.upTopic
}, function(res){
callback(res);
})
},
/**
* 绑定阿里云设备
*/
bindAliCloud:function(paras,callback) {
var that = this;
that.verifyToken();
that.myRequest(that.baseUrl + 'bindalicloud',{
token: that.token,
productkey: paras.productKey,
devicename: paras.deviceName,
devicesecret: paras.deviceSecret,
uptopic: paras.upTopic
}, function (res) {
callback(res);
})
},
/**
* 添加私有云设备
*/
addPrivateDevices:function(paras,callback) {
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl + 'addpridevice',{
token:that.token,
clientid: paras.clientid,
username: paras.username,
password: paras.password,
uptopic: paras.uptopic,
downtopic: paras.downtopic
}, function (res) {
callback(res)
})
},
/**
* 添加阿里云设备
*/
addAliyunDevices:function(paras,callback) {
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl + 'addalidevice', {
token: that.token,
productkey: paras.productkey,
devicename: paras.devicename,
devicesecret: paras.devicesecret,
uptopic: paras.uptopic,
downtopic: paras.downtopic
}, callback)
},
/**
* 登录将已注销字段改成未注销,设置头像和昵称
*/
login:function(callback) {
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl + 'login',{
token : that.token
}, callback);
},
/**
* 注销将登录字段改为已注销
*/
logout:function(callback){
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl + 'logout',{
token: that.token
},callback);
},
/**
* 获取总设备数
*/
getDeviceNum:function(callback){
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl + 'getpridevices', { token: that.token }, function(res){
that.priDeviceNum = res.data.length;
})
that.myRequest(that.baseUrl + 'getAlidevices', { token: that.token }, function(res){
that.aliDeviceNum=res.data.length;
callback();
})
},
/**
* 封装请求接口
*/
myRequest:function(myUrl,paras, callback) {
wx.request({
url: myUrl,
data: paras,
method: 'POST',
header:{
'content-type': 'application/json'
},
success: function (res) {
callback(res)
}
})
},
/**
* 验证token是否有效
*/
verifyToken:function(){
var that=this;
if(that.token==''){
that.token = wx.getStorageSync('token');
}
that.myRequest(that.baseUrl+'verifytoken',{
token:that.token},function(res){
if ((res.data == 1)){
console.log(res)
}else{
//console.log(res);
that.getToken();
that.token = '';
that.verifyToken();
}
})
},
/**
* 获取私有云设备
*/
getPriDevices:function(callback){
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl +'getpridevices',{token:that.token},callback)
},
/**
* 获取阿里云设备
*/
getAliDevices: function (callback) {
var that = this;
that.verifyToken();
that.myRequest(that.baseUrl + 'getAlidevices', { token: that.token }, callback)
},
/**
* 删除私有云设备
*/
deletePriDevice:function(clientId,callback){
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl+'deletepridevice',{
token:that.token,
clientid:clientId
},callback)
},
/**
* 删除阿里云设备
*/
deleteAliDevice:function(deviceName,callback){
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl+'deletealidevice',{
token:that.token,
devicename:deviceName
},callback)
},
/**
* 添加操作
*/
addAction:function(mydid,mydtype,mytype,mywhich,myalias,callback){
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl +'adddeviceaction',{
token:that.token,
deviceid:mydid,
devicetype:mydtype,
type:mytype,
which:mywhich,
alias:myalias
},callback);
},
/**
* 获取操作
*/
getActions: function (mydevicetype, mydeviceid,callback){
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl+'getdeviceaction',{
token:that.token,
dtype: mydevicetype,
did:mydeviceid,
},callback);
},
/**
* 获取绑定的私有云设备
*/
getBindPriDevice:function(callback){
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl+'getbindpridevice',{
token:that.token
},callback);
},
/**
* 获取绑定的阿里云设备
*/
getBindAliDevice:function(callback){
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl+'getbindalidevice',{
token:that.token
},callback)
},
/**
* 获取控制设备的下行主题
*/
getPriDownTopic:function(mydeviceid,callback){
var that=this;
that.verifyToken();
that.myRequest(that.baseUrl +'getdowntopic',{
token:that.token,
deviceid:mydeviceid
},callback)
}
}
import mqtt from "mqtt.js" //引入mqtt库文件
var client=null;
module.exports = {
host:'wxs://www.domain.com/mqtt',
//client: null,
//记录重连的次数
reconnectCounts: 0,
options: {
protocolVersion: 4, //MQTT连接协议版本
clientId: '',
clean: true,
password: '',
username: '',
reconnectPeriod: 1000, //1000毫秒,两次重新连接之间的间隔
connectTimeout: 30 * 1000, //1000毫秒,两次重新连接之间的间隔
resubscribe: true, //如果连接断开并重新连接,则会再次自动订阅已订阅的主题(默认true)
reconnect: true,
},
/**
* 连接私有云设备
*/
ConnectPriCloud: function (options,callback) {
var that = this;
that.options.clientId=options.clientid,
that.options.username = options.username,
that.options.password = options.password,
//开始连接
client = mqtt.connect(that.host, that.options);
client.on("connect", function (connack) {
client.subscribe(options.uptopic, function (err, granted) {
callback(err,granted); //连接成功执行回调函数
})
});
},
/**
* 获取控制设备数据
*/
getData:function(desTopic,desDevice,which,callback){
client.publish(desTopic, '{"src":"wx","des":"' + desDevice + '","msg":{"type":"ask","which":"' + which +'","content":"?"}}');
client.on("message",function(topic,payload){
callback(topic,payload)
})
},
/**
* 询问设备是否在线
*/
askDevice: function (desTopic,which, callback) {
client.publish(desTopic, '{"src":"wx","des":"'+which+'","msg":{"type":"ask","which":"'+which+'","content":"?"}}');
client.on('message', function (topic, payload){
callback(topic, payload)
})
},
//开关量控制
openOrClose: function (desTopic,desDevice,which,action){
client.publish(desTopic, '{"src":"wx","des":"' + desDevice + '","msg":{"type":"ctrl","which":"' + which + '","content":"' + action+'"}}');
}
}
关于阿里云物联网平台设备管理,参照阿里云物联网平台开发文档中基于规则引擎的M2M设备间通信,因为阿里云物联网平台,云下设备发布消息到另一个云下设备,只能通过发布到自己云端设备主题中,然后通过规则引擎转发到规则引擎定义的主题中,所以要实现微信小程序这一个云下设备对其它云下设备的通信,就要把目标设备的设备信息储存在数据库中,当用户认证成功后,可以获取目标设备信息,然后根据设备信息,发送一定格式的json数据,通过规则引擎获取目标设备名,将数据转发到想要转发的目标设备中。
所以我发布json数据是:’{“src”:“wx”,“des”:"’ + desDevice + ‘",“msg”:{“type”:“ask”,“which”:"’ + which + ‘",“content”:"?"}}’
关于连接阿里云物联网平台的库文件,GitHub上可以找到。
import iot from 'alibabacloud-iot-device-sdk.min.js'; //引入库文件
var device=null;
var desTopic=null;
module.exports={
device:null,
options:{
productKey: '******', //必须是socket合法列表中有才不会报错
deviceName: '******',
deviceSecret: '*******************',
// 支付宝小程序和微信小程序额外需要配置协议参数
"protocol": 'alis://',
"protocol": 'wxs://',
},
/**
* 连接阿里云设备
*/
ConnectAliCloud:function(options,callback){
var that=this;
that.options.productKey=options.productkey;
that.options.deviceName=options.devicename;
that.options.deviceSecret=options.devicesecret;
device=iot.device(that.options);
device.on("connect", () => {callback()})
device.subscribe(options.uptopic);
desTopic = '/' + options.productkey + '/' + options.devicename +'/user/download';
},
/**
* 获取控制设备数据
*/
getData:function(desDevice,which,callback){
var that=this;
device.publish(desTopic, '{"src":"wx","des":"' + desDevice + '","msg":{"type":"ask","which":"' + which + '","content":"?"}}');
device.on("message",(topic,payload)=>{
callback(topic,payload)
})
},
/**
* 询问控制设备是否在线
*/
askDevice:function(desDevice,which,callback){
device.publish(desTopic, '{"src":"wx","des":"' + desDevice + '","msg":{"type":"ask","which":"' + which + '","content":"?"}}');
device.on('message',(topic,payload)=>{
callback(topic, payload)
})
},
/**
* 开关量控制
*/
openOrClose: function (desTopic, desDevice, which, action) {
device.publish(desTopic, '{"src":"wx","des":"' + desDevice + '","msg":{"type":"ctrl","which":"' + which + '","content":"' + action + '"}}');
}
}
还有其它页面的代码,太多了就不全部贴上来了,完整代码见文末。
const app = getApp();
const token = require('../../utils/Token');
Page({
data: {
isShowPriConfirm: false,
isShowAliConfirm: false,
nums:0,
num:0,
radio:0,
child: [],
devicenums:0,
devicenum:0,
devices:[]
},
/**
* 根据radio显示出不同的页面
*/
radioChange: function (e) {
var that=this;
that.setData({
nums: 0,
num: 0,
radio: 0,
child: [],
devicenums: 0,
devicenum: 0,
devices: []
})
if (token.islogin == 1) { //在登录的情况下获取用户的设备
if (e.detail.value == "private") { //切换显示私有云设备和阿里云设备
that.setData({
radio: 0
});
token.getPriDevices(function (res) { //调用接口获取数据库储存的设备
//根据获取的设备计算出设备数,在页面中加载出来
that.setData({
nums: parseInt(res.data.length / 3),
num: res.data.length % 3,
child: res.data
})
});
} else {
that.setData({
radio: 1
});
token.getAliDevices(function (res) {
console.log(res);
that.setData({
devicenums: parseInt(res.data.length / 3), //计算设备数量以便在页面中显示出来
devicenum: res.data.length % 3,
devices: res.data
})
});
}
}
},
onLoad:function(){
},
/**
* 添加私有云设备
*/
addPriDevice:function(){
var that=this;
if(token.islogin==1){ //判断是否已经登录
token.isBindPriCloud(function (res) { //是否绑定了云设备
if (res.data.myclientid != "") {
that.setData({
isShowPriConfirm: true
})
} else {
wx.showToast({
title: '请先绑定私有云设备',
image:'../../images/toast.png'
})
}
})
}else{
wx.showToast({
title: '请先登录',
image: '../../images/toast.png'
})
}
},
/**
* 添加阿里云设备
*/
addAliDevice: function () {
var that = this;
if(token.islogin==1){
token.isBindAliCloud(function (res) {
if (res.data.myprikey != "") {
that.setData({
isShowAliConfirm: true
})
} else {
wx.showToast({
title: '请先绑定阿里云设备',
image: '../../images/toast.png'
})
}
})
}else{
wx.showToast({
title: '请先登录',
image: '../../images/toast.png'
})
}
},
/**
* 弹出框取消按钮事件,关闭弹出框
*/
cancel: function () {
var that = this
that.setData({
isShowAliConfirm: false,
isShowPriConfirm: false,
})
},
/**
* 添加私有云设备弹出框,确认按钮事件
*/
PriConfirm: function (e) {
var that = this;
token.addPrivateDevices(e.detail.value,function(res){ //调用接口添加设备
console.log(res);
})
that.setData({
isShowPriConfirm: false,
isShowAliConfirm: false,
})
},
/**
* 添加阿里云设备弹出框,确认按钮事件
*/
AliConfirm: function (e) {
var that = this;
token.addAliyunDevices(e.detail.value, function (res) { //调用接口添加设备
console.log(res);
})
that.setData({
isShowPriConfirm: false,
isShowAliConfirm: false,
})
},
/**
* 设备按钮长按事件,长按删除
*/
prilongtap:function(e){
wx.showModal({
title: '提示',
content: '确认要删除' + e.currentTarget.id+'?',
success(res) {
if (res.confirm) {
token.deletePriDevice(e.currentTarget.id, function (res) {
wx.showToast({
title: '成功删除' + e.currentTarget.id,
})
})
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
},
/**
* 设备按钮长按事件,长按删除
*/
alilongtap: function (e) {
wx.showModal({
title: '提示',
content: '确认要删除'+e.currentTarget.id+'?',
success(res){
if(res.confirm){
token.deleteAliDevice(e.currentTarget.id, function (res) {
wx.showToast({
title: '成功删除' +e.currentTarget.id,
})
})
}else if(res.cancel){
console.log('用户点击取消')
}
}
})
},
/**
* 点击设备进入设备操作页面
*/
goToDevice:function(e){
var that=this;
console.log(e);
wx.navigateTo({
url: '../device/device',
success:function(res){
res.eventChannel.emit('getId', { id: e.currentTarget.id, radioitem: that.data.radio}) //向device页面传递点击的设备名和设备类别
}
})
}
})
服务端主要功能是用于用户的登录注册,用户设备的添加、删除,添加设备操作。服务端代码使用ThinkPHP5写的。使用JWT实现对用户认证,然后根据不同的接口需求对接数据库,返回用户需要的数据。
服务器代码主要在于用户的认证,要与微信小程序的登录机制相对接,下面是微信公众平台的图,具体的登录流程可以在微信公众平台上看到。
namespace app\api\service;
use app\api\model\User;
use app\lib\exception\WeChatException;
use think\facade\Cache;
use think\Exception;
use app\api\model\User as UserModel;
use think\session\driver\Redis;
use Firebase\JWT\JWT;
class UserToken
{
protected $code;
protected $wxAppId;
protected $wxAppSecret;
protected $wxLoginUrl;
function __construct($code)
{
$this->code=$code; //获取用户发过来的code码
$this->wxAppId=config('wx.app_id'); //自己的appid
$this->wxAppSecret=config('wx.app_secret'); //自己的appsecret
$this->wxLoginUrl=sprintf(config('wx.login_url'),$this->wxAppId,$this->wxAppSecret,$this->code); //拼接出请求地址
}
//向微信服务器发送请求获取openid
public function get(){
$result=curl_get($this->wxLoginUrl); //发送请求
$wxResult=json_decode($result,true);
if(empty($wxResult)){
throw new Exception('获取session_key异常');
}
else{
$loginFail=array_key_exists('errcode',$wxResult); //检验请求的数据是否正确
if($loginFail){
$this->processLogError($wxResult);
}
else{
return $this->grantToken($wxResult);
}
}
}
//生成jwt,保存用户id
private function grantjwt($uid){
$key="token";
$token=[
"iss"=>"",
"aud"=>"",
"iat"=>time(),
"nbf"=>time()+1,
"exp"=>time()+7200,
"uid"=>$uid
];
$jwt=JWT::encode($token,$key,"HS256");
return $jwt;
}
//检验用户openid,生成jwt
private function grantToken($wxResult){
$openid=$wxResult['openid']; //获取请求结果中的openid
$user=UserModel::getByOpenID($openid); //通过openid查找数据中的用户
if($user){
$uid=$user->id;
}
else{
$uid=$this->newUser($openid); //用户不存在创建用户
}
$token=self::grantjwt($uid);
return $token;
}
//创建用户
private function newUser($openid){
$user=UserModel::create([
'openid'=>$openid
]);
return $user->id;
}
private function processLogError($wxResult){
throw new WeChatException([
'msg'=>$wxResult['errmsg'],
'errCode'=>$wxResult['errcode']
]);
}
//校验token是否正确,并获取储存的uid
public static function checkjwt($token){
$key="token";
$info=JWT::decode($token,$key,["HS256"]);
return $info->uid;
}
public static function getUid($token){
if($token==''){
throw new ParameterException([
'Token不允许为空'
]);
}
$uid=UserToken::checkjwt($token);
return $uid;
}
}
本项目包含了微信小程序设计,后端代码设计。微信小程序包含:微信小程序的登录接口的使用,UI设计以及弹出框设计,缓存的使用,网络请求的使用。后端代码包含:JWT用户认证,数据库操作,路由设置。但是这个项目还是有很多缺陷,比如后端代码中没有设置验证规则,因为微信小程序发起的请求都是正确的,所以就没有设置,但是一个优秀的接口,这个是必不可少的,其次也没有严格按照标准的接口规则来做,主要只是为了实现其功能。但是基本上实现了预定的功能,满足我管理物联网设备的需求。
后端代码:https://github.com/zhou1217/Iot-Server
前端代码:https://github.com/zhou1217/Iot-MiniProgram
个人能力有限,有什么错误的地方欢迎指正,有问题也可以提,可以一起探讨