node-red-contrib-qbus
Version:
A Qbus node for nodeRED
344 lines (287 loc) • 13.2 kB
JavaScript
var mqtt = require('mqtt');
module.exports = function(RED) {
function Device(client, id, connectable, ctdSn, outputArray) {
this.client = client
this.connectable = connectable
this.id = id
this.ctdSn = ctdSn
this.outputArray = outputArray
}
function Outputs(id, type, name) {
this.id = id
this.type = type
this.name = name
}
function Connection(id, connected, connectable) {
this.id = id;
this.connected = connected;
this.connectable = connectable;
}
GetDevices = function (devs, client, conn, prevDevices) {
RED.log.debug("Processing devices ");
var devices = new Array();
if (devs != null) {
var i = 0
var j = 0
for (i = 0; i < devs.length; i++) {
var functionBlocks = devs[i].functionBlocks;
var outputs = new Array();
if (devs[i].hasOwnProperty("functionBlocks")){
//RED.log.debug(JSON.stringify(functionBlocks))
var fbSize = functionBlocks.length
if (functionBlocks.length > 0){
for (j = 0; j < functionBlocks.length; j++) {
switch (functionBlocks[j].type) {
case 'analog':
outputs.push(new Outputs(functionBlocks[j].id, "analog", functionBlocks[j].name));
break;
case 'weatherstation':
outputs.push(new Outputs(functionBlocks[j].id, "weatherstation", functionBlocks[j].name));
break;
case 'gauge':
outputs.push(new Outputs(functionBlocks[j].id, "gauge", functionBlocks[j].name));
break;
case 'onoff':
outputs.push(new Outputs(functionBlocks[j].id, "onoff", functionBlocks[j].name));
break;
case 'thermo':
outputs.push(new Outputs(functionBlocks[j].id, "thermo", functionBlocks[j].name));
break;
case 'shutter':
if (functionBlocks[j].properties.hasOwnProperty("state")){
// UP/DOWN Type
outputs.push(new Outputs(functionBlocks[j].id, "shutter", functionBlocks[j].name));
} else if (functionBlocks[j].properties.hasOwnProperty("shutterPosition")){
// %type
outputs.push(new Outputs(functionBlocks[j].id, "shutter", functionBlocks[j].name));
} else if (functionBlocks[j].properties.hasOwnProperty("slatPosition")){
// %type with slats
outputs.push(new Outputs(functionBlocks[j].id, "shutter", functionBlocks[j].name));
}
break;
case 'scene':
outputs.push(new Outputs(functionBlocks[j].id, "scene", functionBlocks[j].name));
break;
case 'ventilation':
outputs.push(new Outputs(functionBlocks[j].id, "ventilation", functionBlocks[j].name));
break;
}
}
}
}
prevDevices.push(new Device(client, devs[i].id, conn, devs[i].serialNr, outputs))
}
return prevDevices;
}
return null;
};
class RemoteServerNode {
constructor(n){
RED.nodes.createNode(this,n);
var node = this;
node.config = n;
node.host = node.config.host;
node.on('close', () => this.onClose());
node.setMaxListeners(0);
node.mqtt = node.connectMQTT();
node.mqtt.on('connect', () => this.onMQTTConnect());
node.mqtt.on('message', (topic, message) => this.onMQTTMessage(topic, message));
node.mqtt.on('close', () => this.onMQTTClose());
node.mqtt.on('end', () => this.onMQTTEnd());
node.mqtt.on('reconnect', () => this.onMQTTReconnect());
node.mqtt.on('offline', () => this.onMQTTOffline());
node.mqtt.on('disconnect', (error) => this.onMQTTDisconnect(error));
node.mqtt.on('error', (error) => this.onMQTTError(error));
node.globalContext = node.context().global;
node.globalContext.set("devices",[])
}
getConfig(callback, forceRefresh = false, withGroups = false) {
var node = this;
var timeout = null;
var timeout_ms = 60000;
var client = node.connectMQTT('tmp');
var conn = false
client.on('connect', function() {
//end function after timeout, if no response
timeout = setTimeout(function() {
node.error('Error: getDevices timeout, close connection')
client.end(true);
}, timeout_ms);
// Subscribe to config topic
client.subscribe("cloudapp/QBUSMQTTGW/config", {'qos':parseInt(node.config.mqtt_qos||0)}, function(err) {
if (err) {
node.error('Error code #0023: ' + err);
client.end(true);
}
});
// Subscribe to state topic
client.subscribe(node.getTopic('/state'), {'qos':parseInt(node.config.mqtt_qos||0)}, function(err) {
if (err) {
node.error('Error code #0023: ' + err);
client.end(true);
}
});
});
client.on('error', function(error) {
node.error('Error code #0024: ' + error);
client.end(true);
});
client.on('end', function(error, s) {
clearTimeout(timeout);
});
client.on('message', function(topic, message) {
if (node.getTopic('/state') === topic) {
//node.log("Initial state: " + message)
} else if (topic.includes("/state")) {
//node.log("Connection state: " + message)
obj = JSON.parse(message)
var id = obj.id
var conn = obj.properties.connectable
var devices = node.globalContext.get("devices")
var j = 0
for (j = 0; j < devices.length; j++) {
if (devices[j].id == id) {
devices[j].connectable = conn
}
}
node.globalContext.set("devices",devices)
client.end(true);
} else if (node.getTopic('/config') === topic) {
// Handle config message
var obj = {}
try {
obj = JSON.parse(message)
} catch (error) {
node.error("Not a json object")
}
if (obj.hasOwnProperty("devices") ){
var prevDevices = node.globalContext.get("devices")
var devices = GetDevices(obj.devices, node.name, conn, prevDevices)
node.globalContext.set("devices",devices)
var i = 0
var devs = []
// Subscribe to state topic for all controllers
for (i = 0; i < devices.length; i++) {
devs.push(devices[i].id)
client.subscribe("cloudapp/QBUSMQTTGW/" + devices[i].id + "/state", {'qos':parseInt(node.config.mqtt_qos||0)}, function(err) {
if (err) {
node.error('Error code #0023: ' + err);
client.end(true);
}
});
}
// Get state of all controllers
var devString = JSON.stringify(devs);
client.publish(node.getTopic('/getState'), devString, {'qos':parseInt(0)},function(err) {
if (err) {
node.error(err);
}
});
}
}
});
// Request config message
client.publish("cloudapp/QBUSMQTTGW/getConfig", "", {'qos':parseInt(0)},function(err) {
if (err) {
node.error(err);
}
});
}
connectMQTT(clientId = null) {
var node = this;
var options = {
port: node.config.mqtt_port || 1883,
username: node.config.mqtt_username || null,
password: node.config.mqtt_password || null,
clientId: 'NodeRed-' + node.id + '-' + (clientId ? clientId : (Math.random() + 1).toString(36).substring(7)),
};
var baseUrl = 'mqtt://';
var tlsNode = RED.nodes.getNode(node.config.tls);
if (node.config.usetls && tlsNode) {
tlsNode.addTLSOptions(options);
baseUrl = 'mqtts://';
}
return mqtt.connect(baseUrl + node.config.host, options);
}
onMQTTConnect() {
var node = this;
node.connection = true;
// node.log('MQTT Connected');
node.emit('onMQTTConnect');
node.getConfig(() => {
node.subscribeMQTT("/#");
});
}
subscribeMQTT(topic) {
var node = this;
node.mqtt.subscribe(topic, {'qos':0}, function(err) {
if (err) {
node.log('MQTT Error: Subscribe to ' + topic);
node.emit('onConnectError', err);
}
});
}
publishMQTT(topic,payload) {
var node = this;
node.mqtt.publish(topic, JSON.stringify(payload));
}
unsubscribeMQTT() {
var node = this;
//node.log('MQTT Unsubscribe from mqtt topic: #');
node.mqtt.unsubscribe(node.getTopic('/#'), function(err) {});
node.devices_values = {};
}
onMQTTDisconnect(error) {
var node = this;
node.log('Disconnected from ' + node.name);
console.log(error);
}
onMQTTError(error) {
var node = this;
node.log('MQTT Error for ' + node.name);
node.emit('onConnectError', error);
}
onMQTTOffline() {
var node = this;
node.warn('No connection with MQTT server ' + node.name + ', please check your settings.');
node.emit('onConnectError', "MQTT server offline");
}
onMQTTEnd() {
var node = this;
//node.log('MQTT End');
}
onMQTTReconnect() {
var node = this;
node.log('Trying to connect to ' + node.name);
}
onMQTTClose() {
var node = this;
// node.log('MQTT Closed for ', node.host);
node.emit('onConnectError', "Connection closed to MQTT server");
}
onClose() {
var node = this;
node.unsubscribeMQTT();
node.mqtt.end();
node.mqtt.close();
node.connection = false;
node.emit('onClose');
node.log('MQTT connection closed for ' + node.name);
}
onMQTTMessage(topic, message) {
var node = this;
var messageString = message.toString();
var msg2send = {}
msg2send.topic = topic;
msg2send.payload = messageString;
node.emit('onMQTTMessage', msg2send);
}
getBaseTopic() {
return "cloudapp/QBUSMQTTGW";
}
getTopic(path) {
return this.getBaseTopic() + path;
}
}
RED.nodes.registerType("mqtt-client",RemoteServerNode);
}