Skip to content

Properties

Properties are configuration values that define how a device behaves.


What is a Property?

+------------------+      +------------------+      +------------------+
|    PQ System     | ---> |     Adapter      | ---> | Physical Device  |
+------------------+      +------------------+      +------------------+
  Admin sets:              Stores locally           Applies config:
  "unlock_duration: 5"     Sends to device          door stays
                                                    unlocked for 5s

Properties are:

  • Configuration - set by administrator, not by device
  • Persistent - stored in PQ database
  • Rarely changed - unlike status which changes constantly
  • Applied to device - adapter sends to device when changed

Property vs Status vs Event

Aspect Property Status Event
Who sets Admin/User Device Device
Direction PQ -> Device Device -> PQ Device -> PQ
Frequency Rarely changes Changes often One-time occurrence
Example "unlock duration: 5s" "door is open" "door was opened"
PROPERTY: "Door unlock duration = 5 seconds"
          (configured by admin, applied to device)

STATUS:   "Door is currently unlocked"
          (reported by device, changes over time)

EVENT:    "Door was unlocked at 10:15"
          (happened once, recorded in history)

Common Property Types

Device Properties

Property Type Description
name string Display name
location string Physical location
ip_address string Network address
port int Communication port

Door Properties

Property Type Description
unlock_duration int Seconds door stays unlocked
held_open_timeout int Seconds before "door held" alarm
force_open_alarm bool Trigger alarm on forced entry
relock_on_close bool Lock immediately when door closes

Reader Properties

Property Type Description
led_mode enum LED behavior
beep_on_read bool Sound on card read
direction enum Entry/Exit/Both

Property Definition in YAML

Properties are defined in adapter-registration.yaml:

device_types:
  - type_id: Door
    properties:
      - name: unlock_duration
        type: int
        default: 5
        description: Seconds door stays unlocked after valid access
        range: [1, 60]

      - name: held_open_timeout
        type: int
        default: 30
        description: Seconds before door-held alarm triggers
        range: [0, 300]

      - name: force_open_alarm
        type: bool
        default: true
        description: Generate alarm when door forced open

      - name: relock_on_close
        type: bool
        default: false
        description: Automatically lock when door closes

Property Flow

Admin changes "unlock_duration" to 10s
                |
                v
+----------------------------------+
|           PQ System              |
| Stores property in database      |
| Sends SetProperty command        |
+----------------------------------+
                |
                v
         [NATS message]
                |
                v
+----------------------------------+
|            Adapter               |
| Receives SetProperty command     |
| Updates local cache              |
| Sends to device via protocol     |
+----------------------------------+
                |
                v
+----------------------------------+
|        Physical Device           |
| Applies new configuration        |
| Door now unlocks for 10s         |
+----------------------------------+

Implementing Property Handling

public class DoorController : Thing
{
    // property backing fields with defaults
    private int _unlockDuration = 5;
    private int _heldOpenTimeout = 30;
    private bool _forceOpenAlarm = true;

    // property accessors
    public int UnlockDuration
    {
        get => _unlockDuration;
        set
        {
            if (value == _unlockDuration) return;
            _unlockDuration = value;
            ApplyToDevice();  // send to physical device
        }
    }

    private async void ApplyToDevice()
    {
        await _protocol.SetDoorConfig(DeviceId, new DoorConfig
        {
            UnlockDuration = _unlockDuration,
            HeldOpenTimeout = _heldOpenTimeout,
            ForceOpenAlarm = _forceOpenAlarm
        });
    }
}

Property Sync on Startup

When adapter starts, properties need to sync:

Adapter starts
      |
      v
+------------------+
| Fetch properties |  <-- from PQ database
| from PQ API      |
+------------------+
      |
      v
+------------------+
| Apply to local   |
| Thing objects    |
+------------------+
      |
      v
+------------------+
| Send to devices  |  <-- via protocol
| (if online)      |
+------------------+
public override async Task Start(CancellationToken ct)
{
    // 1. fetch stored properties
    var properties = await _pqApi.GetDeviceProperties(_adapterId, ct);

    // 2. apply to things
    foreach (var (deviceId, props) in properties)
    {
        var thing = FindThing(deviceId);
        thing.ApplyProperties(props);
    }

    // 3. sync to devices
    foreach (var device in GetOnlineDevices())
    {
        await SyncPropertiesToDevice(device, ct);
    }
}

Property Types

YAML Type C# Type Example
string string "Main Entrance"
int int 5
bool bool true
enum enum Direction.Entry
float float 0.75
guid Guid "a1b2c3d4-..."
TypeName TypeName? Device reference (resolved by GUID)
TypeName[] IReadOnlyList<TypeName> Array of device references

Device Reference Properties

When property type matches a type_id in your adapter, it becomes a device reference:

properties:
  target_door:
    type: Door           # single reference (nullable)
  partitions:
    type: Partition[]       # array reference (many-to-many)

See Things - Device References for details.

Enum Properties

properties:
  - name: direction
    type: enum
    values: [entry, exit, both]
    default: both
public enum ReaderDirection { Entry, Exit, Both }

public ReaderDirection Direction { get; set; } = ReaderDirection.Both;

Sensitive Properties

Mark passwords, API keys, and other secrets with sensitive: true:

properties:
  password:
    type: string
    required: false
    sensitive: true
    category: connection
    description: Authentication password

sensitive: true does two things:

  • Masks the UI field - the configuration input renders as a password field (dots instead of text) rather than plain text.
  • Masks the audit trail - property-change records store a masked placeholder, so the secret never lands in audit history.

Not encrypted at rest

sensitive only masks display. The value is stored in the PQ database in plaintext - anyone with database access or device-property API access can read it. Do not treat sensitive as at-rest protection.


Property Validation

Validate before applying:

public int UnlockDuration
{
    get => _unlockDuration;
    set
    {
        // validate range
        if (value < 1 || value > 60)
            throw new ArgumentOutOfRangeException(
                nameof(value),
                "Unlock duration must be 1-60 seconds");

        _unlockDuration = value;
        ApplyToDevice();
    }
}

Properties vs Hardcoded Values

// WRONG - hardcoded, cannot be changed
await _protocol.UnlockDoor(deviceId, duration: 5);

// RIGHT - use property
await _protocol.UnlockDoor(deviceId, duration: door.UnlockDuration);

If a value might need to change per-device, make it a property.


Common Mistakes

1. Not Syncing on Startup

// WRONG - uses defaults even when PQ has different values
public override Task Start(CancellationToken ct)
{
    await _protocol.Connect(ct);
    // forgot to fetch and apply stored properties!
}

2. Not Validating

// WRONG - accepts any value
public int UnlockDuration { get; set; }

// device might not support duration > 60s, will fail silently

3. Mixing Properties and Status

// WRONG - "isLocked" is status, not property
properties:
  - name: isLocked
    type: bool

// RIGHT - "isLocked" is status (device reports it)
// Property would be "defaultLockState" (what admin wants)

Properties in Device Hierarchy

Properties can exist at different levels:

Adapter
  |-- default_timeout: 30        <-- adapter-level default
  |
  +-- Door Controller
        |-- communication_retry: 3
        |
        +-- Door 1
        |     |-- unlock_duration: 5
        |     +-- held_open_timeout: 30
        |
        +-- Door 2
              |-- unlock_duration: 10  <-- different value
              +-- held_open_timeout: 60

Child can inherit from parent or override with specific value.


Next: Things - Device hierarchy