UNPKG

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
<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>