Skip to content

Power

Overview

Power supply and battery monitoring for devices with mains power, battery backup, and UPS capabilities. Tracks AC power status, battery health, charging state, and power-related faults.

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

When to Use

  • Devices with mains power supply
  • Battery backup monitoring
  • UPS-powered equipment
  • Power fault detection
  • Battery health tracking
  • Critical power notifications

States

Power Supply States

State Status String Description
PowerOk pq.state.supervision.power.ok Operating on mains power (normal)
PowerFault pq.state.supervision.power.fault Mains power failure detected (also entered when running on battery backup)

Battery States

State Status String Description
BatteryCharging pq.state.supervision.battery.charging Battery charging in progress
BatteryLow pq.state.supervision.battery.low Battery level low
BatteryCritical pq.state.supervision.battery.critical Battery level critical (imminent shutdown)
BatteryFault pq.state.supervision.battery.fault Battery malfunction detected

Actions

Power Supply Events

AcFault(timestamp)

  • Description: Mains power failure detected
  • Target State: PowerFault
  • Events: pq.event.energy.power.failure (Critical)
  • Usage: AC power lost, device switching to battery

AcRestored(timestamp)

  • Description: Mains power restored
  • Target State: PowerOk
  • Events: pq.event.energy.power.restored (Info)
  • Usage: AC power returned, device on mains

BatteryBackup(timestamp)

  • Description: Running on battery backup
  • Target State: PowerFault
  • Events: pq.event.energy.ups.active (Warn)
  • Usage: Device switched to battery after AC failure

Battery Events

BatteryFault(timestamp)

  • Description: Battery malfunction detected
  • Target State: BatteryFault
  • Events: pq.event.technical.battery.fault (Critical)
  • Usage: Battery not charging, disconnected, or hardware fault

BatteryLow(timestamp)

  • Description: Battery level low
  • Target State: BatteryLow
  • Events: pq.event.technical.battery.low (Warn)
  • Usage: Battery capacity below low threshold (typically 20-30%)

BatteryCritical(timestamp)

  • Description: Battery level critical
  • Target State: BatteryCritical
  • Events: pq.event.technical.battery.critical (Critical)
  • Usage: Battery capacity critically low (typically <10%), imminent shutdown

BatteryNormal(timestamp)

  • Description: Battery condition restored to normal
  • Target State: PowerOk
  • Events: pq.event.technical.battery.normal (Info)
  • Usage: Battery charged, fault cleared, or condition restored

Properties

None. All power monitoring configuration is device-specific. Define properties in adapter-registration.yaml if needed (e.g., battery_low_threshold, power_monitoring_enabled).

YAML Example

device_types:
  - type_id: AccessController
    name: "Access Control Panel"
    functions:
      ConnectionBase:
      Door:
      Power:               # power monitoring
      Tamper:
    properties:
      battery_low_threshold:
        type: "int"
        default: 30
        range: [10, 50]
        description: "Battery low threshold percentage"

      battery_critical_threshold:
        type: "int"
        default: 10
        range: [5, 20]
        description: "Battery critical threshold percentage"

  - type_id: IntrusionPanel
    name: "Intrusion Alarm Panel"
    functions:
      Partition:
      Power:               # panel power monitoring
      Tamper:
    properties:
      power_monitoring_enabled:
        type: "bool"
        default: true
        description: "Enable power supply monitoring"

  - type_id: NetworkReader
    name: "Network-Connected Reader"
    functions:
      ConnectionBase:
      Reader:
      Power:               # PoE/local power monitoring

Code Usage

Basic Power Monitoring

public class AccessPanelThing : Thing
{
    public PowerFunction Power { get; }

    protected override async Task HandleDeviceEvent(PanelEvent evt, CancellationToken ct)
    {
        switch (evt.Type)
        {
            case PanelEventType.PowerLoss:
                await Power.AcFault(evt.Timestamp);
                await Power.BatteryBackup(evt.Timestamp);
                break;

            case PanelEventType.PowerRestored:
                await Power.AcRestored(evt.Timestamp);
                break;

            case PanelEventType.BatteryLow:
                await Power.BatteryLow(evt.Timestamp);
                break;

            case PanelEventType.BatteryNormal:
                await Power.BatteryNormal(evt.Timestamp);
                break;
        }
    }
}

Battery Level Monitoring with Thresholds

public class UPSBackedDeviceThing : Thing
{
    public PowerFunction Power { get; }

    private const int LowThreshold = 30;
    private const int CriticalThreshold = 10;

    private int _lastBatteryLevel = 100;

    protected override async Task HandleDeviceEvent(DeviceEvent evt, CancellationToken ct)
    {
        if (evt.Type == DeviceEventType.BatteryLevel)
        {
            var batteryLevel = evt.BatteryPercentage;

            // Detect threshold crossings
            if (batteryLevel <= CriticalThreshold && _lastBatteryLevel > CriticalThreshold)
            {
                await Power.BatteryCritical(evt.Timestamp);
            }
            else if (batteryLevel <= LowThreshold && _lastBatteryLevel > LowThreshold)
            {
                await Power.BatteryLow(evt.Timestamp);
            }
            else if (batteryLevel > LowThreshold && _lastBatteryLevel <= LowThreshold)
            {
                await Power.BatteryNormal(evt.Timestamp);
            }

            _lastBatteryLevel = batteryLevel;
        }
    }
}

