Skip to content

YAML Configuration: Complete Guide

Complete guide to adapter-registration.yaml with annotated examples and common patterns.

Table of Contents

Overview

adapter-registration.yaml defines:

  • Adapter metadata (name, version, manufacturer)
  • Device types your hardware supports
  • Functions each device type implements
  • Properties for configuration
  • Parent-child hierarchies
  • Supported commands
  • Access control model (optional)

Location: Project root, copied to output directory

Schema: adapter-registration.schema.json (for IDE validation)

Top-Level Structure

# ==============================================================================
# ADAPTER METADATA
# ==============================================================================

adapter_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"  # Unique GUID
name: "My Access Control Adapter"                    # Display name
version: "1.0.0"                                     # SemVer
manufacturer: "ACME Corp"                            # Vendor name
description: "Multi-door access control panels"     # Brief description
adapter_type: "security"                             # Category: security | surveillance | intrusion
security_domains: ["access"]                         # Domains: access | video | intrusion | fire

# ==============================================================================
# CAPABILITIES
# ==============================================================================

capabilities:
  auto_discovery: false         # true when discovery is supported
  time_sync: true               # Supports time synchronization
  firmware_update: false        # Supports firmware updates
  config_backup: false          # Supports configuration backup/restore

# ==============================================================================
# TRANSPORT
# ==============================================================================

transport:
  type: custom                  # custom | tcp
  protocol:
    type: "Events"              # None | Queue | Events
    event_type: "MyAdapter.Communication.DeviceEvent"  # C# type for events
    address_type: int           # int | short | string | guid

Key decisions:

  • adapter_id - Generate once, never change (devices bind to this)
  • security_domains - Determines UI sections and permissions
  • transport.type - Matches your communication layer
  • address_type - How devices are identified (numeric, string, GUID)

Validate this file against docs/adapter-development/reference/adapter-registration.schema.json. Do not invent block names or property names that the schema does not allow.

Device Types

Device types represent physical hardware models. Define one type per hardware variant.

Basic Device Type

device_types:
  - type_id: "Controller"              # Unique within adapter
    name: "Access Controller AC-100"   # Display name
    description: "4-door controller with RS-485 expansion"
    category: panel

    functions:
      ConnectionBase:                  # Always include (connectivity)
      Power:                           # Power monitoring
      Tamper:                          # Tamper detection

    commands:
      - pq.command.reset               # Supported commands

    events:
      - pq.event.system.configuration.changed   # Events this type reports

    properties:
      ip_address:                      # Configuration property
        type: host
        required: true
        category: connection
        description: "Controller host or IP address"

Functions: Use framework functions from functions.yaml. See Functions Registry.

Commands: Use framework commands from pq-commands.yaml. See Generated Code API.

Events: List the taxonomy events this type reports on its own — the ones no function already covers. Each becomes a named publication method on the Thing, so the example above gives Controller a ConfigurationChanged(timestamp, …) method that your event router calls. See Declared Event Methods for how names and parameters are derived.

Device Type with Custom Properties

  - type_id: "DoorModule"
    name: "Door Interface Module"
    description: "Single door with reader, strike, REX"
    category: door

    functions:
      Door:
        has_lock_sensor: false       # Override framework default
        has_position_sensor: true
        has_rex_sensor: true
        doorlongopen_timeout_seconds: 30
      Power:
      Tamper:

    commands:
      - pq.command.access.lock
      - pq.command.access.unlock
      - pq.command.access.open

    properties:
      # Bus addressing for modular systems
      bus_address:
        type: int
        required: true
        range: [1, 32]
        category: hardware
        description: "RS-485 bus address (1-32)"

      # Door-specific configuration
      strike_mode:
        type: StrikeMode               # Custom enum
        required: true
        default: "FailSecure"
        category: door
        description: "Strike behavior on power loss"

      unlock_duration:
        type: int
        required: true
        default: 5
        range: [1, 60]
        category: door
        description: "Seconds to hold strike unlocked"

