@uns-kit/core
Version:
Core utilities and runtime building blocks for UNS-based realtime transformers.
246 lines • 11.6 kB
JavaScript
import mqtt from "mqtt";
import logger from "../logger.js";
import { UnsEventEmitter } from "../uns/uns-event-emitter.js";
import { DataSizeMeasurements, PhysicalMeasurements } from "../uns/uns-measurements.js";
export default class MqttProxy {
event = new UnsEventEmitter();
statusTopic;
instanceName;
mqttHost;
mqttSubToTopics;
mqttSSL;
mqttClient;
startDate;
mqttParameters;
statusUpdateInterval;
transformationStatsInterval = null;
publishedMessageCount = 0;
publishedMessageBytes = 0;
subscribedMessageCount = 0;
subscribedMessageBytes = 0;
mqttWorker;
isConnected = false;
constructor(mqttHost, instanceName, mqttParameters, mqttWorker) {
this.mqttSSL = mqttParameters?.mqttSSL ?? false;
this.mqttSubToTopics = mqttParameters?.mqttSubToTopics ?? [];
this.mqttHost = mqttHost;
this.startDate = new Date();
this.instanceName = instanceName ?? "process";
this.statusTopic = `${mqttParameters?.statusTopic}`;
this.mqttParameters = mqttParameters ?? {};
this.mqttWorker = mqttWorker;
}
async start() {
logger.info(`${this.instanceName} - Connecting to MQTT broker...`);
return new Promise((resolve, reject) => {
try {
const username = this.mqttParameters.username;
const password = this.mqttParameters.password;
const options = {
username,
password,
protocolVersion: 5,
rejectUnauthorized: this.mqttSSL,
will: {
topic: `${this.statusTopic}alive`,
payload: Buffer.from(""),
qos: 0,
retain: true,
properties: { messageExpiryInterval: 3600 },
},
clientId: this.instanceName,
clean: true,
};
const protocol = this.mqttSSL ? "mqtts" : "mqtt";
this.mqttClient = mqtt.connect(`${protocol}://${this.mqttHost}`, options);
const onConnect = () => {
try {
this.mqttClient.stream.setMaxListeners(0);
logger.info(`${this.instanceName} - Connected to MQTT broker at ${this.mqttHost}`);
if (this.mqttSubToTopics && this.mqttSubToTopics.length > 0) {
logger.info(`${this.instanceName} - Subscribed to ${this.mqttSubToTopics.length} topics.`);
this.mqttClient.subscribe(this.mqttSubToTopics, { qos: 0 });
}
if (this.statusTopic) {
this.statusUpdateInterval = setInterval(() => this.emitStatusUpdates(), 10000);
}
this.transformationStatsInterval = setInterval(() => {
this.emitTransformationStatistics();
}, 60000);
this.mqttClient.off("connect", onConnect);
this.isConnected = true;
resolve();
}
catch (error) {
logger.error(`${this.instanceName} - Error in MQTT connect handler: ${error.message}`);
this.isConnected = false;
reject(error);
}
};
this.mqttClient.on("connect", onConnect);
this.mqttClient.on("message", (topic, message, packet) => {
try {
logger.debug(`${this.instanceName} - Message received on topic ${topic}`);
this.event.emit("input", {
message: message.toString(),
topic: topic,
packet: packet,
});
this.updateSubscribeTransformationStats(message.length);
}
catch (error) {
logger.error(`${this.instanceName} - Error in MQTT message handler: ${error.message}`);
}
});
this.mqttClient.on("error", (error) => {
logger.error(`${this.instanceName} - MQTT client error: ${error.message}`);
this.isConnected = false;
if ("code" in error) {
this.event.emit("error", { code: error.code, message: error.message });
}
else {
this.event.emit("error", { message: error.message, code: 0 });
}
reject(error);
});
this.mqttClient.on("reconnect", () => {
logger.debug(`${this.instanceName} - Attempting to reconnect to MQTT broker...`);
});
this.mqttClient.on("close", () => {
logger.debug(`${this.instanceName} - MQTT connection closed.`);
this.isConnected = false;
});
this.mqttClient.on("offline", () => {
logger.debug(`${this.instanceName} - MQTT client is offline.`);
this.isConnected = false;
});
this.mqttClient.on("end", () => {
logger.debug(`${this.instanceName} - MQTT client connection ended.`);
this.isConnected = false;
});
this.mqttClient.on("disconnect", (packet) => {
logger.debug(`${this.instanceName} - MQTT client disconnected. Reason: ${packet?.reasonCode}`);
this.isConnected = false;
});
}
catch (error) {
logger.error(`${this.instanceName} - Error starting MQTT proxy: ${error.message}`);
this.isConnected = false;
reject(error);
}
});
}
async publish(topic, message, options) {
this.updatePublishTransformationStats(JSON.stringify(message).length);
return new Promise((resolve, reject) => {
if (!this.mqttClient || !this.mqttClient.connected) {
const error = new Error(`${this.instanceName} - MQTT client is not connected.`);
logger.error(error.message);
return reject(error);
}
this.mqttClient.publish(topic, message, options || {}, (err) => {
if (err) {
logger.error(`${this.instanceName} - Error publishing to topic ${topic}: ${err.message}`);
return reject(err);
}
logger.debug(`${this.instanceName} - Published message to topic ${topic}`);
resolve();
});
});
}
async subscribeAsync(topic, options) {
return this.mqttClient.subscribeAsync(topic, options);
}
async unsubscribeAsync(topic) {
return this.mqttClient.unsubscribeAsync(topic);
}
stop() {
logger.info(`${this.instanceName} - Disconnecting from MQTT broker...`);
try {
if (this.statusUpdateInterval) {
clearInterval(this.statusUpdateInterval);
this.statusUpdateInterval = null;
}
if (this.transformationStatsInterval) {
clearInterval(this.transformationStatsInterval);
this.transformationStatsInterval = null;
}
if (this.mqttClient) {
this.mqttClient.end(false, () => {
this.isConnected = false;
logger.info(`${this.instanceName} - Disconnected from MQTT broker.`);
});
}
}
catch (error) {
logger.error(`${this.instanceName} - Error during stop: ${error.message}`);
this.isConnected = false;
}
}
emitStatusUpdates() {
try {
const uptime = Math.round((new Date().getTime() - this.startDate.getTime()) / 60000);
this.event.emit("mqttProxyStatus", { event: "uptime", value: uptime, uom: PhysicalMeasurements.Minute, statusTopic: this.statusTopic + "uptime" });
this.event.emit("mqttProxyStatus", { event: "alive", value: 1, uom: DataSizeMeasurements.Bit, statusTopic: this.statusTopic + "alive" });
if (this.mqttWorker !== undefined) {
const tpValue = this.mqttWorker.getPublisherState() ? 1 : 0;
this.event.emit("mqttProxyStatus", { event: "t-publisher-active", value: tpValue, uom: DataSizeMeasurements.Bit, statusTopic: this.statusTopic + "t-publisher-active" });
const tsValue = this.mqttWorker.getSubscriberState() ? 1 : 0;
this.event.emit("mqttProxyStatus", { event: "t-subscriber-active", value: tsValue, uom: DataSizeMeasurements.Bit, statusTopic: this.statusTopic + "t-subscriber-active" });
}
}
catch (error) {
logger.error(`${this.instanceName} - Error publishing MQTT status: ${error.message}`);
}
}
updatePublishTransformationStats(messageSizeOut) {
this.publishedMessageCount += 1;
this.publishedMessageBytes += messageSizeOut;
}
updateSubscribeTransformationStats(messageSizeIn) {
this.subscribedMessageCount += 1;
this.subscribedMessageBytes += messageSizeIn;
}
async emitTransformationStatistics() {
if (this.statusTopic !== "") {
try {
this.event.emit("mqttProxyStatus", {
event: "published-message-count",
value: this.publishedMessageCount,
uom: "",
statusTopic: this.statusTopic + "published-message-count",
});
this.event.emit("mqttProxyStatus", {
event: "published-message-bytes",
value: Math.round(this.publishedMessageBytes / 1024),
uom: DataSizeMeasurements.KiloByte,
statusTopic: this.statusTopic + "published-message-bytes",
});
this.event.emit("mqttProxyStatus", {
event: "subscribed-message-count",
value: this.subscribedMessageCount,
uom: "",
statusTopic: this.statusTopic + "subscribed-message-count",
});
this.event.emit("mqttProxyStatus", {
event: "subscribed-message-bytes",
value: Math.round(this.subscribedMessageBytes / 1024),
uom: DataSizeMeasurements.KiloByte,
statusTopic: this.statusTopic + "subscribed-message-bytes",
});
this.publishedMessageCount = 0;
this.publishedMessageBytes = 0;
this.subscribedMessageCount = 0;
this.subscribedMessageBytes = 0;
}
catch (error) {
this.publishedMessageCount = 0;
this.publishedMessageBytes = 0;
this.subscribedMessageCount = 0;
this.subscribedMessageBytes = 0;
logger.error(`${this.instanceName} - Error emitting transformation statistics: ${error.message}`);
}
}
}
}
//# sourceMappingURL=mqtt-proxy.js.map