Complete Power State Machine

public class IntrusionPanelThing : Thing
{
    public PowerFunction Power { get; }

    private bool _isOnMains = true;
    private bool _isBatteryHealthy = true;

    protected override async Task HandleDeviceEvent(PanelEvent evt, CancellationToken ct)
    {
        switch (evt.Type)
        {
            case PanelEventType.AcPowerLost:
                _isOnMains = false;
                await Power.AcFault(evt.Timestamp);

                if (_isBatteryHealthy)
                    await Power.BatteryBackup(evt.Timestamp);
                break;

            case PanelEventType.AcPowerRestored:
                _isOnMains = true;
                await Power.AcRestored(evt.Timestamp);
                break;

            case PanelEventType.BatteryFault:
                _isBatteryHealthy = false;
                await Power.BatteryFault(evt.Timestamp);
                break;

            case PanelEventType.BatteryRestored:
                _isBatteryHealthy = true;
                await Power.BatteryNormal(evt.Timestamp);
                break;

            case PanelEventType.BatteryLow:
                if (!_isOnMains)
                    await Power.BatteryLow(evt.Timestamp);
                break;

            case PanelEventType.BatteryCritical:
                if (!_isOnMains)
                    await Power.BatteryCritical(evt.Timestamp);
                break;
        }
    }
}

Polling-Based Power Monitoring

public class PollingDeviceThing : Thing
{
    public PowerFunction Power { get; }
    private readonly Timer _pollTimer;

    private bool _lastMainsState = true;
    private int _lastBatteryLevel = 100;

    public PollingDeviceThing()
    {
        _pollTimer = new Timer(PollPowerStatus, null,
            TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));
    }

    private async void PollPowerStatus(object? state)
    {
        try
        {
            var status = await QueryPowerStatusAsync();

            // Check mains power change
            if (status.MainsPower != _lastMainsState)
            {
                if (status.MainsPower)
                    await Power.AcRestored(DeviceTimestamp.UtcNow);
                else
                    await Power.AcFault(DeviceTimestamp.UtcNow);

                _lastMainsState = status.MainsPower;
            }

            // Check battery level change
            if (!status.MainsPower && status.BatteryLevel != _lastBatteryLevel)
            {
                if (status.BatteryLevel <= 10 && _lastBatteryLevel > 10)
                    await Power.BatteryCritical(DeviceTimestamp.UtcNow);
                else if (status.BatteryLevel <= 30 && _lastBatteryLevel > 30)
                    await Power.BatteryLow(DeviceTimestamp.UtcNow);
                else if (status.BatteryLevel > 30 && _lastBatteryLevel <= 30)
                    await Power.BatteryNormal(DeviceTimestamp.UtcNow);

                _lastBatteryLevel = status.BatteryLevel;
            }
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Power status poll failed");
        }
    }
}

Power Loss Notification with Graceful Shutdown

public class CriticalSystemThing : Thing
{
    public PowerFunction Power { get; }
    private CancellationTokenSource? _shutdownCts;

    protected override async Task HandleDeviceEvent(DeviceEvent evt, CancellationToken ct)
    {
        if (evt.Type == DeviceEventType.BatteryCritical)
        {
            await Power.BatteryCritical(evt.Timestamp);

            Logger.LogCritical("Battery critical - initiating graceful shutdown");

            // Begin graceful shutdown sequence
            _shutdownCts = new CancellationTokenSource(TimeSpan.FromMinutes(2));
            await InitiateGracefulShutdown(_shutdownCts.Token);
        }
        else if (evt.Type == DeviceEventType.AcRestored)
        {
            // Cancel shutdown if power restored
            _shutdownCts?.Cancel();
            _shutdownCts = null;

            await Power.AcRestored(evt.Timestamp);
            Logger.LogInformation("Power restored - shutdown cancelled");
        }
    }
}

Notes

  • Critical Events: AcFault, BatteryFault, and BatteryCritical are critical severity events
  • State Transitions: Typical flow: PowerOkPowerFault (on AC loss / battery backup) → BatteryLowBatteryCritical, returning to PowerOk on AcRestored/BatteryNormal
  • No Separate Backup State: BatteryBackup() reports the device is running on battery but still maps to the PowerFault state - there is no distinct battery-backup state
  • Battery Restoration: BatteryNormal() transitions back to PowerOk and publishes pq.event.technical.battery.normal - use it to clear BatteryLow or BatteryCritical once the battery recharges
  • Circuit Faults Are Not Power Actions: the taxonomy carries pq.event.energy.power.overload / pq.event.energy.power.overload.restored (a circuit drawing more current than it may, and its clearing) and pq.event.energy.power.fuse.failed / pq.event.energy.power.fuse.restored (a failed and a replaced fuse). The Power function has no action for them - a device that supervises output current or fuses declares these events under its device type's events: list and publishes them from the Thing. Keep the two branches apart: an overload is a condition on a live circuit, a blown fuse is a failed component
  • Threshold Configuration: Battery thresholds are adapter-specific - define in adapter-registration.yaml
  • Polling vs Events: Some devices report power events, others require polling - implement appropriate pattern
  • Combined Monitoring: Often paired with Tamper and ConnectionBase functions for complete device health monitoring

See Also

  • Functions: Tamper - Physical tamper detection
  • Functions: ConnectionBase - Device connectivity monitoring