Property categories: Group related settings (connection, hardware, door, reader, etc.)

Device Type Inheritance

When you have multiple device variants that share most of their implementation, use abstract and extends to avoid code duplication.

device_types:
  # Abstract base type - not instantiable
  - type_id: "PanelBase"
    abstract: true
    category: panel
    properties:
      ip_address:
        type: host
        required: true
        description: "Network module host or IP address"
      port:
        type: int
        default: 10000
    functions:
      ConnectionBase:
      Power:
      Tamper:
    commands: []
    events:
      - pq.event.system.configuration.changed

  # Concrete variant - inherits from base
  - type_id: "Panel48"
    extends: "PanelBase"
    name: "48-Zone Panel"
    description: "48 zones, 4 partitions"
    properties:
      max_zones:
        type: int
        default: 48
        private: true

  # Another variant with different defaults
  - type_id: "Panel192"
    extends: "PanelBase"
    name: "192-Zone Panel"
    description: "192 zones, 8 partitions"
    properties:
      max_zones:
        type: int
        default: 192
        private: true

Key points:

  • abstract: true - Type cannot be instantiated, serves as base
  • extends: "BaseTypeId" - Inherits properties, functions, commands from base
  • Publication methods are generated on the type that declares the events, so PanelBase gets ConfigurationChanged and both variants inherit it - declare a shared event once on the base
  • Derived types can add properties or override defaults
  • Command handlers on base class work for all derived types
  • name is required on concrete (non-abstract) types
  • category is inherited if not specified on derived type

See Things: Device Type Inheritance for details.

Functions

Functions provide capabilities to device types. See Functions Registry for complete list of available functions.

Framework Functions

Most common:

  • ConnectionBase - Device connectivity (required for all)
  • Door - Physical door with access control
  • Reader - Credential reader (card, PIN, biometric)
  • Power - Power supply and battery monitoring
  • Tamper - Tamper detection
  • Input - Generic monitored input
  • Output - Generic output/relay
  • AccessSynchronization - Credential sync
  • TimeSynchronization - Clock sync

Usage:

functions:
  ConnectionBase:              # No configuration needed

  Door:                        # With direct property overrides
    has_position_sensor: true
    doorlongopen_timeout_seconds: 45

  TimeSynchronization:
    timezone: "EuropePrague"       # PqTimeZone enum name, not "Europe/Prague" or Windows timezone ID
    sync_interval_minutes: 120

Custom Functions

Define adapter-specific functions for non-standard features:

# At top level, after transport
custom_functions:
  BiometricEnrollment:
    description: "Face/fingerprint enrollment capability"
    properties:
      max_templates:
        type: int
        default: 5
        range: [1, 10]
        description: "Maximum biometric templates per person"

      quality_threshold:
        type: int
        default: 70
        range: [1, 100]
        description: "Minimum quality score (1-100)"

# Use in device type
device_types:
  - type_id: "BiometricReader"
    name: "Face Recognition Reader"
    category: reader
    functions:
      ConnectionBase:
      Reader:
      BiometricEnrollment:       # Custom function
        max_templates: 3

Custom function events: Generated as adapter-local types, not framework-wide.

Properties

Properties configure device behavior. Each property has type, validation, and metadata.

Property Types

Type Description Example
string Text value IP address, serial number
int Integer Port number, timeout seconds
uint Unsigned integer Bus address, device count
bool Boolean flag Enable/disable feature
double Decimal number Battery voltage threshold
enum Custom enumeration Connection mode, relay mode
Guid Unique identifier Device ID, person ID
PqTimeZone Timezone Framework-provided type

Property Validation

