Skip to content

AccessSynchronization

Overview

Access control data synchronization between PQ Server and device (credential upload/download, access level sync, offline database management). The adapter implements synchronization logic based on device capabilities.

  • Added in: PQ Framework 2.0
  • Namespace: Pq.Adapters.Framework

When to Use

  • Access control panels with offline credential storage
  • Smart locks with local user databases
  • Door controllers with cached credentials
  • Biometric readers with template storage
  • Any device requiring credential synchronization

States

Synchronization Lifecycle

State Status id Description
SyncIdle access.sync.idle No synchronization in progress
SyncActive access.sync.active Synchronization operation in progress
SyncFailed access.sync.failed Synchronization failed, error condition

SyncIdle is the hidden default state: SetSyncIdle(ct) publishes no status string, so the function reports as cleared.

Actions

No predefined actions. The adapter implements synchronization logic using:

  • Custom actions for sync events
  • State transitions via base class methods
  • Command handlers for sync requests

Common Adapter-Defined Actions

Adapters typically implement these patterns:

SyncStarted(timestamp) (adapter-defined)

  • Transition to SyncActive state
  • Begin credential upload/download operation

SyncCompleted(timestamp, credentialCount) (adapter-defined)

  • Transition to SyncIdle state
  • Log synchronization success with statistics

SyncFailed(timestamp, errorMessage) (adapter-defined)

  • Transition to SyncFailed state
  • Log synchronization failure with error details

Properties

Property Type Default Description
access_sync_enabled bool true Synchronize access rights to this device.

When access_sync_enabled is off, the framework writes nothing to the panel: no cardholders, credentials, PINs, access levels, time zones or holidays, no antipassback zone configuration, and no memory wipe. Synchronize and Reset commands answer Ignored, and the device stops publishing pq.event.access.synchronization.restore_needed. The switch covers antipassback too — its zones reference persons the panel never receives, so it has no separate switch.

The gate sits in the framework above the adapter's Transform and CreateCommands, so an adapter needs no code of its own to honour it.

Stored address mappings survive the switch, because they also resolve protocol card addresses back to PQ person identities at event time. Switching back on therefore runs an ordinary differential sync against mappings that may describe a panel someone else has changed meanwhile; run Reset to wipe the panel and rebuild it from PQ.

Any further synchronization configuration is adapter-specific — declare it as a device property in adapter-registration.yaml.

YAML Example

device_types:
  - type_id: AccessControlPanel
    name: "Access Control Panel"
    functions:
      AccessSynchronization:
      ConnectionBase:
    properties:
      sync_mode:
        type: "string"
        default: "auto"
        description: "Synchronization mode: auto, manual, scheduled"
      batch_size:
        type: "int"
        default: 100
        range: [1, 1000]
        description: "Credentials per batch operation"
      offline_capacity:
        type: "int"
        default: 10000
        description: "Maximum offline credentials supported"

  - type_id: SmartLock
    name: "Smart Door Lock"
    functions:
      AccessSynchronization:
      Door:
    properties:
      sync_interval_hours:
        type: "int"
        default: 24
        description: "Hours between automatic synchronization"

Code Usage

public class AccessPanelThing : Thing
{
    public AccessSynchronizationFunction Sync { get; }

    protected override async Task OnSyncRequested(CancellationToken ct)
    {
        // transition to active state
        await Sync.TransitionToAsync("access.sync.active", ct);

        try
        {
            // adapter-specific sync logic
            var credentials = await GetPendingCredentials(ct);
            await UploadToDevice(credentials, ct);

            // transition to idle on success
            await Sync.TransitionToAsync("access.sync.idle", ct);

            Logger.LogInformation("Synchronized {Count} credentials", credentials.Count);
        }
        catch (Exception ex)
        {
            // transition to failed on error
            await Sync.TransitionToAsync("access.sync.failed", ct);

            Logger.LogError(ex, "Synchronization failed");
        }
    }

    private async Task<List<Credential>> GetPendingCredentials(CancellationToken ct)
    {
        // fetch from PQ Server API
        return await ApiClient.GetCredentialsAsync(DeviceId, ct);
    }

    private async Task UploadToDevice(List<Credential> credentials, CancellationToken ct)
    {
        // adapter-specific device communication
        foreach (var batch in credentials.Chunk(BatchSize))
        {
            await DeviceClient.UploadCredentialsAsync(batch, ct);
        }
    }
}

Extended Construction

AccessSynchronization uses extended construction (extended_construction: true). The framework provides:

public class AccessSynchronizationFunction : FunctionBase
{
    // framework provides constructor with Thing reference
    public AccessSynchronizationFunction(Thing thing)
        : base(thing)
    {
    }

    // adapter implements synchronization logic
    public async Task SyncCredentials(List<Credential> credentials, CancellationToken ct)
    {
        await TransitionToAsync("access.sync.active", ct);
        // sync implementation
    }
}

Synchronization Patterns

Full Sync (Initial/Manual)

  1. Device reports firmware version, capacity, current count
  2. Server compares hashes, determines delta
  3. Upload new/changed credentials
  4. Remove deleted credentials
  5. Verify integrity

Incremental Sync (Automatic)

  1. Device polls for changes since last sync
  2. Server returns delta (adds/updates/deletes)
  3. Device applies changes
  4. Device confirms completion

Person Profiles

Access synchronization can include adapter-specific per-person settings declared in access-model.yaml under person_profiles. Use this for panel user flags such as user level, menu type, dual-code mode, or group-selection permission.

When person_profiles is declared, the generated synchronization base calls:

protected override AccessModel.AccessModel Transform(AccessModel.PersonAccess[] persons)

Generated AccessModel.PersonAccess contains typed profile properties, for example person.UserProfile. Adapter code should read those properties directly. The generated mapper applies YAML defaults and validates required profile fields before Transform() runs.

Event-Driven Sync (Real-Time)

  1. Server publishes credential change event
  2. Adapter receives event, filters by device
  3. Adapter pushes change to device immediately
  4. Device acknowledges change

Notes

  • No Predefined Actions: Adapters define custom actions based on device protocol
  • State Tracking: Framework tracks sync state, adapter handles transitions
  • Error Recovery: Adapter implements retry logic, partial sync, rollback
  • Capacity Management: Check device capacity before sync operations
  • Conflict Resolution: Server is authoritative, device reflects server state
  • Performance: Use batch operations, parallel upload when supported

See Also