UNPKG

mm_os

Version:

这是超级美眉服务端框架,用于快速构建应用程序。

567 lines (530 loc) 12.6 kB
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;