properties:
  # Hostname, IPv4, or IPv6 address
  ip_address:
    type: host
    required: true
    category: connection
    description: "Controller host or IP address"

  # Integer with range
  port:
    type: int
    required: true
    default: 4000
    range: [1, 65535]
    category: connection
    description: "TCP port number"

  # Optional with default
  timeout_seconds:
    type: int
    required: false
    default: 30
    range: [5, 300]
    category: communication
    description: "Command timeout in seconds"

  # Sensitive (masked in UI)
  password:
    type: string
    required: false
    sensitive: true
    category: connection
    description: "Authentication password"

  # Custom enum
  connection_mode:
    type: ConnectionMode     # Defined in C# code
    required: true
    default: "Server"
    category: connection
    description: "Server = adapter connects, Client = device connects"

Custom Enums

Define enums in C# code:

namespace MyAdapter.Definitions;

/// <summary>
/// connection mode
/// </summary>
public enum ConnectionMode
{
    /// <summary>
    /// adapter connects to device (client mode)
    /// </summary>
    Server,

    /// <summary>
    /// device connects to adapter (server mode)
    /// </summary>
    Client
}

Use in YAML:

properties:
  connection_mode:
    type: ConnectionMode
    required: true
    default: "Server"

Hierarchies & Parents

Define parent-child relationships for modular systems.

Simple Hierarchy

device_types:
  # Parent: Main controller
  - type_id: "Controller"
    name: "Main Controller"
    category: panel
    functions:
      ConnectionBase:
      Power:

  # Child: Door module
  - type_id: "DoorModule"
    name: "Door Module"
    category: door
    functions:
      Door:

    parents:
      Controller:              # Can be child of Controller
        max_siblings: 4        # Up to 4 doors per controller
        required: true         # Must have parent

Result: User creates Controller, then adds up to 4 DoorModule children.

Multi-Level Hierarchy

device_types:
  # Level 1: Controller
  - type_id: "Controller"
    name: "System Controller"
    category: panel
    functions:
      ConnectionBase:
      Power:

  # Level 2: Expansion module
  - type_id: "ExpansionModule"
    name: "I/O Expansion Module"
    category: module
    functions:
      Power:
      Tamper:

    properties:
      bus_address:
        type: int
        required: true
        range: [1, 32]

    parents:
      Controller:
        max_siblings: 32       # Up to 32 modules per controller
        required: true

  # Level 3: Door (child of module)
  - type_id: "ModuleDoor"
    name: "Module Door Port"
    category: door
    functions:
      Door:

    properties:
      port_number:
        type: int
        required: true
        range: [1, 4]

    parents:
      ExpansionModule:
        max_siblings: 4        # 4 doors per module
        required: true

Result: Controller → ExpansionModule → ModuleDoor hierarchy

Optional Parents

device_types:
  # Standalone OR child device
  - type_id: "SmartReader"
    name: "Standalone Smart Reader"
    category: reader
    functions:
      ConnectionBase:
      Reader:
      Door:

    parents:
      Controller:
        max_siblings: 16
        required: false        # Can exist without parent

Result: Reader works standalone OR as controller child

Commands

Declare which PQ commands each device type supports. Framework auto-routes commands to handler methods.

Basic Commands

device_types:
  - type_id: "Door"
    name: "Access Door"
    category: door
    functions:
      Door:

    commands:
      - pq.command.access.lock          # Engage strike
      - pq.command.access.unlock        # Release strike
      - pq.command.access.open          # Momentary unlock
      - pq.command.access.open.permanent  # Hold open

Output Commands

  - type_id: "RelayOutput"
    name: "Generic Relay"
    category: output
    functions:
      Output:

    commands:
      - pq.command.output.activate
      - pq.command.output.deactivate

Emergency Commands

  - type_id: "Controller"
    name: "Security Controller"
    category: panel
    functions:
      ConnectionBase:

    commands:
      - pq.command.emergency.lockdown         # Deny all access
      - pq.command.emergency.lockdown.cancel  # Restore normal
      - pq.command.emergency.evacuation       # Unsecure all
      - pq.command.emergency.evacuation.cancel

Note: Commands automatically generate handler detection. See Pattern 4: Command Handling.

Access Model

Optional. Define access control data model for credential synchronization.

Create access-model.yaml:

