Skip to content

feat(server): BACnet command prioritization (Priority_Array / Relinquish_Default)#43

Open
ravz wants to merge 2 commits into
masterfrom
feat/issue-40-priority-array
Open

feat(server): BACnet command prioritization (Priority_Array / Relinquish_Default)#43
ravz wants to merge 2 commits into
masterfrom
feat/issue-40-priority-array

Conversation

@ravz
Copy link
Copy Markdown
Collaborator

@ravz ravz commented Apr 30, 2026

Closes #40.

What

Adds BACnet command prioritization to server-side commandable objects (Analog_Value and Binary_Value). Writes to Present_Value now honor the priority field per BACnet spec, with Priority_Array and Relinquish_Default arbitrating the effective Present_Value.

CharacterString_Value is not commandable per BACnet 12.x and is unchanged.

Behavior

Scenario Result
Write Present_Value with priority N Sets Priority_Array[N]
Write Present_Value with no priority Defaults to priority 16
Write NULL to Present_Value at priority N Relinquishes slot N
Write to Priority_Array with array index 1..16 Sets/relinquishes that single slot
Write to Relinquish_Default Updates the fallback (NULL rejected)
Read Present_Value Returns the highest-priority non-NULL slot, else Relinquish_Default
Read Priority_Array Returns the raw 16-slot array
Read Relinquish_Default Returns the fallback
Out_Of_Service, Status_Flags, Reliability Unchanged — still object-level, not prioritized

Backward compatibility

  • addObject(name, primitive) (the existing Node-RED gateway publish path) seeds Relinquish_Default from the primitive, so a read with no active commands returns the same value as before.
  • Re-calling addObject with the same name updates Relinquish_Default (the server-sourced baseline) rather than overwriting Present_Value directly. Externally-commanded priority slots continue to win until relinquished.
  • Objects loaded from on-disk cache that pre-date this change are migrated on startup: dummy Priority_Array becomes a real 16-slot array, Relinquish_Default is seeded from the stored Present_Value, and Property_List is extended.
  • Binary_Value PV continues to use BACnet application tag BOOLEAN (the existing representation in this file), not ENUMERATED.

Implementation

All changes in bacnet_server.js:

  • Helpers: _isCommandable, _appTagFor, _emptyPriorityArray, _arbitratePresentValue, _findObject, _refreshPresentValue, _ensureCommandableShape.
  • New entry point handlePrioritizedWrite invoked from the writeProperty event handler before the existing modifyObject fallback.
  • addObject for AV/BV now creates the 16-slot PA, seeds RD, and registers both in PROPERTY_LIST.
  • Constructor cache-restore migrates legacy stored objects via _ensureCommandableShape.

Testing

The project has no test infrastructure, so I exercised the arbitration logic in a throwaway smoke test (20/20 assertions): empty PA → RD; priority slot ordering (5 > 8 > 16); NULL relinquish cascade; direct PA[i] write/relinquish; full-array PA write rejected; NULL RD write rejected; non-commandable types fall through; legacy AV migration (PA promoted, RD seeded, PROPERTY_LIST extended).

Test plan

  • Commission a server-side AV; from a remote BACnet client, WriteProperty PV at priority 8, confirm read of PV returns the commanded value and Priority_Array[8] holds it.
  • WriteProperty PV at priority 5 with a different value — confirm it wins.
  • WriteProperty PV NULL at priority 5 — confirm PV falls back to slot 8.
  • WriteProperty PV NULL at priority 8 — confirm PV falls back to Relinquish_Default.
  • WriteProperty Priority_Array with array index 3 — confirm only that slot changes.
  • WriteProperty Relinquish_Default — confirm read of PV reflects new RD when all slots NULL.
  • Restart node-red — confirm a previously-stored AV survives migration and reads correctly.
  • Same flow against a BV.

…jects

Implements Priority_Array + Relinquish_Default arbitration of Present_Value
for server-side Analog_Value and Binary_Value objects. Closes the gap noted
in issue #40 where writes simply overwrote PV regardless of priority.

