diff --git a/bacnet_client.js b/bacnet_client.js index fd43309..2f9122a 100644 --- a/bacnet_client.js +++ b/bacnet_client.js @@ -1648,6 +1648,7 @@ class BacnetClient extends EventEmitter { let objectType = point.meta.objectId.type; let resolvedAppTag = that._resolveAppTag(objectType, options.appTag); let resolvedValue = that._coerceWriteValue(value, resolvedAppTag); + let resolvedPropertyId = that._resolveWritePropertyId(point.propertyId); let writeObject = { address: addressObject, @@ -1657,7 +1658,7 @@ class BacnetClient extends EventEmitter { }, values: { property: { - id: 85, + id: resolvedPropertyId, index: point.meta.arrayIndex, }, value: [ @@ -1691,7 +1692,7 @@ class BacnetClient extends EventEmitter { that.client.writeProperty( point.address, point.objectId, - baEnum.PropertyIdentifier.PRESENT_VALUE, + point.values.property.id, point.values.value, point.options, (err, value) => { @@ -2386,6 +2387,76 @@ class BacnetClient extends EventEmitter { } } + _resolveWritePropertyId(propertyId) { + + // Default to Present_Value to preserve existing behavior when no property is supplied. + if (propertyId === null || typeof propertyId === "undefined") { + return baEnum.PropertyIdentifier.PRESENT_VALUE; + } + + // Allow raw numeric BACnet property identifiers. + if (typeof propertyId === "number" && Number.isInteger(propertyId)) { + return propertyId; + } + + if (typeof propertyId === "string") { + const trimmed = propertyId.trim(); + + // Empty / whitespace-only string also defaults to Present_Value, matching the + // null/undefined branch above. The Write node UI uses an empty default so the + // placeholder ("PRESENT_VALUE") shows. + if (trimmed === "") { + return baEnum.PropertyIdentifier.PRESENT_VALUE; + } + + // Numeric strings ("85") are treated as raw numeric BACnet property identifiers, + // since users see numeric IDs alongside names in the UI suggestions. + if (/^\d+$/.test(trimmed)) { + return parseInt(trimmed, 10); + } + + const upper = trimmed.toUpperCase(); + + // First try a direct enum lookup using the uppercase string exactly as given. + // Example: "present_value" -> "PRESENT_VALUE" + const exactResolvedPropertyId = baEnum.PropertyIdentifier[upper]; + if (typeof exactResolvedPropertyId === "number") { + return exactResolvedPropertyId; + } + + // Final fallback: compare without separators so camelCase/PascalCase can still match + // BACnet enum names that use underscores. + // Example: + // user input "presentValue" -> "PRESENTVALUE" + // enum key "PRESENT_VALUE" -> "PRESENTVALUE" + // + const underscored = upper.replace(/[\s-]+/g, "_"); + const underscoredResolvedPropertyId = baEnum.PropertyIdentifier[underscored]; + if (typeof underscoredResolvedPropertyId === "number") { + return underscoredResolvedPropertyId; + } + + // Separator-insensitiver fallback: PresentValue -> PRESENT_VALUE + // This strips off separators in both the user input and the enum key + // to try to find a match + const compact = upper.replace(/[\s_-]+/g, ""); + + // The key should already be in uppercase but just in case, let's force it. + // This protects against somebody doing something different in the future. + const matchedKey = Object.keys(baEnum.PropertyIdentifier).find( + (key) => key.toUpperCase().replace(/_/g, "") === compact + ); + + if (matchedKey) { + return baEnum.PropertyIdentifier[matchedKey]; + } + + throw new Error(`Invalid BACnet property name: ${propertyId}`); + } + + throw new Error(`Invalid BACnet property id type: ${typeof propertyId}`); + } + _coerceWriteValue(value, appTag) { switch (appTag) { case 9: // ENUMERATED - binary objects expect 0 (inactive) or 1 (active) diff --git a/bacnet_write.html b/bacnet_write.html index 945331b..531da84 100644 --- a/bacnet_write.html +++ b/bacnet_write.html @@ -10,6 +10,7 @@ name: { value: "" }, applicationTag: { value: "-1" }, priority: { value: "16" }, + propertyId: { value: "" }, pointsToWrite: { value: [] }, writeDevices: { value: [] }, hiddenDeployToggle: { value: false }, @@ -515,6 +516,7 @@ document.getElementById("node-input-applicationTag").value = node.applicationTag; document.getElementById("node-input-priority").value = node.priority; + document.getElementById("node-input-propertyId").value = node.propertyId || ""; var menu = document.querySelector(".context-menu"); window.addEventListener("click", (event) => { @@ -985,6 +987,57 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
Write List

Properties

    - This tab allows the user to choose the Application Tag and Priority to be used in a Write function. Please make sure + This tab allows the user to choose the Application Tag, BACnet Property, and Priority to be used in a Write function. + The Property field accepts any BACnet property — by name (e.g., PRESENT_VALUE, + RELINQUISH_DEFAULT, OUT_OF_SERVICE, also presentValue or + relinquish_default) or by numeric identifier (e.g., 85). Common picks are offered as + autocomplete suggestions; leaving the field blank defaults to PRESENT_VALUE. To override per-point, + set propertyId on individual entries in msg.options.pointsToWrite. Please make sure the device / controller is configured correctly to accept the write command.

diff --git a/bacnet_write.js b/bacnet_write.js index 83e71ec..e7adb9e 100644 --- a/bacnet_write.js +++ b/bacnet_write.js @@ -8,6 +8,7 @@ module.exports = function (RED) { var node = this; node.priority = config.priority; node.appTag = config.applicationTag; + node.propertyId = config.propertyId; node.pointsToWrite = config.pointsToWrite; node.writeDevices = config.writeDevices; @@ -18,13 +19,19 @@ module.exports = function (RED) { let value = msg.payload == "null" ? null : msg.payload; let priority = node.priority == "null" ? null : parseInt(node.priority); + // Stamp the configured BACnet property onto each point so doWrite() resolves it per-point. + // Per-point propertyId already on the point (e.g., supplied via msg) wins. + let pointsToWrite = Array.isArray(node.pointsToWrite) + ? node.pointsToWrite.map((p) => ({ ...p, propertyId: p.propertyId ?? node.propertyId })) + : node.pointsToWrite; + let output = { type: "Write", id: node.id, options: { priority: priority, appTag: parseInt(node.appTag), - pointsToWrite: node.pointsToWrite + pointsToWrite: pointsToWrite }, value: value, outputType: {