model:
  # Step 1: schedule entity
  - type_id: Schedule
    updatable: inplace
    properties:
      address: uint
      periods: SchedulePeriod[]
    range: [1, 255]

  # Embedded in Schedule
  - type_id: SchedulePeriod
    properties:
      day_mask: uint
      start_minute: uint
      end_minute: uint

  # Step 2: access level references schedules
  - type_id: AccessLevel
    updatable: inplace
    properties:
      address: uint
      door_schedules: DoorSchedule[]
    range: [1, 32000]

  # Embedded in AccessLevel; references Schedule by object reference
  - type_id: DoorSchedule
    properties:
      door_address: uint
      schedule: Schedule

  # Step 3: user references access levels
  - type_id: User
    properties:
      address: uint
      name: string
      card_number: string
      access_levels: AccessLevel[]
    range: [1, 10000]

person_profiles:
  - slot_type: User
    fields:
      - name: user_level
        type: int
        default: 0
        range: [0, 3]
        category: alarm
        description: "Alarm user level"

Dependency order: Declare model entities in dependency order: leaves first, roots last. Cross-entity references use EntityName or EntityName[]; do not use raw address integers for model references.

Ownership: You do not declare ownership. The framework infers, from these same references, which allocator-addressed entities are person-owned roots and which are shared tables — this drives selective sync. The optional owner: field is an exceptional override only. See Pattern 1: Selective Sync Ownership.

Person profiles: Use person_profiles for per-person panel settings that are not credentials and not grants. Profile fields reuse the same property shape as device properties where applicable (type, required, default, range, pattern, category, description, sensitive).

Usage: See Tutorial 3: Access Synchronization.

Complete Example

Full adapter-registration.yaml for multi-level access control system:

# ==============================================================================
# Adapter: ACME Access Control System
# Supports AC-1000 controllers with modular expansion
# ==============================================================================

adapter_id: "12345678-1234-5678-1234-567812345678"
name: "ACME Access Control Adapter"
version: "2.1.0"
manufacturer: "ACME Security Systems"
description: "Multi-door access control with RS-485 expansion modules"
adapter_type: "security"
security_domains: ["access"]

capabilities:
  auto_discovery: true
  time_sync: true
  firmware_update: true
  config_backup: true

transport:
  type: tcp
  protocol:
    type: "Events"
    address_type: int
    frame_type: "AcmeFrame"

# ==============================================================================
# Custom Enums (defined in C# code)
# ==============================================================================
# ConnectionMode: Server | Client
# StrikeMode: FailSecure | FailSafe
# ReaderType: Wiegand | OSDP | ClockData

# ==============================================================================
# Device Type Hierarchy:
#   AC1000 (controller)
#     +-- ExpansionModule (I/O module on RS-485)
#           +-- DoorPort (logical door)
#           +-- InputPoint (monitored input)
#           +-- OutputRelay (control output)
# ==============================================================================

