node-red-contrib-azure-iot-hub
Version:
Connector to azure on Node-Red
352 lines (300 loc) • 13.8 kB
JavaScript
module.exports = function (RED) {
var Client = require('azure-iot-device').Client;
var Registry = require('azure-iothub').Registry;
var Message = require('azure-iot-device').Message;
var Protocols = {
amqp: require('azure-iot-device-amqp').Amqp,
mqtt: require('azure-iot-device-mqtt').Mqtt,
http: require('azure-iot-device-http').Http,
amqpWs: require('azure-iot-device-amqp-ws').AmqpWs
};
var EventHubClient = require('azure-event-hubs').Client;
var client = null;
var clientConnectionString = "";
var newConnectionString = "";
var newProtocol = "";
var clientProtocol = "";
var statusEnum = {
disconnected: { color: "red", text: "Disconnected" },
connected: { color: "green", text: "Connected" },
sent: { color: "blue", text: "Sent message" },
received: { color: "yellow", text: "Received" },
error: { color: "grey", text: "Error" }
};
var setStatus = function (node, status) {
node.status({ fill: status.color, shape: "dot", text: status.text });
}
var sendData = function (node, data) {
node.log('Sending Message to Azure IoT Hub :\n Payload: ' + JSON.stringify(data));
// Create a message and send it to the IoT Hub every second
var message = new Message(JSON.stringify(data));
client.sendEvent(message, function (err, res) {
if (err) {
node.error('Error while trying to send message:' + err.toString());
setStatus(node, statusEnum.error);
} else {
node.log('Message sent.');
node.send({payload: "Message sent."});
setStatus(node, statusEnum.sent);
}
});
};
var sendMessageToIoTHub = function (node, message, reconnect) {
if (!client || reconnect) {
node.log('Connection to IoT Hub not established or configuration changed. Reconnecting.');
// Update the connection string
clientConnectionString = newConnectionString;
// update the protocol
clientProtocol = newProtocol;
// If client was previously connected, disconnect first
if (client)
disconnectFromIoTHub(node);
// Connect the IoT Hub
connectToIoTHub(node, message);
} else {
sendData(node, message);
}
};
var connectToIoTHub = function (node, pendingMessage) {
node.log('Connecting to Azure IoT Hub:\n Protocol: ' + newProtocol + '\n Connection string :' + newConnectionString);
client = Client.fromConnectionString(newConnectionString, Protocols[newProtocol]);
client.open(function (err) {
if (err) {
node.error('Could not connect: ' + err.message);
setStatus(node, statusEnum.disconnected);
// works for me..
client = undefined;
} else {
node.log('Connected to Azure IoT Hub.');
setStatus(node, statusEnum.connected);
// Check if a message is pending and send it
if (pendingMessage) {
node.log('Message is pending. Sending it to Azure IoT Hub.');
// Send the pending message
sendData(node, pendingMessage);
}
client.on('message', function (msg) {
// We received a message
node.log('Message received from Azure IoT Hub\n Id: ' + msg.messageId + '\n Payload: ' + msg.data);
var outpuMessage = new Message();
outpuMessage.payload = msg.data;
setStatus(node, statusEnum.received);
node.log(JSON.stringify(outpuMessage));
node.send(outpuMessage);
client.complete(msg, printResultFor(node,'Completed'));
});
client.on('error', function (err) {
node.error(err.message);
});
client.on('disconnect', function () {
disconnectFromIoTHub(node);
});
}
});
};
var disconnectFromIoTHub = function (node) {
if (client) {
node.log('Disconnecting from Azure IoT Hub');
client.removeAllListeners();
client.close(printResultFor(node, 'close'));
client = null;
setStatus(node, statusEnum.disconnected);
}
};
function nodeConfigUpdated(cs, proto) {
return ((clientConnectionString != cs) || (clientProtocol != proto));
}
// Main function called by Node-RED
function AzureIoTHubNode(config) {
// Store node for further use
var node = this;
//nodeConfig = config;
// Create the Node-RED node
RED.nodes.createNode(this, config);
node.on('input', function (msg) {
var messageJSON = null;
if (typeof (msg.payload) != "string") {
node.log("JSON");
messageJSON = msg.payload;
} else {
node.log("String");
//Converting string to JSON Object
//Sample string: {"deviceId": "name", "key": "jsadhjahdue7230-=13", "protocol": "amqp", "data": "25"}
messageJSON = JSON.parse(msg.payload);
}
//Creating connectionString
//Sample
//HostName=sample.azure-devices.net;DeviceId=sampleDevice;SharedAccessKey=wddU//P8fdfbSBDbIdghZAoSSS5gPhIZREhy3Zcv0JU=
newConnectionString = "HostName=" + node.credentials.hostname + ";DeviceId=" + messageJSON.deviceId + ";SharedAccessKey=" + messageJSON.key
if( typeof messageJSON.protocol !== 'undefined'){
newProtocol = messageJSON.protocol;
} else {
newProtocol = config.protocol;
}
// Sending data to Azure IoT Hub Hub using specific connectionString
sendMessageToIoTHub(node, messageJSON.data, nodeConfigUpdated(newConnectionString, newProtocol));
});
node.on('close', function () {
disconnectFromIoTHub(node, this);
});
}
function IoTHubRegistry(config) {
RED.nodes.createNode(this, config);
var node = this;
node.on('input', function (msg) {
if (typeof (msg.payload) == 'string') {
msg.payload = JSON.parse(msg.payload);
}
var registry = Registry.fromConnectionString(node.credentials.connectionString);
registry.create(msg.payload, function (err, device) {
if (err) {
node.error('Error while trying to create a new device: ' + err.toString());
setStatus(node, statusEnum.error);
} else {
node.log("Device created: " + JSON.stringify(device));
node.log("Device ID: " + device.deviceId + " - primaryKey: " + device.authentication.SymmetricKey.primaryKey + " - secondaryKey: " + device.authentication.SymmetricKey.secondaryKey);
node.send("Device ID: " + device.deviceId + " - primaryKey: " + device.authentication.SymmetricKey.primaryKey + " - secondaryKey: " + device.authentication.SymmetricKey.secondaryKey);
}
});
});
node.on('close', function () {
disconnectFromIoTHub(node, this);
});
}
var disconnectFromEventHub = function( node ){
if( node.reconnectTimer ){
clearTimeout( node.reconnectTimer );
node.reconnectTimer = null;
}
if (node.client) {
node.log('Disconnecting from Azure IoT Hub');
node.client.close();
node.client = null;
setStatus(node, statusEnum.disconnected);
}
};
var connectToEventHub = function( node, connectionString ){
// Open connection
node.client = EventHubClient.fromConnectionString(connectionString);
node.client.open()
.then(node.client.getPartitionIds.bind(node.client))
.then((partitionIds)=>{
return Promise.all( partitionIds.map( (partitionId)=> {
return node.client.createReceiver('$Default', partitionId, { 'startAfterTime' : Date.now()}).then(function(receiver) {
node.log('Created Event Hub partition receiver: ' + partitionId);
// Allthough 'errorReceived' event is defined in azure-event-hubs function documentation, it does not appear to throw one when disconnected
receiver.on('errorReceived', function( err ){
node.log('Receiver error: ', err.message);
setStatus(node, statusEnum.error);
});
receiver.on('message', function( message ){
setStatus(node, statusEnum.received);
let msg = {
deviceId: message.annotations["iothub-connection-device-id"],
//topic: message.properties.subject||message.properties.to,
payload: message.body
};
node.send(msg);
});
});
}));
}).then(()=>{
node.log("Connected to each partition receiver - ready to receive data!");
setStatus(node, statusEnum.connected);
// Since EventHubClient does not provide any mechanism to catch disconnection nor override AMQP retry policy, the only available option is to listen to it's private _amqp member directly
node.client._amqp.once('connection:closed', function(){
node.log("AMQP disconnected");
process.nextTick(()=>{
disconnectFromEventHub(node);
connectToEventHub( node, connectionString );
});
});
}).catch(function(error){
node.log("Event Hub connection threw an error: " + error.message);
disconnectFromEventHub(node);
node.reconnectTimer = setTimeout( function(){
node.reconnectTimer = null;
if( !node.client ) connectToEventHub( node, connectionString );
}, 30000);
});
};
function AzureIoTHubReceiverNode(config) {
// Store node for further use
var node = this;
this.client = null;
this.reconnectTimer = null;
// Create the Node-RED node
RED.nodes.createNode(this, config);
setStatus(node, statusEnum.disconnected);
connectToEventHub( this, node.credentials.connectionString );
node.on('close', function() {
disconnectFromEventHub(node);
});
}
function AzureIoTHubDeviceTwin(config){
RED.nodes.createNode(this, config);
var node = this;
node.on('input', function (msg) {
var registry = Registry.fromConnectionString(node.credentials.connectionString);
if( typeof msg.payload === "string" ) var query = registry.createQuery("SELECT * FROM devices WHERE deviceId ='" + msg.payload + "'");
else var query = registry.createQuery("SELECT * FROM devices");
query.nextAsTwin( function(err, results){
if (err) {
node.error('Error while trying to retrieve device twins: ' + err.message);
msg.error = err;
delete msg.payload;
node.send(msg);
} else {
msg.payload = results;
disconnectFromIoTHub(node, this);
node.send(msg);
}
});
});
node.on('close', function () {
disconnectFromIoTHub(node, this);
});
}
// Registration of the node into Node-RED
RED.nodes.registerType("azureiothub", AzureIoTHubNode, {
credentials: {
hostname: { type: "text" }
},
defaults: {
name: { value: "Azure IoT Hub" },
protocol: { value: "amqp" }
}
});
// Registration of the node into Node-RED
RED.nodes.registerType("azureiothubregistry", IoTHubRegistry, {
credentials: {
connectionString: { type: "text" }
},
defaults: {
name: { value: "Azure IoT Hub Registry" },
}
});
RED.nodes.registerType("azureiothubreceiver", AzureIoTHubReceiverNode, {
credentials: {
connectionString: { type: "text" }
},
defaults: {
name: { value: "Azure IoT Hub Receiver" }
}
});
RED.nodes.registerType("azureiothubdevicetwin", AzureIoTHubDeviceTwin, {
credentials: {
connectionString: { type: "text" }
},
defaults: {
name: { value: "Azure IoT Hub Device Twin" }
}
});
// Helper function to print results in the console
function printResultFor(node, op) {
return function printResult(err, res) {
if (err) node.error(op + ' error: ' + err.toString());
if (res) node.log(op + ' status: ' + res.constructor.name);
};
}
}