@vitormnm/node-red-simple-opcua 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/client/lib/opcua-client-browser.js +46 -7
- package/client/lib/opcua-client-write-service.js +127 -33
- package/client/opcua-client.html +142 -74
- package/examples/flows_simple_opc.json +2851 -1
- package/package.json +1 -1
- package/server/opcua-server.html +106 -40
package/package.json
CHANGED
package/server/opcua-server.html
CHANGED
|
@@ -730,6 +730,30 @@
|
|
|
730
730
|
function getNamespaceOptions() {
|
|
731
731
|
return Array.isArray(editorState.nameSpaces) ? editorState.nameSpaces.slice().sort(function (left, right) { return left.id - right.id; }) : [];
|
|
732
732
|
}
|
|
733
|
+
|
|
734
|
+
function getDefinedObjectTypeNames() {
|
|
735
|
+
var names = [];
|
|
736
|
+
(editorState.objectsTypes || []).forEach(function (ot) {
|
|
737
|
+
if (ot && ot.name) names.push(String(ot.name));
|
|
738
|
+
});
|
|
739
|
+
return names;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
function buildObjectTypeSelect(id, currentValue) {
|
|
744
|
+
var names = getDefinedObjectTypeNames();
|
|
745
|
+
var cv = String(currentValue || "");
|
|
746
|
+
var opts = "";
|
|
747
|
+
var noneSelected = (cv === "") ? " selected" : "";
|
|
748
|
+
opts += "<option value=\"\"" + noneSelected + ">\u2014 none \u2014</option>";
|
|
749
|
+
names.forEach(function (n) {
|
|
750
|
+
var esc = n.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
751
|
+
var sel = (n === cv) ? " selected" : "";
|
|
752
|
+
opts += "<option value=\"" + esc + "\"" + sel + ">" + esc + "</option>";
|
|
753
|
+
});
|
|
754
|
+
return "<select id=\"" + id + "\">" + opts + "</select>";
|
|
755
|
+
}
|
|
756
|
+
|
|
733
757
|
function getNamespaceLabel(namespaceId) {
|
|
734
758
|
var match = getNamespaceOptions().find(function (item) { return item.id === normalizeNamespaceId(namespaceId); });
|
|
735
759
|
return match ? String(match.id) + " - " + match.name : String(normalizeNamespaceId(namespaceId));
|
|
@@ -900,43 +924,72 @@
|
|
|
900
924
|
return "fa-tag";
|
|
901
925
|
}
|
|
902
926
|
|
|
903
|
-
|
|
927
|
+
// ── performance helpers ───────────────────────────────────────────
|
|
928
|
+
var _renderTreePending = false;
|
|
929
|
+
|
|
930
|
+
function debounce(fn, delay) {
|
|
931
|
+
var timer;
|
|
932
|
+
return function () { var ctx = this, args = arguments; clearTimeout(timer); timer = setTimeout(function () { fn.apply(ctx, args); }, delay); };
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function escapeHtml(v) {
|
|
936
|
+
return String(v || "").replace(/[&<>"']/g, function (c) {
|
|
937
|
+
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function appendNodeToFrag(frag, path, depth, ancestorMatched) {
|
|
904
942
|
var item = getAtPath(editorState, path);
|
|
905
|
-
if (!item) return
|
|
943
|
+
if (!item) return;
|
|
906
944
|
var nodeClass = nodeClassFromPath(path);
|
|
907
945
|
var hasChildren = nodeClass !== "Variable" && nodeClass !== "Alarm" && nodeClass !== "Namespace" && getChildrenByPath(path).length > 0;
|
|
908
|
-
var expanded = isExpanded(path, depth < 1 || treeSearchTerm);
|
|
946
|
+
var expanded = isExpanded(path, depth < 1 || !!treeSearchTerm);
|
|
909
947
|
var subtreeVisible = !!ancestorMatched || nodeMatchesSearch(path);
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
for (var i = 0; i < depth; i +=
|
|
913
|
-
|
|
914
|
-
row
|
|
915
|
-
row.
|
|
916
|
-
row.
|
|
917
|
-
row.
|
|
918
|
-
|
|
948
|
+
|
|
949
|
+
var indents = "";
|
|
950
|
+
for (var i = 0; i < depth; i++) indents += '<span class="opcua-tree-indent"></span>';
|
|
951
|
+
|
|
952
|
+
var row = document.createElement("div");
|
|
953
|
+
row.className = "opcua-tree-row" + (path === selectedPath ? " is-selected" : "");
|
|
954
|
+
row.setAttribute("data-path", path);
|
|
955
|
+
row.innerHTML = indents
|
|
956
|
+
+ '<span class="opcua-tree-twisty">' + (hasChildren ? '<i class="fa ' + (expanded ? "fa-caret-down" : "fa-caret-right") + '"></i>' : "") + "</span>"
|
|
957
|
+
+ '<span class="opcua-tree-icon"><i class="fa ' + iconForNodeClass(nodeClass) + '"></i></span>'
|
|
958
|
+
+ '<span class="opcua-tree-label">' + escapeHtml(item.name || "(unnamed)") + "</span>"
|
|
959
|
+
+ '<span class="opcua-tree-type">' + escapeHtml(nodeClass) + "</span>";
|
|
960
|
+
frag.appendChild(row);
|
|
961
|
+
|
|
919
962
|
if (hasChildren && expanded) {
|
|
920
963
|
getChildrenByPath(path).forEach(function (childPath) {
|
|
921
964
|
if (!treeSearchTerm || subtreeVisible || branchHasSearchMatch(childPath)) {
|
|
922
|
-
|
|
965
|
+
appendNodeToFrag(frag, childPath, depth + 1, subtreeVisible);
|
|
923
966
|
}
|
|
924
967
|
});
|
|
925
968
|
}
|
|
926
|
-
return fragment;
|
|
927
969
|
}
|
|
928
970
|
|
|
929
971
|
function renderTree() {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
list.
|
|
935
|
-
return;
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
if (!
|
|
939
|
-
|
|
972
|
+
if (_renderTreePending) return;
|
|
973
|
+
_renderTreePending = true;
|
|
974
|
+
setTimeout(function () {
|
|
975
|
+
_renderTreePending = false;
|
|
976
|
+
var list = document.getElementById("node-input-object-list");
|
|
977
|
+
if (!list) return;
|
|
978
|
+
var frag = document.createDocumentFragment();
|
|
979
|
+
var roots = getTopLevelPaths();
|
|
980
|
+
if (!roots.length) {
|
|
981
|
+
var empty = document.createElement("div");
|
|
982
|
+
empty.className = "opcua-tree-empty";
|
|
983
|
+
empty.textContent = "No OPC UA items. Use Add folder, Add object, or Add namespace.";
|
|
984
|
+
frag.appendChild(empty);
|
|
985
|
+
} else {
|
|
986
|
+
roots.forEach(function (path) {
|
|
987
|
+
if (!treeSearchTerm || branchHasSearchMatch(path)) appendNodeToFrag(frag, path, 0, false);
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
list.innerHTML = "";
|
|
991
|
+
list.appendChild(frag);
|
|
992
|
+
}, 0);
|
|
940
993
|
}
|
|
941
994
|
|
|
942
995
|
function renderBreadcrumbs() {
|
|
@@ -972,8 +1025,10 @@
|
|
|
972
1025
|
parentPath: path,
|
|
973
1026
|
kind: kind,
|
|
974
1027
|
name: kind === "variable" ? "newVariable" : kind === "folder" ? "newFolder" : kind === "objecttype" ? "newObjectType" : kind === "alarm" ? "newAlarm" : kind === "method" ? "newMethod" : "newObject",
|
|
1028
|
+
displayName: "",
|
|
975
1029
|
dataType: "Int32",
|
|
976
1030
|
value: "",
|
|
1031
|
+
access: "readwrite",
|
|
977
1032
|
objectsType: "",
|
|
978
1033
|
alarmType: "levelAlarm",
|
|
979
1034
|
variableNodeId: "",
|
|
@@ -1012,16 +1067,19 @@
|
|
|
1012
1067
|
if (kind === "variable") {
|
|
1013
1068
|
target.push(normalizeVariable({
|
|
1014
1069
|
name: pendingCreate.name,
|
|
1070
|
+
displayName: pendingCreate.displayName || "",
|
|
1015
1071
|
type: pendingCreate.dataType,
|
|
1016
|
-
value: pendingCreate.value
|
|
1072
|
+
value: pendingCreate.value,
|
|
1073
|
+
access: pendingCreate.access || "readwrite"
|
|
1017
1074
|
}));
|
|
1018
1075
|
} else if (kind === "folder") {
|
|
1019
|
-
target.push(normalizeBranch({ name: pendingCreate.name }));
|
|
1076
|
+
target.push(normalizeBranch({ name: pendingCreate.name, displayName: pendingCreate.displayName || "" }));
|
|
1020
1077
|
} else if (kind === "objecttype") {
|
|
1021
|
-
target.push(normalizeBranch({ name: pendingCreate.name, objectsType: pendingCreate.objectsType || "" }));
|
|
1078
|
+
target.push(normalizeBranch({ name: pendingCreate.name, displayName: pendingCreate.displayName || "", objectsType: pendingCreate.objectsType || "" }));
|
|
1022
1079
|
target[target.length - 1].nodeId = buildGeneratedNodeIdForPath(branchTargetPath + "." + (target.length - 1));
|
|
1023
1080
|
} else if (kind === "alarm") {
|
|
1024
1081
|
target.push(normalizeAlarm({
|
|
1082
|
+
displayName: pendingCreate.displayName || "",
|
|
1025
1083
|
name: pendingCreate.name,
|
|
1026
1084
|
type: pendingCreate.alarmType,
|
|
1027
1085
|
variableNodeId: pendingCreate.variableNodeId,
|
|
@@ -1038,9 +1096,9 @@
|
|
|
1038
1096
|
digitalMessage: pendingCreate.digitalMessage
|
|
1039
1097
|
}));
|
|
1040
1098
|
} else if (kind === "method") {
|
|
1041
|
-
target.push(normalizeMethod({ name: pendingCreate.name }));
|
|
1099
|
+
target.push(normalizeMethod({ name: pendingCreate.name, displayName: pendingCreate.displayName || "" }));
|
|
1042
1100
|
} else {
|
|
1043
|
-
target.push(normalizeBranch({ name: pendingCreate.name }));
|
|
1101
|
+
target.push(normalizeBranch({ name: pendingCreate.name, displayName: pendingCreate.displayName || "" }));
|
|
1044
1102
|
}
|
|
1045
1103
|
expansionState[parentPath] = true;
|
|
1046
1104
|
pendingCreate = null;
|
|
@@ -1060,12 +1118,14 @@
|
|
|
1060
1118
|
panel.append('<div class="form-row"><label>Parent</label><input type="text" id="opcua-create-parent" readonly></div>');
|
|
1061
1119
|
panel.append('<div class="form-row"><label>Type</label><input type="text" id="opcua-create-kind" readonly></div>');
|
|
1062
1120
|
panel.append('<div class="form-row"><label>Name</label><input type="text" id="opcua-create-name"></div>');
|
|
1121
|
+
panel.append('<div class="form-row"><label>displayName</label><input type="text" id="opcua-create-displayname" placeholder="Leave blank to use browseName"></div>');
|
|
1063
1122
|
if (pendingCreate.kind === "variable") {
|
|
1064
1123
|
panel.append('<div class="form-row"><label>dataType</label><select id="opcua-create-type"><option value="Int16">Int16</option><option value="Int32">Int32</option><option value="Float">Float</option><option value="Boolean">Boolean</option><option value="String">String</option></select></div>');
|
|
1065
1124
|
panel.append('<div class="form-row"><label>Value</label><input type="text" id="opcua-create-value"></div>');
|
|
1125
|
+
panel.append('<div class="form-row"><label>Access</label><select id="opcua-create-access"><option value="readwrite">readwrite</option><option value="readonly">readonly</option></select></div>');
|
|
1066
1126
|
}
|
|
1067
1127
|
if (pendingCreate.kind === "objecttype") {
|
|
1068
|
-
panel.append('<div class="form-row"><label>objectsType</label
|
|
1128
|
+
panel.append('<div class="form-row"><label>objectsType</label>' + buildObjectTypeSelect("opcua-create-objectstype", pendingCreate.objectsType || "") + '</div>');
|
|
1069
1129
|
}
|
|
1070
1130
|
if (pendingCreate.kind === "alarm") {
|
|
1071
1131
|
panel.append('<div class="form-row"><label>alarmType</label><select id="opcua-create-alarm-type"><option value="levelAlarm">levelAlarm</option><option value="digitalAlarm">digitalAlarm</option></select></div>');
|
|
@@ -1089,9 +1149,11 @@
|
|
|
1089
1149
|
$("#opcua-create-parent").val(pendingCreate.parentPath);
|
|
1090
1150
|
$("#opcua-create-kind").val(pendingCreate.kind);
|
|
1091
1151
|
$("#opcua-create-name").val(pendingCreate.name);
|
|
1152
|
+
$("#opcua-create-displayname").val(pendingCreate.displayName || "");
|
|
1092
1153
|
$("#opcua-create-type").val(pendingCreate.dataType);
|
|
1093
1154
|
$("#opcua-create-value").val(pendingCreate.value);
|
|
1094
1155
|
$("#opcua-create-objectstype").val(pendingCreate.objectsType);
|
|
1156
|
+
$("#opcua-create-access").val(pendingCreate.access || "readwrite");
|
|
1095
1157
|
$("#opcua-create-alarm-type").val(pendingCreate.alarmType);
|
|
1096
1158
|
$("#opcua-create-variable-nodeid").val(pendingCreate.variableNodeId);
|
|
1097
1159
|
$("#opcua-create-severity").val(pendingCreate.severity);
|
|
@@ -1130,15 +1192,16 @@
|
|
|
1130
1192
|
panel.append('<div class="form-row"><label>namespace</label><select id="opcua-detail-namespace"></select></div>');
|
|
1131
1193
|
panel.append('<div class="form-row"><label>nodeId</label><div class="opcua-nodeid-field"><span class="opcua-nodeid-prefix">' + getNodeIdPrefix(namespaceId) + '</span><input type="text" id="opcua-detail-nodeid"' + (nodeIdLocked ? ' readonly title="Generated automatically for object type models."' : '') + '><a href="#" id="opcua-detail-copy-nodeid" class="editor-button editor-button-small"><i class="fa fa-copy"></i> Copy</a></div></div>');
|
|
1132
1194
|
panel.append('<div class="form-row"><label>Description</label><input type="text" id="opcua-detail-description"></div>');
|
|
1195
|
+
panel.append('<div class="form-row"><label>displayName</label><input type="text" id="opcua-detail-displayname" placeholder="Leave blank to use browseName"></div>');
|
|
1133
1196
|
if (nodeClass === "ObjectType") {
|
|
1134
|
-
panel.append('<div class="form-row"><label>objectsType</label
|
|
1197
|
+
panel.append('<div class="form-row"><label>objectsType</label>' + buildObjectTypeSelect("opcua-detail-objectstype", item.objectsType || "") + '</div>');
|
|
1135
1198
|
}
|
|
1136
1199
|
if (nodeClass === "Variable") {
|
|
1137
1200
|
panel.append('<div class="form-row"><label>dataType</label><select id="opcua-detail-type"><option value="Int16">Int16</option><option value="Int32">Int32</option><option value="Float">Float</option><option value="Boolean">Boolean</option><option value="String">String</option></select></div>');
|
|
1138
1201
|
panel.append('<div class="form-row"><label>Value</label><input type="text" id="opcua-detail-value"></div>');
|
|
1202
|
+
panel.append('<div class="form-row"><label>Access</label><select id="opcua-detail-access"><option value="readwrite">readwrite</option><option value="readonly">readonly</option></select></div>');
|
|
1139
1203
|
}
|
|
1140
1204
|
if (nodeClass === "Method") {
|
|
1141
|
-
panel.append('<div class="form-row"><label>displayName</label><input type="text" id="opcua-detail-displayname"></div>');
|
|
1142
1205
|
panel.append('<hr style="margin:8px 0; border-color:#e3e3e3;">');
|
|
1143
1206
|
panel.append('<div style="font-size:11px;font-weight:700;text-transform:uppercase;color:#666;margin-bottom:4px;">Inputs</div>');
|
|
1144
1207
|
var inputsDiv = $('<div id="opcua-detail-inputs"></div>').appendTo(panel);
|
|
@@ -1203,9 +1266,9 @@
|
|
|
1203
1266
|
$("#opcua-detail-namespace").append($("<option></option>").val(option.id).text(getNamespaceLabel(option.id)));
|
|
1204
1267
|
});
|
|
1205
1268
|
$("#opcua-detail-namespace").val(String(namespaceId));
|
|
1206
|
-
|
|
1207
|
-
if (nodeClass === "ObjectType") $("#opcua-detail-objectstype").val(item.objectsType || "");
|
|
1208
|
-
if (nodeClass === "Variable") { $("#opcua-detail-type").val(item.type || "Int32"); $("#opcua-detail-value").val(item.value !== undefined ? item.value : ""); }
|
|
1269
|
+
$("#opcua-detail-displayname").val(item.displayName || "");
|
|
1270
|
+
if (nodeClass === "ObjectType") { $("#opcua-detail-objectstype").val(item.objectsType || ""); }
|
|
1271
|
+
if (nodeClass === "Variable") { $("#opcua-detail-type").val(item.type || "Int32"); $("#opcua-detail-value").val(item.value !== undefined ? item.value : ""); $("#opcua-detail-access").val(item.access || "readwrite"); }
|
|
1209
1272
|
if (nodeClass === "Alarm") {
|
|
1210
1273
|
$("#opcua-detail-alarm-type").val(item.type || "levelAlarm");
|
|
1211
1274
|
$("#opcua-detail-variable-nodeid").val(item.variableNodeId || "");
|
|
@@ -1398,8 +1461,9 @@
|
|
|
1398
1461
|
}
|
|
1399
1462
|
});
|
|
1400
1463
|
$(document).on("input", "#opcua-detail-description", function () { updateNode(selectedPath, { description: $(this).val() }); });
|
|
1401
|
-
$(document).on("
|
|
1464
|
+
$(document).on("change", "#opcua-detail-objectstype", function () { updateNode(selectedPath, { objectsType: $(this).val() }); });
|
|
1402
1465
|
$(document).on("change", "#opcua-detail-type", function () { updateNode(selectedPath, { type: $(this).val() }); });
|
|
1466
|
+
$(document).on("change", "#opcua-detail-access", function () { updateNode(selectedPath, { access: $(this).val() }); });
|
|
1403
1467
|
$(document).on("input", "#opcua-detail-value", function () { updateNode(selectedPath, { value: $(this).val() }); });
|
|
1404
1468
|
$(document).on("change", "#opcua-detail-alarm-type", function () {
|
|
1405
1469
|
updateNode(selectedPath, { type: $(this).val() });
|
|
@@ -1418,9 +1482,11 @@
|
|
|
1418
1482
|
$(document).on("input", "#opcua-detail-normalstatevalue", function () { updateNode(selectedPath, { normalStateValue: Number($(this).val() || 0) }); });
|
|
1419
1483
|
$(document).on("input", "#opcua-detail-digitalmessage", function () { updateNode(selectedPath, { digitalMessage: $(this).val() }); });
|
|
1420
1484
|
$(document).on("input", "#opcua-create-name", function () { if (pendingCreate) pendingCreate.name = $(this).val(); });
|
|
1485
|
+
$(document).on("input", "#opcua-create-displayname", function () { if (pendingCreate) pendingCreate.displayName = $(this).val(); });
|
|
1421
1486
|
$(document).on("change", "#opcua-create-type", function () { if (pendingCreate) pendingCreate.dataType = $(this).val(); });
|
|
1422
1487
|
$(document).on("input", "#opcua-create-value", function () { if (pendingCreate) pendingCreate.value = $(this).val(); });
|
|
1423
|
-
$(document).on("
|
|
1488
|
+
$(document).on("change", "#opcua-create-objectstype", function () { if (pendingCreate) pendingCreate.objectsType = $(this).val(); });
|
|
1489
|
+
$(document).on("change", "#opcua-create-access", function () { if (pendingCreate) pendingCreate.access = $(this).val(); });
|
|
1424
1490
|
$(document).on("change", "#opcua-create-alarm-type", function () {
|
|
1425
1491
|
if (pendingCreate) {
|
|
1426
1492
|
pendingCreate.alarmType = $(this).val();
|
|
@@ -1487,10 +1553,10 @@
|
|
|
1487
1553
|
$("#node-input-close-tree-modal").off("click").on("click", function (event) { event.preventDefault(); closeTreeModal(); });
|
|
1488
1554
|
$("#node-input-tree-modal").off("click").on("click", function (event) { if (event.target === this) closeTreeModal(); });
|
|
1489
1555
|
|
|
1490
|
-
$("#node-input-tree-search").off("input").on("input", function () {
|
|
1556
|
+
$("#node-input-tree-search").off("input").on("input", debounce(function () {
|
|
1491
1557
|
treeSearchValue = $(this).val(); treeSearchTerm = normalizeSearchTerm(treeSearchValue);
|
|
1492
1558
|
$("#node-input-tree-search-clear").toggle(!!treeSearchTerm); renderTree();
|
|
1493
|
-
});
|
|
1559
|
+
}, 200));
|
|
1494
1560
|
$("#node-input-tree-search-clear").off("click").on("click", function (event) {
|
|
1495
1561
|
event.preventDefault(); treeSearchValue = ""; treeSearchTerm = "";
|
|
1496
1562
|
$("#node-input-tree-search").val(""); $(this).hide(); renderTree();
|
|
@@ -1545,4 +1611,4 @@
|
|
|
1545
1611
|
<p>Supported variable types: <code>Int32</code>, <code>Float</code>, <code>Boolean</code>, <code>String</code>.</p>
|
|
1546
1612
|
<p>Supported access modes: <code>readonly</code> and <code>readwrite</code>.</p>
|
|
1547
1613
|
<p>Authentication can be configured in the editor with a local username list and bcrypt password hashes, and anonymous login can be disabled.</p>
|
|
1548
|
-
</script>
|
|
1614
|
+
</script>
|