device_types:
  # ============================================================================
  # AC-1000 - Main Controller
  # ============================================================================
  - type_id: "AC1000"
    name: "ACME AC-1000 Controller"
    description: "4-door intelligent controller with RS-485 expansion"
    category: panel

    functions:
      ConnectionBase:
      Power:
      Tamper:
      TimeSynchronization:
        timezone: "Utc"            # PqTimeZone enum name
        sync_interval_minutes: 60
      AccessSynchronization:

    commands:
      - pq.command.reset
      - pq.command.emergency.lockdown
      - pq.command.emergency.lockdown.cancel
      - pq.command.emergency.evacuation
      - pq.command.emergency.evacuation.cancel

    properties:
      # Connection settings
      connection_mode:
        type: ConnectionMode
        required: true
        default: "Server"
        category: connection
        description: "Server = adapter connects, Client = device connects"

      ip_address:
        type: host
        required: true
        category: connection
        description: "Controller host or IP address"

      port:
        type: int
        required: true
        default: 4000
        range: [1, 65535]
        category: connection
        description: "TCP port"

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

      # Hardware identification
      serial_number:
        type: string
        required: true
        category: hardware
        description: "Device serial number for matching"

      # Access control settings
      max_cardholders:
        type: int
        required: false
        default: 10000
        range: [100, 100000]
        category: access
        description: "Maximum cardholders in device database"

  # ============================================================================
  # Expansion Module - RS-485 I/O Module
  # ============================================================================
  - type_id: "ExpansionModule"
    name: "ACME EXP-200 Expansion Module"
    description: "RS-485 expansion with 4 doors, 8 inputs, 8 outputs"
    category: module

    functions:
      Power:
      Tamper:

    properties:
      bus_address:
        type: int
        required: true
        range: [1, 32]
        category: hardware
        description: "RS-485 address (1-32)"

    parents:
      AC1000:
        max_siblings: 32
        required: true

  # ============================================================================
  # Door Port - Logical Door (Reader + Strike + Contact + REX)
  # ============================================================================
  - type_id: "DoorPort"
    name: "Door Interface"
    description: "Complete door with reader, strike, contact sensor, REX"
    category: door

    functions:
      Door:
        has_lock_sensor: false
        has_position_sensor: true
        has_rex_sensor: true
        fail_safe_mode: false
        doorlongopen_timeout_seconds: 30
        auto_relock_timeout_seconds: 5
      Reader:

    commands:
      - pq.command.access.lock
      - pq.command.access.unlock
      - pq.command.access.open
      - pq.command.access.open.permanent

    properties:
      port_number:
        type: int
        required: true
        range: [1, 4]
        category: hardware
        description: "Door port number on module (1-4)"

      reader_type:
        type: ReaderType
        required: true
        default: "Wiegand"
        category: reader
        description: "Reader interface type"

      strike_mode:
        type: StrikeMode
        required: true
        default: "FailSecure"
        category: door
        description: "Strike behavior on power loss"

      unlock_duration:
        type: int
        required: true
        default: 5
        range: [1, 60]
        category: door
        description: "Seconds to hold strike unlocked"

    parents:
      ExpansionModule:
        max_siblings: 4
        required: true
      AC1000:
        max_siblings: 4
        required: true

  # ============================================================================
  # Input Point - Monitored Input
  # ============================================================================
  - type_id: "InputPoint"
    name: "Monitor Point"
    description: "Supervised input for alarm sensors"
    category: input

    functions:
      Input:

    properties:
      input_number:
        type: int
        required: true
        range: [1, 8]
        category: hardware
        description: "Input number on module (1-8)"

      supervision_type:
        type: InputSupervision
        required: true
        default: "NormallyOpen"
        category: input
        description: "Input supervision (NO/NC/EOL)"

    parents:
      ExpansionModule:
        max_siblings: 8
        required: true

  # ============================================================================
  # Output Relay - Control Output
  # ============================================================================
  - type_id: "OutputRelay"
    name: "Control Relay"
    description: "Programmable relay output"
    category: output

    functions:
      Output:

    commands:
      - pq.command.output.activate
      - pq.command.output.deactivate

    properties:
      output_number:
        type: int
        required: true
        range: [1, 8]
        category: hardware
        description: "Output number on module (1-8)"

      pulse_duration:
        type: int
        required: false
        default: 0
        range: [0, 255]
        category: output
        description: "Pulse duration in 100ms units (0 = latching)"

    parents:
      ExpansionModule:
        max_siblings: 8
        required: true

Validation

Schema Validation

Add schema reference to YAML:

# yaml-language-server: $schema=../reference/adapter-registration.schema.json

adapter_id: "..."

IDE support: VS Code, Rider, Visual Studio recognize schema for autocomplete and validation.

Runtime Validation

Framework validates on load:

  • Required properties present
  • Range constraints respected
  • Pattern matches valid
  • Enum values recognized
  • Parent relationships valid

Errors halt adapter startup - check logs for validation failures.

See Also