Behavior:
- Each commandable object now stores a 16-slot Priority_Array. Each slot is
  either NULL or a commanded {value, type} pair.
- Each commandable object now exposes Relinquish_Default; reads return it
  when all 16 slots are NULL.
- Writes to Present_Value honor the WriteProperty `priority` field, defaulting
  to 16 when absent. Writing NULL relinquishes that slot.
- Direct writes to a single Priority_Array slot (via property array index 1..16)
  set or relinquish that slot. Full-array overwrites are rejected.
- Writes to Relinquish_Default update the fallback (NULL is rejected).
- Effective Present_Value is recomputed after every prioritized write so
  read paths need no changes.
- Object-level properties (Out_Of_Service, Status_Flags, Reliability, etc.)
  remain object-level; non-commandable properties fall through to the
  existing modifyObject path.

Backward compatibility:
- addObject(name, primitive) still accepts a primitive payload; it seeds
  Relinquish_Default so reads return the same value as before until a
  write arrives. Server-sourced refreshes (re-calling addObject with the
  same name) update Relinquish_Default rather than Present_Value, so
  externally-commanded priorities continue to win until relinquished.
- Objects loaded from on-disk cache that pre-date this change are migrated
  on startup: their dummy Priority_Array is replaced with a real 16-slot
  array, Relinquish_Default is seeded from the stored Present_Value, and
  Property_List is extended to advertise the priority properties.
- Binary_Value Present_Value continues to use BACnet application tag
  BOOLEAN (the prior representation in this file), not ENUMERATED.
Copilot AI review requested due to automatic review settings April 30, 2026 02:26
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements BACnet command prioritization for server-side commandable objects (Analog_Value and Binary_Value) by introducing Priority_Array / Relinquish_Default arbitration and routing eligible writes through the new priority-aware path.

Changes:

  • Add prioritized write handling for Present_Value, Priority_Array[i], and Relinquish_Default on AV/BV objects.
  • Add migration logic for cached legacy AV/BV objects to ensure they have 16-slot Priority_Array and Relinquish_Default.
  • Update addObject behavior so server-sourced updates adjust Relinquish_Default (baseline) rather than directly overwriting Present_Value for commandable objects.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread bacnet_server.js
Comment thread bacnet_server.js Outdated
Comment thread bacnet_server.js Outdated
Comment thread bacnet_server.js Outdated
Comment thread bacnet_server.js
- writeProperty handler: return after errorResponse so an UNKNOWN_OBJECT
  error is no longer followed by a duplicate Ack/emit when modifyObject
  fails (Copilot review #1).
- handlePrioritizedWrite: throws now carry errorClass/errorCode, mapped
  to specific BACnet errors. PA writes without a 1..16 array index now
  surface as INVALID_ARRAY_INDEX; NULL writes to RELINQUISH_DEFAULT now
  surface as VALUE_OUT_OF_RANGE. Catch in writeProperty handler reads
  these and falls back to PROPERTY/WRITE_ACCESS_DENIED for unknown
  errors (Copilot review #2).
- _ensureCommandableShape: BV's RD seed is now `false` (matching the
  BOOLEAN encoding used elsewhere in this file) instead of numeric 0
  when PV is missing/corrupt (Copilot review #3).
- _ensureCommandableShape: PROPERTY_LIST is now constructed from the
  object's own keys when absent on a legacy cached object, so migrated
  objects advertise their full property set instead of a [PA, RD] stub
  (Copilot review #4).
- addObject: commandable objects now have PV refreshed via arbitration
  immediately after creation, so a payload.value vs payload.relinquishDefault
  mismatch can't leave PV pointing at the raw payload while PA is empty
  (Copilot review #5).

Validated with 19/19 smoke-test assertions covering the new behaviors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Support BACnet command prioritization (Priority_Array / Relinquish_Default) for commandable objects

2 participants