mm_os
Version:
这是超级美眉服务端框架,用于快速构建应用程序。
567 lines (530 loc) • 12.6 kB
JavaScript
const MQTT_client = require('mqtt');
const Index = require('mm_machine').Index;
const Drive = require('./drive');
/**
* MQTT通讯类
* @extends {Index}
* @class
*/
class MQTT extends Index {
/**
* 构造函数
* @param {Object} scope 作用域
* @param {String} title 标题
* @constructor
*/
constructor(scope, title) {
super(scope, __dirname);
this.Drive = Drive;
// 更新并重载脚本
this.mode = 3;
this.type = "mqtt";
this.title = title;
this.dict = {};
this.config = {
hostname: "127.0.0.1",
port: "1883",
protocol: "mqtt",
clientId: "iot_test",
subscribe_qos: 1,
publish_qos: 1,
username: "iot_test",
password: "asd123",
table: "iot_device",
clean: false,
longtime: 15000,
requestTimeout: 3000,
// 在线有效期时长判断 单位:毫秒,150000毫秒为2.5分钟
online_expires: 150000,
// 在线有效期检测间隔
online_interval: 10000,
// 连接超时
connectTimeout: 30000,
// maxInflight: 20,
// 重连间隔
reconnectPeriod: 6000 // 重连间隔1秒
}
// 定时器
this.timer = null;
// mqtt客户端服务器
this.client = null;
/**
* message queue
*/
this.list_msg = [
/*
{
// information ID
id: "",
// request method
method: "",
// message parameters
params: {},
// Callback
func: function(res){}
}
*/
];
/**
* 订阅集合 (主题 => 函数列表)
*/
this.dict_subscribe = {};
/**
* method collection
*/
this.methods = {
/**
* Provide to the server to see how many open functions there are
*/
get_method: function() {
return Object.keys(_this.methods)
}
};
this.retryTimes = 0;
this.connecting = false;
}
}
/**
* 设置配置
* @param {Object} config 配置
*/
MQTT.prototype.set_config = function(config) {
Object.assign(this.config, config);
}
/**
* 主题匹配
* @param {String} topic 接收到的主题
* @param {String} top 匹配用的主题
* @@return {Boolean} 返回匹配结果,正确返回true,错误返回false
*/
MQTT.prototype.match = function(topic, top) {
if (topic === top) {
return true;
}
var str = "^" + top.replace("#", ".*").replace("+", "~~~").replace("~~~", "[a-zA-Z0-9_-]+").replace("$", "~~~")
.replace("~~~", "\\$");
var s = str.substring(str.length - 1, str.length);
if (s !== "#") {
str += "$";
}
var mh = new RegExp(str);
return mh.test(topic);
}
/**
* 初始化
*/
MQTT.prototype.update_after = async function() {
var _this = this;
var list = this.list;
for (var i = 0, o; o = list[i++];) {
if (o.config.state === 1) {
o.send = function(topic, msg, qos = 0, retain = false) {
if (topic) {
_this.send(topic, msg, qos, retain);
}
};
await o.exec('init');
var topics = o.config.topic;
if (topics) {
for (var i = 0; i < topics.length; i++) {
var tc = topics[i];
var retain = o.config.retain || false;
var qos = o.config.qos || 0;
if (tc.indexOf("heartbeat") !== -1) {
retain = false;
qos = 0;
}
this.subscribe(tc, null, qos, retain);
}
}
}
}
}
/**
* 重新连接
*/
MQTT.prototype.reconnect = function() {
// $.sleep(this.config.interval);
// this.retryTimes += 1;
// if (this.retryTimes > 5) {
// try {
// this.client.end();
// this.init();
// } catch (error) {
// this.$message.error(error.toString());
// }
// }
}
/**
* 初始化
* @param {Object} config 配置参数
*/
MQTT.prototype.init = function(config) {
this.config = Object.assign(this.config, config);
this.retryTimes = 0;
this.connecting = false;
};
/**
* 运行MQTT
* @param {Object} config 配置参数
*/
MQTT.prototype.start = function() {
setTimeout(() => {
this.start_timer();
}, 2000)
this.connecting = true;
var err;
try {
var cg = this.config;
if (cg.host) {
delete cg.hostname;
delete cg.port;
this.client = MQTT_client.connect(cg.host, cg);
} else {
this.client = MQTT_client.connect(cg);
}
if (this.client && this.client.on) {
return new Promise((resolve, reject) => {
this.client.on('connect', (packet, err) => {
this.connecting = false;
if (err) {
resolve(null);
reject(err);
} else {
this.client.on('message', async (topic, message) => {
await this.receive(topic, message.toString());
});
resolve(packet);
}
});
this.client.on("reconnect", () => {
this.reconnect();
});
this.client.on("error", (error) => {
console.error("Connection failed", error);
});
});
}
} catch (error) {
this.connecting = false;
err = error;
}
return new Promise((resolve, reject) => {
resolve(null);
reject(err);
});
};
/**
* 监听事件
* @param {String} topic 事件名称
* @param {Function} func 回调函数
*/
MQTT.prototype.on = function(topic, func) {
this.client.on(topic, func);
}
/**
* 接收消息
* @param {String} topic 订阅板块
* @param {Object} msg 消息主体
*/
MQTT.prototype.message = async function(topic, msg) {
if (this.dict_subscribe[topic]) {
var list = this.dict_subscribe[topic];
try {
for (var key in list) {
list[key](msg);
}
} catch (err) {
console.error(err);
}
}
}
/**
* 接收消息
* @param {String} push_topic 推送过来的主题
* @param {Object} msg 消息主体
*/
MQTT.prototype.receive = async function(push_topic, msg) {
if (msg && (msg.indexOf('[') === 0) || msg.indexOf('{') === 0) {
try {
msg = JSON.parse(msg);
} catch {}
}
var ret;
var list = this.list;
for (var i = 0, o; o = list[i++];) {
if (o.config.state === 1) {
var topics = o.config.topic;
if (topics) {
for (var i = 0; i < topics.length; i++) {
var topic = topics[i];
if (this.match(push_topic, topic)) {
ret = await o.exec('handle', push_topic, msg, topic, i);
}
}
}
}
}
try {
await this.message(topic, msg);
} catch (error) {
$.log.error("mqtt处理程序错误", error);
return
}
};
/**
* 订阅
* @param {String} topic 主题
* @param {Function} func 回调函数
* @param {Number} qos 推送方式 0
* @param {Boolean} retain 保持
*/
MQTT.prototype.subscribe = function(topic, func, qos = null, retain = false) {
if (!this.client) {
return;
}
if (qos === null || qos === undefined) {
qos = this.config.subscribe_qos || 0;
}
this.client.subscribe(topic, {
// 订阅消息方式,0为保留 , 1为确认收到1次
qos,
retain
});
if (func) {
if (!this.dict_subscribe[topic]) {
this.dict_subscribe[topic] = {};
}
var key = this.key_num + 1;
this.dict_subscribe[topic][key] = func;
return key;
}
}
/**
* 取消订阅
* @param {String} topic 主题
* @param {String} key 主键
*/
MQTT.prototype.unsubscribe = function(topic, key) {
this.client.unsubscribe(topic);
if (this.dict_subscribe[topic]) {
delete this.dict_subscribe[topic][key];
}
}
/**
* 结束客户端
*/
MQTT.prototype.end = function() {
if (this.client) {
this.client.end();
}
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
};
/**
* 发布主题
* @param {String} topic 主题
* @param {Object} message 消息对象
* @param {Number} qos 推送方式 0
* @param {Boolean} retain 保持
*/
MQTT.prototype.publish = function(topic, message, qos = null, retain = false) {
if (qos === null || qos === undefined) {
qos = this.config.publish_qos || 0;
}
// console.log("发布", qos, topic);
this.client.publish(topic, message, {
qos,
retain
});
};
/**
* 主题推送
* @param {String} topic 主题
* @param {Object} msgObj 消息对象
* @param {Number} qos 推送方式 0
* @param {Boolean} retain 保持
*/
MQTT.prototype.send = function(topic, msgObj, qos = null, retain = false) {
this.publish(topic, JSON.stringify(msgObj), qos, retain);
};
/**
* 请求
* @param {String} topic 主题
* @param {String} method 请求方法
* @param {Object} params 请求参数
* @param {Function} func 回调函数
*/
MQTT.prototype.req = function(topic, method, params, func) {
var data = {
id: this.client.options.clientId + "_" + Date.parse(new Date()),
method,
params
};
if (func) {
data.func = func;
this.list_msg.push(data);
}
this.send(topic, data);
};
/**
* 同步请求 - 可及时取回消息
* @param {String} method 请求方法
* @param {Object} params 传递参数
* @returns {Object} 返回响应结果
*/
MQTT.prototype.reqASync = function(topic, method, params) {
var _this = this;
return new Promise((resolve, reject) => {
var hasMsg;
_this.req(topic, method, params, (res) => {
hasMsg = true;
resolve(res);
});
setTimeout(function() {
if (!hasMsg) {
resolve(null);
reject("request timeout!");
}
}, _this.config.requestTimeout);
});
};
/**
* 保存在线到数据库
* @param {Array} arr 客户端ID数组
* @param {Number} online 在线情况 0为不在线,1为在线
*/
MQTT.prototype.save_online = async function(arr, online = 1) {
if (!arr || !arr.length) {
return
}
var body = {
online
}
var db = $.sql.db();
db.table = this.config.table || "iot_device";
db.size = 0;
var query = {};
if (arr) {
query.clientid_has = arr.join(",");
}
await db.set(query, body);
}
/**
* 获取所有设备
*/
MQTT.prototype.get_drives = async function() {
var db = $.sql.db();
db.table = this.config.table || "iot_device";
db.size = 0;
return await db.get({}, "", "clientid, online");
}
/**
* 更新在线设备
*/
MQTT.prototype.update_online = async function() {
// 获取所有方案
var list = this.list;
var list_online = [];
var list_offline = [];
var list_has = [];
var now = new Date().getTime();
var online_expires = this.config.online_expires; // 180
// 获取所有设备
var drives = await this.get_drives();
for (var i = 0; i < list.length; i++) {
var dict = list[i].drives;
for (var clientid in dict) {
list_has.push(clientid);
var drive = drives.getObj({
clientid
});
var o = dict[clientid];
if (o.online === 1) {
if (o.time_last) {
var cha = now - o.time_last;
if (cha > online_expires) {
if (drive && drive.online) {
// 如果设备本身在线,需要改为离线
list_offline.push(clientid);
}
o.online = 0;
delete dict[clientid];
} else {
if (drive && !drive.online) {
// 如果设备本身离线,需要改为在线
list_online.push(clientid);
}
}
}
} else {
if (drive && drive.online) {
// 如果设备本身在线,需要改为离线
list_offline.push(clientid);
}
}
}
}
// 如果设备没有出现过,则视为离线
for (var i = 0; i < drives.length; i++) {
var {
clientid,
online
} = drives[i];
if (online && list_has.indexOf(clientid) === -1) {
list_offline.push(clientid);
}
}
// console.log("在线设备变化", list_online);
// console.log("离线设备变化", list_offline);
// 先将设备离线
var list_off = list_offline.to2D(30);
for (var i = 0; i < list_off.length; i++) {
await this.save_online(list_off[i], 0);
}
// 再将设备在线
var list_on = list_online.to2D(30);
for (var i = 0; i < list_on.length; i++) {
await this.save_online(list_on[i], 1);
}
}
/**
* 开始计时器
*/
MQTT.prototype.start_timer = function() {
if (!this.timer) {
this.timer = setInterval(() => {
try {
this.update_online();
} catch (err) {
}
}, this.config.online_interval);
}
}
module.exports = MQTT;
/**
* 创建全局管理器
*/
if (!$.pool.mqtt) {
$.pool.mqtt = {};
}
function mqtt_admin(scope, title) {
if (!scope) {
scope = $.val.scope + '';
}
var obj = $.pool.mqtt[scope];
if (!obj) {
$.pool.mqtt[scope] = new MQTT(scope, title);
obj = $.pool.mqtt[scope];
}
return obj;
}
/**
* mqtt管理器, 用于管理插件
* @param {string} scope 作用域
* @param {string} title 标题
* @return {Object} 返回一个缓存类
*/
$.mqtt_admin = mqtt_admin;