UNPKG

sparkplug-client

Version:

A client module for MQTT communication using the Sparkplug specification from Cirrus Link Solutions

452 lines 18.8 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.newClient = void 0; /** * Copyright (c) 2016-2017 Cirrus Link Solutions * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cirrus Link Solutions */ var mqtt = __importStar(require("mqtt")); var events_1 = __importDefault(require("events")); var sparkplug = __importStar(require("sparkplug-payload")); var pako_1 = __importDefault(require("pako")); var debug_1 = __importDefault(require("debug")); var sparkplugbpayload = sparkplug.get("spBv1.0"); var compressed = "SPBV1.0_COMPRESSED"; // setup logging var debugLog = (0, debug_1.default)('sparkplug-client:debug'); var infoLog = (0, debug_1.default)('sparkplug-client:info'); var logger = { debug: function (formatter) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } return debugLog.apply(void 0, __spreadArray([formatter], args, false)); }, info: function (formatter) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } return infoLog.apply(void 0, __spreadArray([formatter], args, false)); }, }; function getRequiredProperty(config, propName) { if (config[propName] !== undefined) { return config[propName]; } throw new Error("Missing required configuration property '" + propName + "'"); } function getProperty(config, propName, defaultValue) { if (config[propName] !== undefined) { return config[propName]; } else { return defaultValue; } } /* * Sparkplug Client */ var SparkplugClient = /** @class */ (function (_super) { __extends(SparkplugClient, _super); function SparkplugClient(config) { var _this = _super.call(this) || this; // Constants _this.type_int32 = 7; _this.type_boolean = 11; _this.type_string = 12; _this.versionB = "spBv1.0"; // MQTT Client Variables _this.bdSeq = 0; _this.seq = 0; _this.client = null; _this.connecting = false; _this.connected = false; _this.groupId = getRequiredProperty(config, "groupId"); _this.edgeNode = getRequiredProperty(config, "edgeNode"); _this.publishDeath = getProperty(config, "publishDeath", false); _this.version = getProperty(config, "version", _this.versionB); // Client connection options _this.serverUrl = getRequiredProperty(config, "serverUrl"); var username = getRequiredProperty(config, "username"); var password = getRequiredProperty(config, "password"); var clientId = getRequiredProperty(config, "clientId"); var keepalive = getProperty(config, "keepalive", 5); _this.mqttOptions = __assign(__assign({}, config.mqttOptions || {}), { // allow additional options clientId: clientId, clean: true, keepalive: keepalive, reschedulePings: false, connectTimeout: 30000, username: username, password: password, will: { topic: _this.version + "/" + _this.groupId + "/NDEATH/" + _this.edgeNode, payload: Buffer.from(_this.encodePayload(_this.getDeathPayload())), qos: 0, retain: false, } }); _this.init(); return _this; } // Increments a sequence number SparkplugClient.prototype.incrementSeqNum = function () { if (this.seq == 256) { this.seq = 0; } return this.seq++; }; SparkplugClient.prototype.encodePayload = function (payload) { return sparkplugbpayload.encodePayload(payload); }; ; SparkplugClient.prototype.decodePayload = function (payload) { return sparkplugbpayload.decodePayload(payload); }; SparkplugClient.prototype.addSeqNumber = function (payload) { payload.seq = this.incrementSeqNum(); }; // Get DEATH payload SparkplugClient.prototype.getDeathPayload = function () { return { "timestamp": new Date().getTime(), "metrics": [{ "name": "bdSeq", "value": this.bdSeq, "type": "uint64" }] }; }; // Publishes DEATH certificates for the edge node SparkplugClient.prototype.publishNDeath = function (client) { var payload, topic; // Publish DEATH certificate for edge node logger.info("Publishing Edge Node Death"); payload = this.getDeathPayload(); topic = this.version + "/" + this.groupId + "/NDEATH/" + this.edgeNode; client.publish(topic, Buffer.from(this.encodePayload(payload))); this.messageAlert("published", topic, payload); }; // Logs a message alert to the console SparkplugClient.prototype.messageAlert = function (alert, topic, payload) { logger.debug("Message " + alert); logger.debug(" topic: " + topic); logger.debug(" payload: " + JSON.stringify(payload)); }; SparkplugClient.prototype.compressPayload = function (payload, options) { var algorithm = null, compressedPayload, resultPayload = { "uuid": compressed, "metrics": [] }; logger.debug("Compressing payload " + JSON.stringify(options)); // See if any options have been set if (options !== undefined && options !== null) { // Check algorithm if (options['algorithm']) { algorithm = options['algorithm']; } } if (algorithm === null || algorithm.toUpperCase() === "DEFLATE") { logger.debug("Compressing with DEFLATE!"); resultPayload.body = pako_1.default.deflate(payload); } else if (algorithm.toUpperCase() === "GZIP") { logger.debug("Compressing with GZIP"); resultPayload.body = pako_1.default.gzip(payload); } else { throw new Error("Unknown or unsupported algorithm " + algorithm); } // Create and add the algorithm metric if is has been specified in the options if (algorithm !== null) { resultPayload.metrics = [{ "name": "algorithm", "value": algorithm.toUpperCase(), "type": "String" }]; } return resultPayload; }; SparkplugClient.prototype.decompressPayload = function (payload) { var metrics = payload.metrics || [], algorithm = null; var body = payload.body || new Uint8Array(); logger.debug("Decompressing payload"); var algorithmMetric = metrics.find(function (m) { return m.name === 'algorithm'; }); if (algorithmMetric && typeof algorithmMetric.value === 'string') { algorithm = algorithmMetric.value; } if (algorithm === null || algorithm.toUpperCase() === "DEFLATE") { logger.debug("Decompressing with DEFLATE!"); return pako_1.default.inflate(body); } else if (algorithm.toUpperCase() === "GZIP") { logger.debug("Decompressing with GZIP"); return pako_1.default.ungzip(body); } else { throw new Error("Unknown or unsupported algorithm " + algorithm); } }; SparkplugClient.prototype.maybeCompressPayload = function (payload, options) { if (options === null || options === void 0 ? void 0 : options.compress) { // Compress the payload return this.compressPayload(this.encodePayload(payload), options); } else { // Don't compress the payload return payload; } }; SparkplugClient.prototype.maybeDecompressPayload = function (payload) { if (payload.uuid !== undefined && payload.uuid === compressed) { // Decompress the payload return this.decodePayload(this.decompressPayload(payload)); } else { // The payload is not compressed return payload; } }; SparkplugClient.prototype.subscribeTopic = function (topic, options, callback) { if (options === void 0) { options = { "qos": 0 }; } logger.info("Subscribing to topic:", topic); this.client.subscribe(topic, options, callback); }; SparkplugClient.prototype.unsubscribeTopic = function (topic, options, callback) { logger.info("Unsubscribing topic:", topic); this.client.unsubscribe(topic, options, callback); }; // Publishes Node BIRTH certificates for the edge node SparkplugClient.prototype.publishNodeBirth = function (payload, options) { var topic = this.version + "/" + this.groupId + "/NBIRTH/" + this.edgeNode; // Reset sequence number this.seq = 0; // Add seq number this.addSeqNumber(payload); // Add bdSeq number var metrics = payload.metrics; if (metrics !== undefined && metrics !== null) { metrics.push({ "name": "bdSeq", "type": "UInt64", "value": this.bdSeq }); } // Publish BIRTH certificate for edge node logger.info("Publishing Edge Node Birth"); var p = this.maybeCompressPayload(payload, options); this.client.publish(topic, Buffer.from(this.encodePayload(p))); this.messageAlert("published", topic, p); }; // Publishes Node Data messages for the edge node SparkplugClient.prototype.publishNodeData = function (payload, options) { var topic = this.version + "/" + this.groupId + "/NDATA/" + this.edgeNode; // Add seq number this.addSeqNumber(payload); // Publish logger.info("Publishing NDATA"); this.client.publish(topic, Buffer.from(this.encodePayload(this.maybeCompressPayload(payload, options)))); this.messageAlert("published", topic, payload); }; // Publishes Node BIRTH certificates for the edge node SparkplugClient.prototype.publishDeviceData = function (deviceId, payload, options) { var topic = this.version + "/" + this.groupId + "/DDATA/" + this.edgeNode + "/" + deviceId; // Add seq number this.addSeqNumber(payload); // Publish logger.info("Publishing DDATA for device " + deviceId); this.client.publish(topic, Buffer.from(this.encodePayload(this.maybeCompressPayload(payload, options)))); this.messageAlert("published", topic, payload); }; ; // Publishes Node BIRTH certificates for the edge node SparkplugClient.prototype.publishDeviceBirth = function (deviceId, payload, options) { var topic = this.version + "/" + this.groupId + "/DBIRTH/" + this.edgeNode + "/" + deviceId; // Add seq number this.addSeqNumber(payload); // Publish logger.info("Publishing DBIRTH for device " + deviceId); var p = this.maybeCompressPayload(payload, options); this.client.publish(topic, Buffer.from(this.encodePayload(p))); this.messageAlert("published", topic, p); }; // Publishes Node BIRTH certificates for the edge node SparkplugClient.prototype.publishDeviceDeath = function (deviceId, payload) { var topic = this.version + "/" + this.groupId + "/DDEATH/" + this.edgeNode + "/" + deviceId, options = {}; // Add seq number this.addSeqNumber(payload); // Publish logger.info("Publishing DDEATH for device " + deviceId); this.client.publish(topic, Buffer.from(this.encodePayload(this.maybeCompressPayload(payload, options)))); this.messageAlert("published", topic, payload); }; SparkplugClient.prototype.stop = function () { logger.debug("publishDeath: " + this.publishDeath); if (this.publishDeath) { // Publish the DEATH certificate this.publishNDeath(this.client); } this.client.end(); }; // Configures and connects the client SparkplugClient.prototype.init = function () { var _this = this; // Connect to the MQTT server this.connecting = true; logger.debug("Attempting to connect: " + this.serverUrl); logger.debug(" options: " + JSON.stringify(this.mqttOptions)); this.client = mqtt.connect(this.serverUrl, this.mqttOptions); logger.debug("Finished attempting to connect"); /* * 'connect' handler */ this.client.on('connect', function () { logger.info("Client has connected"); _this.connecting = false; _this.connected = true; _this.emit("connect"); // Subscribe to control/command messages for both the edge node and the attached devices logger.info("Subscribing to control/command messages for both the edge node and the attached devices"); _this.client.subscribe(_this.version + "/" + _this.groupId + "/NCMD/" + _this.edgeNode + "/#", { "qos": 0 }); _this.client.subscribe(_this.version + "/" + _this.groupId + "/DCMD/" + _this.edgeNode + "/#", { "qos": 0 }); // Emit the "birth" event to notify the application to send a births _this.emit("birth"); }); /* * 'error' handler */ this.client.on('error', function (error) { if (_this.connecting) { _this.emit("error", error); _this.client.end(); } }); /* * 'close' handler */ this.client.on('close', function () { if (_this.connected) { _this.connected = false; _this.emit("close"); } }); /* * 'reconnect' handler */ this.client.on("reconnect", function () { _this.emit("reconnect"); }); /* * 'reconnect' handler */ this.client.on("offline", function () { _this.emit("offline"); }); /* * 'packetsend' handler */ this.client.on("packetsend", function (packet) { logger.debug("packetsend: " + packet.cmd); }); /* * 'packetreceive' handler */ this.client.on("packetreceive", function (packet) { logger.debug("packetreceive: " + packet.cmd); }); /* * 'message' handler */ this.client.on('message', function (topic, message) { var payload = _this.maybeDecompressPayload(_this.decodePayload(message)), timestamp = payload.timestamp, splitTopic, metrics; _this.messageAlert("arrived", topic, payload); // Split the topic up into tokens splitTopic = topic.split("/"); if (splitTopic[0] === _this.version && splitTopic[1] === _this.groupId && splitTopic[2] === "NCMD" && splitTopic[3] === _this.edgeNode) { // Emit the "command" event _this.emit("ncmd", payload); } else if (splitTopic[0] === _this.version && splitTopic[1] === _this.groupId && splitTopic[2] === "DCMD" && splitTopic[3] === _this.edgeNode) { // Emit the "command" event for the given deviceId _this.emit("dcmd", splitTopic[4], payload); } else { _this.emit("message", topic, payload); } }); }; return SparkplugClient; }(events_1.default.EventEmitter)); function newClient(config) { return new SparkplugClient(config); } exports.newClient = newClient; //# sourceMappingURL=index.js.map