Skip to content

Commit

Permalink
feat(mqtt): Collect runtime metrics and provide them via REST
Browse files Browse the repository at this point in the history
  • Loading branch information
Hypfer committed Feb 12, 2022
1 parent 3c24eba commit dcc1db4
Show file tree
Hide file tree
Showing 9 changed files with 257 additions and 79 deletions.
15 changes: 8 additions & 7 deletions backend/lib/Valetudo.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,21 @@ class Valetudo {
robot: this.robot
});

this.mqttController = new MqttController({
config: this.config,
robot: this.robot
});

this.webserver = new Webserver({
config: this.config,
robot: this.robot,
mqttController: this.mqttController,
ntpClient: this.ntpClient,
updater: this.updater,
valetudoEventStore: this.valetudoEventStore
});


this.mqttClient = new MqttController({
config: this.config,
robot: this.robot
});

this.scheduler = new Scheduler({
config: this.config,
robot: this.robot,
Expand Down Expand Up @@ -216,8 +217,8 @@ class Valetudo {

await this.networkAdvertisementManager.shutdown();
await this.scheduler.shutdown();
if (this.mqttClient) {
await this.mqttClient.shutdown();
if (this.mqttController) {
await this.mqttController.shutdown();
}

await this.webserver.shutdown();
Expand Down
70 changes: 64 additions & 6 deletions backend/lib/mqtt/MqttController.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,35 @@ class MqttController {
this.subscriptions = {};

this.state = HomieCommonAttributes.STATE.INIT;
this.stats = {
messages: {
count: {
received: 0,
sent: 0
},
bytes: {
received: 0,
sent: 0
}

},
connection: {
connects: 0,
disconnects: 0,
reconnects: 0,
errors: 0
}
};

this.configDefaults = {
identity: {
friendlyName: this.robot.getModelName() + " " + Tools.GET_HUMAN_READABLE_SYSTEM_ID(),
identifier: Tools.GET_HUMAN_READABLE_SYSTEM_ID()
},
customizations: {
topicPrefix: "valetudo"
}
};

/** @public */
this.homieAddICBINVMapProperty = false;
Expand Down Expand Up @@ -116,6 +145,25 @@ class MqttController {
});
}

/**
* @public
* @return {{stats: ({messages: {bytes: {received: number, sent: number}, count: {received: number, sent: number}}, connection: {reconnects: number, connects: number, disconnects: number, errors: number}}), state: string}}
*/
getStatus() {
return {
state: this.state,
stats: this.stats
};
}

/**
* @public
* @return {{identity: {identifier: string, friendlyName: string}, customizations: {topicPrefix: string}}}
*/
getConfigDefaults() {
return this.configDefaults;
}

/**
* @private
*/
Expand All @@ -134,15 +182,15 @@ class MqttController {
});

if (!this.currentConfig.identity.identifier) {
this.currentConfig.identity.identifier = Tools.GET_HUMAN_READABLE_SYSTEM_ID();
this.currentConfig.identity.identifier = this.configDefaults.identity.identifier;
}

if (!this.currentConfig.identity.friendlyName) {
this.currentConfig.identity.friendlyName = this.robot.getModelName() + " " + Tools.GET_HUMAN_READABLE_SYSTEM_ID();
this.currentConfig.identity.friendlyName = this.configDefaults.identity.friendlyName;
}

if (!this.currentConfig.customizations.topicPrefix) {
this.currentConfig.customizations.topicPrefix = "valetudo";
this.currentConfig.customizations.topicPrefix = this.configDefaults.customizations.topicPrefix;
}

this.currentConfig.stateTopic = this.currentConfig.customizations.topicPrefix + "/" + this.currentConfig.identity.identifier + "/$state";
Expand Down Expand Up @@ -231,6 +279,7 @@ class MqttController {
this.client.on("connect", () => {
Logger.info("Connected successfully to MQTT broker");

this.stats.connection.connects++;
this.messageDeduplicationCache.clear();

this.reconfigure(async () => {
Expand Down Expand Up @@ -259,17 +308,20 @@ class MqttController {
});

this.client.on("message", (topic, message, packet) => {
this.stats.messages.count.received++;
this.stats.messages.bytes.received += packet.length;

if (!Object.prototype.hasOwnProperty.call(this.subscriptions, topic)) {
return;
}

const msg = message.toString();

//@ts-ignore
if (packet?.retain === true) {
if (packet.retain === true) {
Logger.warn(
"Received a retained MQTT message. Most certainly you or the home automation software integration " +
"you are using are sending the MQTT command incorrectly. Please remove the \"retained\" flag to fix this issue. Discarding.",
"you are using is sending the MQTT command incorrectly. Please remove the \"retained\" flag to fix this issue. Discarding message.",
{
topic: topic,
message: msg
Expand All @@ -283,8 +335,10 @@ class MqttController {
});

this.client.on("error", (e) => {
this.stats.connection.errors++;

if (e && e.message === "Not supported") {
Logger.info("Connected to non standard compliant MQTT Broker.");
Logger.info("Connected to non-standard-compliant MQTT Broker.");
} else {
Logger.error("MQTT error:", e.toString());

Expand All @@ -303,6 +357,7 @@ class MqttController {
});

this.client.on("reconnect", () => {
this.stats.connection.reconnects++;
Logger.info("Attempting to reconnect to MQTT broker");
});

Expand Down Expand Up @@ -383,6 +438,7 @@ class MqttController {
this.messageDeduplicationCache.clear();

Logger.info("Successfully disconnected from the MQTT Broker");
this.stats.connection.disconnects++;
}

/**
Expand Down Expand Up @@ -755,6 +811,8 @@ class MqttController {
const hasChanged = this.messageDeduplicationCache.update(topic, message);

if (hasChanged) {
this.stats.messages.count.sent++;
this.stats.messages.bytes.sent += message.length;
return this.asyncClient.publish(topic, message, options);
} else {
return new Promise(resolve => {
Expand Down
40 changes: 40 additions & 0 deletions backend/lib/webserver/MQTTRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const express = require("express");

class MQTTRouter {
/**
*
* @param {object} options
* @param {import("../mqtt/MqttController")} options.mqttController
* @param {import("../Configuration")} options.config
* @param {*} options.validator
*/
constructor(options) {
this.router = express.Router({mergeParams: true});

this.config = options.config;
this.mqttController = options.mqttController;
this.validator = options.validator;

this.initRoutes();
}


initRoutes() {
this.router.get("/status", (req, res) => {
res.json(this.mqttController.getStatus());
});

this.router.get("/properties", (req, res) => {
res.json({
defaults: this.mqttController.getConfigDefaults()
});
});

}

getRouter() {
return this.router;
}
}

module.exports = MQTTRouter;
15 changes: 0 additions & 15 deletions backend/lib/webserver/ValetudoRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,6 @@ class ValetudoRouter {
res.json(mqttConfig);
});

this.router.get("/config/interfaces/mqtt/properties", (req, res) => {
//It might make sense to pull this from the mqttController but that would introduce a dependency between the webserver and the mqttController :/
res.json({
defaults: {
identity: {
friendlyName: this.robot.getModelName() + " " + Tools.GET_HUMAN_READABLE_SYSTEM_ID(),
identifier: Tools.GET_HUMAN_READABLE_SYSTEM_ID()
},
customizations: {
topicPrefix: "valetudo"
}
}
});
});

this.router.put("/config/interfaces/mqtt", this.validator, (req, res) => {
let mqttConfig = req.body;
let oldConfig = this.config.get("mqtt");
Expand Down
4 changes: 4 additions & 0 deletions backend/lib/webserver/WebServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const ValetudoRouter = require("./ValetudoRouter");

const fs = require("fs");
const MiioValetudoRobot = require("../robots/MiioValetudoRobot");
const MQTTRouter = require("./MQTTRouter");
const NTPClientRouter = require("./NTPClientRouter");
const SSDPRouter = require("./SSDPRouter");
const SystemRouter = require("./SystemRouter");
Expand All @@ -33,6 +34,7 @@ class WebServer {
/**
* @param {object} options
* @param {import("../core/ValetudoRobot")} options.robot
* @param {import("../mqtt/MqttController")} options.mqttController
* @param {import("../NTPClient")} options.ntpClient
* @param {import("../updater/Updater")} options.updater
* @param {import("../ValetudoEventStore")} options.valetudoEventStore
Expand Down Expand Up @@ -112,6 +114,8 @@ class WebServer {

this.app.use("/api/v2/valetudo/", this.valetudoRouter.getRouter());

this.app.use("/api/v2/mqtt/", new MQTTRouter({config: this.config, mqttController: options.mqttController, validator: this.validator}).getRouter());

this.app.use("/api/v2/ntpclient/", new NTPClientRouter({config: this.config, ntpClient: options.ntpClient, validator: this.validator}).getRouter());

this.app.use("/api/v2/timers/", new TimerRouter({config: this.config, robot: this.robot, validator: this.validator}).getRouter());
Expand Down
Loading

0 comments on commit dcc1db4

Please sign in to comment.