node-red-contrib-azure-iot-device-enhanced
Version:
Production-ready Node-RED Azure IoT Device node with enhanced reliability, infinite reconnection, and comprehensive error handling. This is a fork of the original node-red-contrib-azure-iot-device by Eric van Uum, significantly enhanced by Payman Abbasian
452 lines (412 loc) • 20.9 kB
HTML
<script type="text/javascript">
RED.nodes.registerType('azureiotdevice',{
category: 'Azure IoT',
color: '#00FF00',
defaults: {
deviceid: {value:"", required:true},
pnpModelid: {value:"", required:false},
connectiontype: {value: "", required:true},
authenticationmethod: {value: "", required:true},
iothub: {value: "", required: false},
isIotcentral: {value: false, required:false},
scopeid: {value: "", required:false},
enrollmenttype: {value: "", required:false},
saskey: {value: "", required:false},
certname: {value:""},
keyname: {value:""},
passphrase: {value:""},
protocol: {value: "", required:true},
retryInterval: {value: 10, required:true, validate: RED.validators.number()},
methods: {value: []},
DPSpayload: {value:""},
gatewayHostname: {value:"", required: false},
caname: {value:""},
cert: {type:"text", value: "", required:false},
key: {type:"text", value: "", required:false},
ca: {type:"text", value:"", required: false}
},
inputs:1,
outputs:1,
icon: "azureiotdevice.png",
label: function() {
return "Device: " + this.deviceid;
},
paletteLabel: function(){
return "Device";
},
oneditprepare: function() {
let node = this;
// Save the uploaded file
function saveFile(property, file) {
var dataInputId = "#node-input-"+property;
var filenameInputId = "#node-input-"+property+"name";
var filename = file.name;
var reader = new FileReader();
reader.onload = function(event) {
$("#device-config-"+property+"name").text(filename);
$(filenameInputId).val(filename);
$(dataInputId).val(event.target.result);
}
reader.readAsText(file,'utf-8');
}
$(".node-input-retryInterval").spinner({
max:60,
min:1
});
$(".node-input-retryNumber").spinner({
max:60,
min:1
});
// Add DPS payload editor
this.editor = RED.editor.createEditor({
id: 'node-input-DPSpayload-editor',
mode: 'ace/mode/json',
value: $("#node-input-DPSpayload").val()
});
this.editor.focus();
// React on file changes
$("#node-input-certfile" ).on("change", function() {
saveFile("cert", this.files[0]);
});
$("#node-input-keyfile" ).on("change", function() {
saveFile("key", this.files[0]);
});
$("#node-input-cafile" ).on("change", function() {
saveFile("ca", this.files[0]);
});
$("#device-config-certname").text(this.certname);
$("#device-config-keyname").text(this.keyname);
$("#device-config-caname").text(this.caname);
// Clear certificate files
function clearNameData(prop) {
$("#device-config-"+prop+"name").text("");
$("#node-input-"+prop).val("");
$("#node-input-"+prop+"name").val("");
}
$("#device-config-button-cert-clear").on("click", function() {
clearNameData("cert");
});
$("#device-config-button-key-clear").on("click", function() {
clearNameData("key");
});
$("#device-config-button-ca-clear").on("click", function() {
clearNameData("ca");
});
// Enable tabbed interaction
let tabs = RED.tabs.create({
id: "node-input-device-tabs",
onchange: function(tab) {
$("#node-input-device-tabs-content")
.children()
.hide();
$("#" + tab.id).show();
}
});
tabs.addTab({
id: "compact-device-tab-identity",
label: this._("Device Identity")
});
tabs.addTab({
id: "compact-device-tab-customDPS",
label: this._("Custom Provisioning")
});
tabs.addTab({
id: "compact-device-tab-methods",
label: this._("Direct Methods")
});
tabs.addTab({
id: "compact-device-tab-iotedge",
label: this._("IoT Edge Gateway")
});
// Start settings field defaults
$(".iothub-row").hide();
$(".dps-row").hide();
$(".sas-row").hide();
$(".x509-row").hide();
$("proxy-ros").hide();
$("#node-input-connectiontype").change(function() {
if ($(this).val() === "constr") {
$(".iothub-row").show();
$(".dps-row").hide();
} else if ($(this).val() === "dps") {
$(".iothub-row").hide();
$(".dps-row").show();
}
});
$("#node-input-authenticationmethod").change(function() {
if ($(this).val() === "sas") {
$(".sas-row").show();
$(".x509-row").hide();
$("#node-input-enrollmenttype").empty();
$('#node-input-enrollmenttype').append($('<option>',
{
value: "group",
text: 'Group'
}));
$('#node-input-enrollmenttype').append($('<option>',
{
value: "device",
text: 'Individual'
}));
} else if ($(this).val() === "x509") {
$(".sas-row").hide();
$(".x509-row").show();
$("#node-input-enrollmenttype").empty();
$('#node-input-enrollmenttype').append($('<option>',
{
value: "group",
text: 'Group'
}));
}
});
// Direct methods
let methodsItemCount = 0;
if (node.methods && node.methods.length > 0) {
methodsItemCount = node.methods.length;
node.methods.forEach(function(element, index, array) {
generateMethodEntry(element, index);
});
}
function generateMethodEntry(method, id) {
let container = $("<li/>", {
style:
"background: #fefefe; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"
});
let row = $('<div id="row' + id + '"/>').appendTo(container);
$(
'<i style="color: #eee; cursor: move;" class="node-input-device-methods-handle fa fa-bars"></i>'
).appendTo(row);
let nameField = $("<input/>", {
id: "node-input-device-methods-name" + id,
class: "deviceMethodName",
type: "text",
style: "margin-left:5px;width:85%;",
placeholder: "name"
}).appendTo(row);
nameField.val(method.name);
let finalspan = $("<span/>", {
style: "float: right;margin-right: 10px;"
}).appendTo(row);
let removeMethodButton = $("<a/>", {
href: "#",
id: "node-button-method-remove" + id,
class: "editor-button editor-button-small",
style: "margin-top: 7px; margin-left: 5px;"
}).appendTo(finalspan);
$("<i/>", { class: "fa fa-remove" }).appendTo(removeMethodButton);
removeMethodButton.click(function() {
container.css({ background: "#fee" });
container.fadeOut(300, function() {
$(this).remove();
});
});
$("#node-input-device-methods-container").append(container);
};
$("#node-input-device-methods-container").sortable({
axis: "y",
handle: ".node-input-device-methods-handle",
cursor: "move"
});
$(
"#node-input-device-methods-container .node-input-device-methods-handle"
).disableSelection();
$("#node-input-device-methods-add").click(function() {
if (!methodsItemCount || methodsItemCount < 0) {
methodsItemCount = 0;
}
generateMethodEntry({ name: "" }, methodsItemCount++); // length is every time one more than index
$("#node-input-device-methods-container-div").scrollTop(
$("#node-input-device-methods-container-div").get(0).scrollHeight
);
});
function switchDialogResize() {
switchMethodsDialogResize();
}
// dialog direct Methods handling
function switchMethodsDialogResize() {
let rows = $(
"#dialog-form>div:not(.node-input-device-methods-container-row)"
);
let height = $("#dialog-form").height();
rows.each(function(index, row) {
height -= row.outerHeight(true);
});
let editorRow = $(
"#dialog-form>div.node-input-device-methods-container-row"
);
height -=
parseInt(editorRow.css("marginTop")) +
parseInt(editorRow.css("marginBottom"));
$("#node-input-device-methods-container-div").css(
"height",
height + "px"
);
}
$("#dialog").on("dialogresize", switchDialogResize);
$("#dialog").on("dialogopen", function(ev) {
let size = $("#dialog").dialog("option", "sizeCache-switch");
if (size) {
$("#dialog").dialog("option", "width", size.width);
$("#dialog").dialog("option", "height", size.height);
switchDialogResize();
} else {
setTimeout(switchDialogResize, 10);
}
});
$("#dialog").on("dialogclose", function(ev, ui) {
$("#dialog").off("dialogresize", switchDialogResize);
});
},
oneditsave: function() {
let node = this;
// Save the methods for this device
let cacheMethods = $("#node-input-device-methods-container").children();
node.methods = [];
cacheMethods.each(function() {
node.methods.push({
name: $(this)
.find(".deviceMethodName")
.val()
});
});
// Save the DPS custom payload
$("#node-input-DPSpayload").val(this.editor.getValue());
this.editor.destroy();
delete this.editor;
},
oneditcancel: function() {
// Remove the DPS Payload editor
this.editor.destroy();
delete this.editor;
}
});
</script>
<script type="text/x-red" data-template-name="azureiotdevice">
<div class="form-row">
<ul style="background: #fff; min-width: 680px; margin-bottom: 20px;" id="node-input-device-tabs"></ul>
</div>
<div id="node-input-device-tabs-content" style="min-height: 170px;">
<div id="compact-device-tab-identity" style="display:none">
<div class="form-row">
<label for="node-input-deviceid"><i class="icon-tag"></i> <span>Registration / Device ID</span></label>
<input type="text" id="node-input-deviceid" placeholder="%deviceId%">
</div>
<div class="form-row">
<label for="node-input-pnpModelid"><i class="icon-tag"></i> <span>PnP Model ID</span></label>
<input type="text" id="node-input-pnpModelid" placeholder="%PnP Model ID%">
</div>
<div class="form-row">
<label for="node-input-connectiontype"><i class="icon-tag"></i> <span>Connection Type</span></label>
<select id="node-input-connectiontype">
<option value="constr">Connection string</option>
<option value="dps">Device provisioning service</option>
</select>
</div>
<div class="form-row iothub-row">
<label for="node-input-iothub"><i class="icon-tag"></i> <span>IoT Hub Hostname</span></label>
<input type="text" id="node-input-iothub" placeholder="%iothubname%.azure-devices.net">
</div>
<div class="form-row dps-row">
<label for="node-input-isIotcentral"><i class="icon-tag"></i> <span>IoT Central Device</span></label>
<input type="checkbox" id="node-input-isIotcentral" style="max-width:30px">
</div>
<div class="form-row dps-row">
<label for="node-input-scopeid"><i class="icon-tag"></i> <span>Scope ID</span></label>
<input type="text" id="node-input-scopeid" placeholder="%scopeId%">
</div>
<div class="form-row">
<label for="node-input-authenticationmethod"><i class="icon-tag"></i> <span>Authentication Method</span></label>
<select id="node-input-authenticationmethod">
<option value="sas">Shared access signature (SAS)</option>
<option value="x509">CA Certificates (X.509)</option>
</select>
</div>
<div class="form-row dps-row">
<label for="node-input-enrollmenttype"><i class="icon-tag"></i> <span>Enrollment type</span></label>
<select id="node-input-enrollmenttype">
</select>
</div>
<div class="form-row sas-row">
<label for="node-input-saskey"><i class="icon-tag"></i> <span>SAS Key</span></label>
<input type="password" id="node-input-saskey" placeholder="%primairy or secundairy key%">
</div>
<div class="form-row x509-row">
<label for="node-input-x509certificate"><i class="fa fa-file-text-o"></i> <span>X.509 Certificate</span></label>
<span class="device-config-input-data">
<label class="red-ui-button" for="node-input-certfile"><i class="fa fa-upload"></i> <span>Upload</span></label>
<input class="hide" type="file" id="node-input-certfile">
<span id="device-config-certname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
<button class="red-ui-button red-ui-button-small" id="device-config-button-cert-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
</span>
<input type="hidden" id="node-input-certname">
<input type="hidden" id="node-input-cert">
</div>
<div class="form-row x509-row">
<label for="node-input-x509key"><i class="fa fa-file-text-o"></i> <span>X.509 Key</span></label>
<span class="device-config-input-data">
<label class="red-ui-button" for="node-input-keyfile"><i class="fa fa-upload"></i> <span>Upload</span></label>
<input class="hide" type="file" id="node-input-keyfile">
<span id="device-config-keyname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
<button class="red-ui-button red-ui-button-small" id="device-config-button-key-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
</span>
<input type="hidden" id="node-input-keyname">
<input type="hidden" id="node-input-key">
</div>
<div class="form-row x509-row">
<label for="node-input-passphrase"><i class="icon-tag"></i> <span>Passphrase (optional)</span></label>
<input type="password" id="node-input-passphrase" placeholder="%provide passphrase if needed%">
</div>
<div class="form-row">
<label for="node-input-protocol"><i class="icon-tag"></i> <span>Protocol</span></label>
<select id="node-input-protocol">
<option value="amqp">AMQP</option>
<option value="amqpWs">AMQP_WS</option>
<option value="mqtt">MQTT</option>
<option value="mqttWs">MQTT_WS</option>
</select>
</div>
</div>
<div id="compact-device-tab-customDPS" style="display:none">
<div class="form-row dps-row">
<div><i class="icon-tag"></i> <span>Device Provisioning Payload (JSON)</span></div>
<div style="height: 250px;" class="node-json-editor" id="node-input-DPSpayload-editor"></div>
<input type="hidden" id="node-input-DPSpayload" autofocus="autofocus">
</div>
</div>
<div id="compact-device-tab-methods" style="display:none">
<div class="form-row node-input-device-methods-container-row" style="margin-bottom: 0px;">
<div id="node-input-device-methods-container-div"
style="box-sizing: border-box; border-radius: 5px;
height: 450px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
<ol id="node-input-device-methods-container" style="list-style-type:none; margin: 0;"></ol>
</div>
</div>
<div class="form-row">
<a href="#" class="editor-button editor-button-small" id="node-input-device-methods-add"
style="margin-top: 4px;"><i class="fa fa-plus"></i>
<span data-i18n="methods-addButton"></span></a>
</div>
</div>
<div id="compact-device-tab-iotedge" style="display:none">
<div class="form-row">
<label for="node-input-gatewayHostname"><i class="icon-tag"></i> <span>Hostname</span></label>
<input type="text" id="node-input-gatewayHostname" placeholder="%IoT Edge Hostname%">
</div>
<div class="form-row">
<label for="node-input-cafile"><i class="fa fa-file-text-o"></i> <span>X.509 Gateway CA Certificate</span></label>
<span class="device-config-input-data">
<label class="red-ui-button" for="node-input-cafile"><i class="fa fa-upload"></i> <span>Upload</span></label>
<input class="hide" type="file" id="node-input-cafile">
<span id="device-config-caname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
<button class="red-ui-button red-ui-button-small" id="device-config-button-ca-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
</span>
<input type="hidden" id="node-input-caname">
<input type="hidden" id="node-input-ca">
</div>
</div>
</div>
</script>
<script type="text/x-red" data-help-name="azureiotdevice">
<p>The Azure IoT Device node enables you to send telemetry and properties to, and receive settings and commands from the Azure IoT platform using Node-Red. The payload requires to have a certain structure.</p>
<p>How to use this node is documented on the <a href="https://github.com/iotblackbelt/node-red-contrib-azure-iot-device" _target="new">github page</a> for this project.</p>
</script>