UNPKG

@flowfuse/mqtt-schema-agent

Version:

Agent to collect MQTT topic Schema

215 lines (197 loc) 7.73 kB
const got = require('got') const isUtf8 = require('is-utf8') const mqtt = require('mqtt') const schemaGen = require('./schema-generator') const REPORT_TIME = 1000 * 60 * 2 class Agent { constructor (options) { this.options = options this.topics = {} this.error = null this.stopped = true } async connect () { const agent = this this.connected = false this.reconnectCount = 0 this.stopped = false // console.log(`${this.options.forgeURL}/api/v1/teams/${this.options.team}/brokers/${this.options.broker}/credentials`) try { this.creds = await got.get(`${this.options.forgeURL}/api/v1/teams/${this.options.team}/brokers/${this.options.broker}/credentials`, { headers: { Authorization: `Bearer ${this.options.token}` } }).json() // console.log(`creds: ${JSON.stringify(this.creds,null, 2)}`) this.creds = Object.assign(this.creds, { reconnectPeriod: 0 }) if (this.creds.ssl) { switch (this.creds.protocol) { case 'mqtt:': this.creds.protocol = 'mqtts:' break case 'ws:': this.creds.protocol = 'wss:' break } } const options = { clientId: this.creds.clientId, protocolVersion: this.creds.protocolVersion, username: this.creds.credentials.username, password: this.creds.credentials.password } if (!this.creds.verifySSL) { options.rejectUnauthorized = false } const url = `${this.creds.protocol}//${this.creds.host}:${this.creds.port}` // console.log(url) // console.log(options) // this.client = mqtt.connect(`${this.creds.protocol}//${this.creds.hostname}:${this.creds.port}`, this.creds) this.client = mqtt.connect(url, options) this.client.on('connect', function () { agent.connected = true console.log(`connected to ${agent.creds.host}:${agent.creds.port} as ${options.clientId}`) agent.error = null const topics = agent.creds.topicPrefix || ['#'] console.log(`subscribing to "${topics.join(',')}"`) agent.client.subscribe(topics) }) this.client.on('subscribe', function () { // on successful subscribe reset reconnect count agent.reconnectCount = 0 }) this.client.on('reconnect', function () { // console.log('reconnecting') }) this.client.on('close', function () { // console.log('closed') if (!agent.stopped) { if (agent.reconnectCount < 3) { agent.reconnectCount++ agent.reconnectTimeout = setTimeout(() => { agent.client.reconnect() }, 5000) } } }) this.client.on('disconnect', function (packet) { // console.log('MQTTv5 disconnected') // should check the reason code // console.log(packet) }) this.client.on('error', function (error) { console.log('error', error.code) console.log(error) switch (error.code) { case 'ECONNREFUSED': // connection refused case 'ENOTFOUND': // DNS lookup failed agent.error = error.code break case 1: agent.error = 'WRONGMQTTVERSION' break case 2: case 133: agent.error = 'CLIENTIDNOTALLOWED' break case 4: agent.error = 'MALFORMEDCREDS' break case 5: case 135: // not authorized agent.error = 'NOTAUTHORIZED' break case 138: agent.error = 'BANNED' break default: // console.log('default error') } // console.log(`agent.error set to: ${agent.error}`) }) this.client.on('message', function (topic, payload) { // console.log(topic) let type = { type: 'unknown' } try { if (isUtf8(payload)) { const stringPayload = payload.toString('utf8') type = { type: 'string' } if (stringPayload.charAt(0) === '<' && stringPayload.charAt(stringPayload.length - 1) === '>') { type.type = 'xml' } else { try { const json = JSON.parse(stringPayload) type = schemaGen.generateSchema(json) } catch (err) { // Not JSON parseable - default to string } } } else { type.type = 'bin' } } catch (err) { console.log(`Error parsing payload on topic '${topic}' ${err}`) } // console.log(topic, type) agent.topics[topic] = { timestamp: Date.now(), type } }) } catch (err) { console.log(err) throw err } this.reportInterval = setInterval(async () => { if (agent.connected) { // console.log(`${agent.options.forgeURL}/api/v1/teams/${agent.options.team}/brokers/${agent.options.broker}/topics`) // console.log(JSON.stringify(agent.topics, null, 2)) const upload = [] Object.keys(agent.topics).forEach((key) => { upload.push({ topic: key, type: agent.topics[key].type, timestamp: agent.topics[key].timestamp }) }) try { await got.post(`${agent.options.forgeURL}/api/v1/teams/${agent.options.team}/brokers/${agent.options.broker}/topics`, { headers: { Authorization: `Bearer ${this.options.token}` }, json: upload }) // clear list so only uploading new topics each time agent.topics = {} } catch (err) { console.log(err) } } }, REPORT_TIME) } async start () { if (this.client) { await this.stop() } await this.connect() } async stop () { this.stopped = true if (this.client) { this.client.end() } clearTimeout(this.reconnectTimeout) clearInterval(this.reportInterval) this.reportInterval = null this.connected = false } state () { return { connected: this.connected, topics: this.topics } } } module.exports = { Agent }