zigbee2mqtt/lib/mqtt.js
Dustin Sallings 97a4b6b539
Add support for expiring retained messages. (#3082)
* Add support for expiring retained messages.

For most of my environmental monitoring use cases, I want the readings
retained so I can pick them up from clients at any time, but if the
sensor (or zigbee2mqtt) fails, I want the readings to go away so I can
tell the difference between a stale reading and a missing reading.

This is easily accomplished in MQTTv5 using the "message expiry
interval" property.  To add that to zigbee2mqtt, I added a 'version'
option to the mqtt section so I can specify to connect with version 5
and added a 'retention' property to devices allowing me to specify how
long items should be retained.

e.g.

    mqtt:
      base_topic: site/zigbee2mqtt
      server: 'mqtt://myserver'
      user: zigbee
      version: 5
    serial:
      port: /dev/ttyACM0
    devices:
      '0x00358d00022308da':
        friendly_name: someroom
        retain: true
        retention: 900

* Also get from deviceOptions

* Update settings.js

* Update controller.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-03-12 20:25:37 +01:00

127 lines
4.1 KiB
JavaScript

const mqtt = require('mqtt');
const logger = require('./util/logger');
const settings = require('./util/settings');
const fs = require('fs');
const events = require('events');
class MQTT extends events.EventEmitter {
constructor() {
super();
this.onMessage = this.onMessage.bind(this);
}
async connect() {
const mqttSettings = settings.get().mqtt;
logger.info(`Connecting to MQTT server at ${mqttSettings.server}`);
const options = {
will: {
topic: `${settings.get().mqtt.base_topic}/bridge/state`,
payload: 'offline',
retain: true,
},
};
if (mqttSettings.version) {
options.protocolVersion = mqttSettings.version;
}
if (mqttSettings.keepalive) {
logger.debug(`Using MQTT keepalive: ${mqttSettings.keepalive}`);
options.keepalive = mqttSettings.keepalive;
}
if (mqttSettings.ca) {
logger.debug(`MQTT SSL/TLS: Path to CA certificate = ${mqttSettings.ca}`);
options.ca = fs.readFileSync(mqttSettings.ca);
}
if (mqttSettings.key && mqttSettings.cert) {
logger.debug(`MQTT SSL/TLS: Path to client key = ${mqttSettings.key}`);
logger.debug(`MQTT SSL/TLS: Path to client certificate = ${mqttSettings.cert}`);
options.key = fs.readFileSync(mqttSettings.key);
options.cert = fs.readFileSync(mqttSettings.cert);
}
if (mqttSettings.user && mqttSettings.password) {
options.username = mqttSettings.user;
options.password = mqttSettings.password;
}
if (mqttSettings.client_id) {
logger.debug(`Using MQTT client ID: '${mqttSettings.client_id}'`);
options.clientId = mqttSettings.client_id;
}
if (mqttSettings.hasOwnProperty('reject_unauthorized') && !mqttSettings.reject_unauthorized) {
logger.debug(`MQTT reject_unauthorized set false, ignoring certificate warnings.`);
options.rejectUnauthorized = false;
}
// Set timer at interval to check if connected to MQTT server.
const interval = 10 * 1000; // seconds * 1000.
this.connectionTimer = setInterval(() => {
if (this.client.reconnecting) {
logger.error('Not connected to MQTT server!');
}
}, interval);
return new Promise((resolve) => {
this.client = mqtt.connect(mqttSettings.server, options);
this.client.on('connect', () => {
logger.info('Connected to MQTT server');
this.publish('bridge/state', 'online', {retain: true, qos: 0});
resolve();
});
this.client.on('message', this.onMessage);
});
}
async disconnect() {
clearTimeout(this.connectionTimer);
this.connectionTimer = null;
await this.publish('bridge/state', 'offline', {retain: true, qos: 0});
logger.info('Disconnecting from MQTT server');
this.client.end();
}
subscribe(topic) {
this.client.subscribe(topic);
}
onMessage(topic, message) {
this.emit('message', {topic, message: message.toString()});
}
async publish(topic, payload, options, base=settings.get().mqtt.base_topic) {
topic = `${base}/${topic}`;
options = {qos: 0, retain: false, ...options};
if (!this.client || this.client.reconnecting) {
logger.error(`Not connected to MQTT server!`);
logger.error(`Cannot send message: topic: '${topic}', payload: '${payload}`);
return;
}
logger.info(`MQTT publish: topic '${topic}', payload '${payload}'`);
return new Promise((resolve) => {
this.client.publish(topic, payload, options, () => resolve());
});
}
log(type, message, meta=null) {
const payload = {type, message};
if (meta) {
payload.meta = meta;
}
this.publish('bridge/log', JSON.stringify(payload), {retain: false});
}
}
module.exports = MQTT;