Property Definition¶
Property definitions within adapter-registration.yaml.
Properties can be defined at:
- device type level (
device_types[].properties) - custom function level (
custom_functions[].properties) - bootstrap config (
bootstrap_config) - access profile field level (
access-model.yamlperson_profiles[].fields[])
Structure¶
properties:
property_name:
type: string # required - data type
required: true # optional - is value mandatory?
default: "value" # optional - default value
description: "desc" # optional - property description
# type-specific fields...
Common Fields¶
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type |
string | yes | - | Property data type |
required |
bool | no | false |
Marks mandatory property |
default |
any | no | null |
Default value |
description |
string/object | no | "" |
Description (supports multi-language) |
category |
string | no | null |
UI grouping category (overrides function-level category) |
sensitive |
bool | no | false |
Marks sensitive data (passwords, keys) |
private |
bool | no | false |
When true, property is not exposed to UI (internal to adapter) |
Multi-language Description¶
Description supports two formats:
# simple string
description: "Door address on the panel"
# multi-language dictionary
description:
en: "Door address on the panel"
cs: "Adresa dveri na panelu"
When processing, the en key is preferred, otherwise the first available value.
Who reads a description¶
Descriptions on device types and properties are published on the adapter's page and shown next to the setting, so the reader is the installer commissioning a site — not you, and not the next developer.
Say what the thing is and what filling it in does. Never explain how the protocol names or encodes it: command numbers, register or frame field names, and enumeration values of the wire protocol mean nothing to the reader and make a correct sentence unreadable.
- good:
description: "Alarm partition — a group of zones armed and disarmed together. Up to 32 per panel." - bad:
description: "Alarm partition. Device name type 0 is partition 1..32."— "type 0" is a protocol argument the reader has no way to interpret - good:
description: "RS-485 speed for downstream modules; all modules on the line must match." - bad:
description: "RS-485 port 1 downstream module baud rate (Command 108 CC_MSP1.baud_rate)"
Do not put protocol documentation into the published description. Keep any implementation note in the documentation system where you normally maintain it. A YAML comment is optional and is not required for adapter delivery.
Supported Types¶
Integer Types¶
| Type | C# Type | Range |
|---|---|---|
int |
int |
-2,147,483,648 to 2,147,483,647 |
uint |
uint |
0 to 4,294,967,295 |
long |
long |
-9,223,372,036,854,775,808 to ... |
ulong |
ulong |
0 to 18,446,744,073,709,551,615 |
short |
short |
-32,768 to 32,767 |
ushort |
ushort |
0 to 65,535 |
byte |
byte |
0 to 255 |
sbyte |
sbyte |
-128 to 127 |
Decimal Types¶
| Type | C# Type | Description |
|---|---|---|
float |
float |
single-precision floating point |
double |
double |
double-precision floating point |
decimal |
decimal |
high-precision decimal |
currency |
decimal |
alias for decimal (financial values) |
Other Primitive Types¶
| Type | C# Type | Description |
|---|---|---|
string |
string |
text string |
host |
HostAddress |
DNS hostname, IPv4, or IPv6 address |
ipv4 |
HostAddress |
IPv4 address only |
bool |
bool |
boolean true/false |
guid |
Guid |
UUID identifier |
datetime |
DateTime |
date and time |
datetimeoffset |
DateTimeOffset |
date and time with timezone |
timespan |
TimeSpan |
time interval |
Special Types¶
| Type | Description |
|---|---|
EnumTypeName |
name of a public enum declared in the adapter's C# code - generates a typed enum property |
enum |
untyped value list - requires values, generates a string property; use only in access profile fields |
byte[] |
byte array |
<type>[] |
array of any type (e.g., int[], string[]) |
Device Reference Types¶
When property type matches a type_id defined in the same adapter, it becomes a device reference - a cross-link to another Thing in the device tree.
| Type | C# Type | JSON Value | Description |
|---|---|---|---|
TypeName |
TypeName? |
"guid-string" |
Single device reference (nullable) |
TypeName[] |
IReadOnlyList<TypeName> |
["guid1", "guid2"] |
Array of device references |
Single Device Reference¶
device_types:
- type_id: DoorAlarm
properties:
target_door:
type: Door # matches type_id "Door"
required: true
description: "Door this alarm monitors"
Generated C#:
JSON configuration:
Array Device Reference (Many-to-Many)¶
For relationships where one Thing references multiple others:
device_types:
- type_id: Zone
properties:
partitions:
type: Partition[] # [] suffix for array
required: false
description: "Partitions this zone belongs to"
Generated C#:
internal readonly List<Partition> PartitionsBuilder = [];
public IReadOnlyList<Partition> Partitions => PartitionsBuilder;
JSON configuration:
Resolution¶
Device references are resolved after the entire device tree is built:
- All Things are created with their
device_id - Parent/Children relationships are established
- Device references are resolved by looking up target GUIDs
AddInboundReference()is called for reverse navigation- Deferred validations run (can access resolved references)
Reverse Navigation¶
Every Thing tracks which other Things reference it via ReferencedBy:
// Forward navigation
zone.Partitions // IReadOnlyList<Partition>
// Reverse navigation
partition.ReferencedBy.OfType<Zone>() // all zones referencing this partition
Type-specific Fields¶
For String¶
| Field | Type | Description |
|---|---|---|
max_length |
int | maximum string length |
pattern |
string | regex validation pattern |
properties:
serial_number:
type: string
max_length: 32
pattern: "^[A-Z0-9]+$"
description: "Device serial number"
For Host Addresses¶
Use host for device connection endpoints that may be DNS names or IP literals.
Use ipv4 only when the device or SDK explicitly requires IPv4.
Both host and ipv4 generate Pq.Adapters.Framework.HostAddress properties.
host accepts DNS hostnames, IPv4, and IPv6. ipv4 accepts only dotted IPv4.
For Numeric Types¶
| Field | Type | Description |
|---|---|---|
range |
int[2] | allowed range [min, max] |
properties:
door_address:
type: uint
required: true
range: [1, 32]
description: "Door address (1-32)"
For Enum¶
A device property with a fixed set of values names a public enum declared in the
adapter's C# code. The generator resolves the name against the compilation and emits a
typed property, so adapter code switches on enum members instead of parsing strings.
namespace MyAdapter.Definitions;
/// <summary>
/// Reader authentication mode.
/// </summary>
public enum ReaderMode
{
/// <summary>
/// Card only.
/// </summary>
CardOnly,
/// <summary>
/// PIN only.
/// </summary>
PinOnly,
/// <summary>
/// Card and PIN.
/// </summary>
CardAndPin,
}
properties:
reader_mode:
type: ReaderMode
default: CardOnly
description: "Reader authentication mode"
public MyAdapter.Definitions.ReaderMode ReaderMode { get; set; } = MyAdapter.Definitions.ReaderMode.CardOnly;
default must be an enum member name. A type that resolves to no enum degrades to
object - the property becomes unusable, which is the usual symptom of a typo or a
non-public enum.
The untyped type: enum + values form generates a plain string and is reserved for
access profile fields (below), where no adapter C# type backs the value.
Generated Validation¶
The source generator creates FluentValidation validators based on the definition:
| Definition | Generated Validation |
|---|---|
required: true |
.NotNull() |
required: true + type: string |
.NotNull().NotEmpty() |
max_length: N |
.MaximumLength(N) |
pattern: "regex" |
.Matches(@"regex") |
type: host |
host/IP validation |
type: ipv4 |
IPv4 validation |
range: [min, max] |
.InclusiveBetween(min, max) |
type: enum + values |
.Must(x => values.Contains(x)) |
Access Profile Fields¶
access-model.yaml uses the same property definition shape for
person_profiles[].fields[], with one difference: fields are listed as array items
and include a name field:
person_profiles:
- slot_type: User
fields:
- name: user_level
type: int
required: false
default: 0
range: [0, 3]
category: alarm
description: "Alarm user level"
- name: duress_code
type: string
sensitive: true
pattern: "^[0-9]{4,8}$"
Supported fields are the same where applicable: type, required, default,
range, max_length, pattern, values, category, description, and
sensitive.
Profile fields generate typed profile records used by access synchronization. YAML
defaults become C# property initializers. Required fields are checked by generated
mapping before adapter Transform() executes.
Examples¶
Complex Device Properties¶
device_types:
- type_id: door-controller
name: Door Controller
properties:
address:
type: uint
required: true
range: [1, 64]
description: "Controller address on RS-485 bus"
name:
type: string
max_length: 64
description: "User-defined controller name"
firmware_version:
type: string
pattern: "^\\d+\\.\\d+\\.\\d+$"
description: "Firmware version (semver format)"
unlock_duration:
type: uint
default: 5
range: [1, 60]
description:
en: "Door unlock duration in seconds"
cs: "Doba odemknuti dveri v sekundach"
authentication_mode:
type: enum
values: ["card", "pin", "card_and_pin", "fingerprint"]
default: "card"
description: "Primary authentication method"
Properties with Category and Sensitive¶
device_types:
- type_id: controller
properties:
host:
type: host
required: true
category: connection
description: "Controller host or IP address"
port:
type: uint
default: 4370
category: connection
range: [1, 65535]
description: "TCP port"
password:
type: string
required: true
category: connection
sensitive: true
description: "Device password"
Bootstrap Config Properties¶
bootstrap_config:
connection:
host:
type: host
required: true
description: "Controller hostname or IP address"
port:
type: uint
default: 4370
range: [1, 65535]
description: "TCP port number"
Nullable Behavior¶
Properties are nullable based on type and required combination:
| type | required | C# type |
|---|---|---|
string |
false |
string? |
string |
true |
string |
host |
false |
HostAddress? |
host |
true |
HostAddress |
ipv4 |
false |
HostAddress? |
ipv4 |
true |
HostAddress |
int |
false |
int? |
int |
true |
int |
enum |
false |
string? |
enum |
true |
string |
EnumTypeName |
false |
EnumTypeName? |
EnumTypeName |
true |
EnumTypeName |
A default also makes the property non-nullable, whatever required says.
Naming Conventions¶
- property names in YAML:
snake_case - generated C# properties:
PascalCase
Private Properties¶
Properties marked with private: true are not exposed to UI through DeviceAdapterInformation. They are still generated as C# properties and can be used internally by the adapter.
Use Cases¶
- Technical settings determined by protocol capabilities (e.g.,
use_framework_timers) - Internal configuration that users should not modify
- Protocol-specific parameters
Framework-level Private¶
Define in functions.yaml to make property always private across all adapters:
# functions.yaml
Door:
properties:
use_framework_timers:
type: "bool"
default: true
private: true # always hidden from UI
Adapter-level Override¶
Override framework property to make it private in specific adapter:
# adapter-registration.yaml
device_types:
- type_id: "AccessControlReader"
category: door
functions:
Door:
use_framework_timers:
private: true
default: false # Panel manages timers
Reader:
Adapter overrides can:
- Make a public property private
- Change the default value
- Both
Adapter cannot make a private framework property public (security).
Device Category¶
Each device type must specify a category for UI icon selection.
Required Field¶
Available Categories¶
| Category | Description | Typical Devices |
|---|---|---|
door |
Door access point | ACR, door controller |
reader |
Credential reader | Card reader, biometric |
panel |
Main controller | X1100, X1200 |
module |
Expansion module | X100, X200, X300 |
input |
Input point | Monitor point, sensor |
output |
Output point | Relay, control point |
partition |
Alarm zone | MPG, area |
keypad |
Security keypad | Arming station |
detector |
Intrusion sensor | PIR, contact |
Categories are normalized to lowercase in generated code.
Device Tree Ordering¶
The device tree is sorted for display. By default, siblings under the same
parent are ordered: leaf types before container types (a container is any type
that can have children), then by category, then by the type's address key, then
by natural name. The address key is the type's unique properties (else the
discovery identity, else an address property). Digit runs sort numerically,
so address: 2 comes before address: 10 and Port1 before Port2.
Two optional device_type keys override the defaults.
order (optional, int)¶
Explicit position for this type among its siblings; lower sorts first. When omitted, the position is derived from category and whether the type can contain children.
sort_by (optional, list of property names)¶
Orders instances of this type among same-type siblings, in priority order (natural comparison). Overrides the default address key.