Skip to content
Version 6.1.0
Contact Us

COV, alarms and events, and trend logs

This guide shows how change of value (COV), alarms and events, and trend logs work with the CAS BACnet Stack: what the stack does on its own, and what your application must supply. It assumes you have already added a device and its objects, as in Quick Start. For complete, working programs, see the example projects on Chipkin’s GitHub, such as BACnetServerExampleCPP.

Two columns. The stack keeps the COV subscriptions, sends COV notifications, runs the alarm algorithms you arm, sends event notifications and stores trend log records. You call BACnetStack_UpdateValue() when a value changes, call BACnetStack_Tick() every 10 to 100 ms, and register the system time callback.

The stack keeps the COV subscription tables, sends COV notifications, runs the alarm algorithms you arm, sends event notifications to recipients, and stores trend log records. Your application owns the data, and has three jobs:

  • Tell the stack when a value changes. Call BACnetStack_UpdateValue() with the object and property that changed. This one call runs everything attached to that value: COV notifications, COV-driven trend logging and the alarm algorithms. The alarm algorithms do nothing until you call it, so an application that never calls it never generates an event notification.
  • Call BACnetStack_Tick() regularly. Tick receives the requests from other devices, including COV subscriptions, and runs everything driven by time: polled trend logging, COV subscription lifetimes, alarm transitions that wait for a time delay, and confirmed-request retries and timeouts. A slow tick delays all of them. Aim for every 10 to 100 ms.
  • Register the system time callback with BACnetStack_RegisterCallbackGetSystemTime(). The stack uses it to timestamp events and log records. FPCallbackGetSystemTime returns seconds since the Unix epoch.

BACnetStack_UpdateValue() returns false if the object type or instance is out of range, the device does not exist, or the property is not enabled on that object.

Sequence: a BACnet client sends SubscribeCOV for Analog Input 1 with a 300 second lifetime and the stack answers SimpleAck. Later your application calls BACnetStack_UpdateValue for Analog Input 1 Present_Value; the stack calls GetPropertyReal and sends the client a COV notification of 22.0. The stack keeps the subscription and its lifetime.

To let other devices subscribe to your objects:

  1. Enable the services with BACnetStack_SetServiceEnabled(). The service argument is a bit number from BACnetServicesSupported, not a service choice: 5 is SubscribeCOV, 38 is SubscribeCOVProperty and 41 is SubscribeCOVPropertyMultiple.
  2. Mark properties as subscribable where needed, with BACnetStack_SetPropertySubscribable() (one object) or BACnetStack_SetPropertyByObjectTypeSubscribable() (an object type’s default). The standard COV object types already accept a plain SubscribeCOV without it. You need it for SubscribeCOVProperty on any property, and for Present_Value COV on any other object type. The property must already be enabled before you call it.
  3. Call BACnetStack_UpdateValue() whenever a subscribed value changes.

You can limit the subscription tables:

BACnetStack_SetCOVSettings() sets the table size and the lifetime in one call, but it is not atomic: if the second setting fails, the first has already taken effect. Prefer the two single-purpose functions.

To subscribe to another device, use BACnetStack_SendSubscribeCOV(), BACnetStack_SendSubscribeCOVProperty() or BACnetStack_SendSubscribeCOVPropertyMultiple(). Cancel with BACnetStack_SendCancelSubscribeCOV() or BACnetStack_SendCancelSubscribeCOVProperty(). BACnetStack_SendSubscribeCOVPropertyMultiple() needs at least one reference, so it cannot send a cancellation.

These are confirmed requests. They follow the pattern in Acting as a client: each returns an invoke id, and the subscription’s acknowledgement or error arrives through the hooks.

There is no hook for incoming COV notifications, by design. They arrive as ordinary frames through your receive message callback. Decode them with BACnetStack_DecodeAs().

To announce values without a subscription, use BACnetStack_SendUnsubscribedUnconfirmedCOVNotification(). It reads the values from your own local device, exactly as a ReadProperty would, rather than taking them as arguments.

Event states: from normal, the event algorithm moves an object to offnormal, high-limit, low-limit or fault when it detects the condition, and the object returns to normal when the condition clears. Each transition sends event notifications, which may require an operator to acknowledge them.

The stack generates alarms from the algorithms you arm on each object. Set them up in this order. Each step needs the stack to be built with STACK_OPTION_ALARMS_AND_EVENTS.

  1. Add a notification class with BACnetStack_AddNotificationClassObject(), giving the priority and acknowledgement requirement for each kind of transition. Use this function, not BACnetStack_AddObject(): a Notification Class added with BACnetStack_AddObject() is not usable.
  2. Add recipients with BACnetStack_AddRecipientToNotificationClass(). Set exactly one of useRecipientDeviceChoice and useRecipientAddressChoice, or the call fails. The list has no size limit, and no function removes a recipient once added.
  3. Enable alarms and events on the object with BACnetStack_SetAlarmsAndEventsForObjectEnabled(), naming the notification class. notifyType is 0 for alarms and 1 for events. Passing enabled as false is destructive: it discards the object’s alarm state and every algorithm armed on it.
  4. Arm an algorithm with one of the BACnetStack_SetIntrinsic* or BACnetStack_SetFault* functions, such as BACnetStack_SetIntrinsicOutOfRangeAlgorithm(), BACnetStack_SetIntrinsicChangeOfStateAlgorithmBool() or BACnetStack_SetFaultOutOfRangeAlgorithmReal(). Their enable argument does not remove an algorithm. false leaves it armed but inhibited.
  5. Call BACnetStack_UpdateValue() for the monitored property, usually Present_Value. Each algorithm’s documentation names the property it monitors.

BACnetStack_SetAlarmsAndEventsForObjectEnabled() makes the object’s Event_Algorithm_Inhibit and Reliability_Evaluation_Inhibit properties writable, so a remote client can inhibit the algorithms you armed.

For an object with intrinsic alarming, the stack asks your enumerated property callback for the object’s Reliability. Any value other than NO_FAULT_DETECTED is treated as a fault. The change to the fault state happens when the algorithms next run, so call BACnetStack_UpdateValue() with Reliability when your fault state changes.

To accept acknowledgements from other devices, enable the AcknowledgeAlarm service (bit 0) with BACnetStack_SetServiceEnabled(), and register BACnetStack_RegisterCallbackAcknowledgeAlarm(). The stack calls your FPCallbackAcknowledgeAlarm when an AcknowledgeAlarm request arrives, so you can check whether the source is allowed to acknowledge, and log it. To refuse, set errorCode (usually SERVICE_REQUEST_DENIED, 29). This callback needs STACK_OPTION_AE_ACKNOWLEDGE_ALARMS.

To acknowledge an alarm in another device, use the function that matches the form of the event’s timestamp: BACnetStack_SendAcknowledgeAlarmDateTime(), BACnetStack_SendAcknowledgeAlarmUnsigned() (sequence number) or BACnetStack_SendAcknowledgeAlarmTime(). To list a device’s active events, use BACnetStack_SendGetEventInformation(), which is paged.

To receive event notifications sent to your device, register BACnetStack_RegisterCallbackEventNotificationReceived(). Enable the services first with BACnetStack_SetServiceEnabled(): bit 2 for ConfirmedEventNotification and bit 29 for UnconfirmedEventNotification. The callback gets the decoded notification, so you do not decode the frame yourself. It is called only for a valid notification addressed to a device your stack hosts. A confirmed notification is acknowledged whether or not the callback is registered.

Records enter a trend log’s circular Log_Buffer in three ways: polled every interval, on COV through BACnetStack_UpdateValue(), or triggered when a client writes Trigger. The oldest record is overwritten unless Stop_When_Full is set. A client reads the buffer with ReadRange.

Add a Trend Log with BACnetStack_AddTrendLogObject(), naming the object and property to log and the buffer size in records. The buffer is circular: once full, the oldest record is overwritten unless Stop_When_Full is set. Only local properties can be logged: isLoggedObjectInRemoteDevice must be false. Then choose how it logs:

BACnetStack_SetTrendLogStartStopTime() limits logging to a time window. The stack compares the window to UTC time from your system time callback, shifted by the Device object’s UTC_Offset if you serve that property. Build both values from that same clock, not from local time.

Do not re-add a Trend Log to change its buffer size. BACnetStack_AddTrendLogObject() follows the usual rule for repeated Add* calls: calling it again for an existing Trend Log returns true and applies none of its arguments, maxBufferSize included. To resize the buffer, remove the object with BACnetStack_RemoveObject() and add it again.

The stack stores the Trend Log’s own properties, including Log_Buffer, and a Get property callback cannot override them. A WriteProperty to any Trend Log property is aborted unless you have registered the system time callback. Clients read Log_Buffer with ReadRange only.

To log several properties in one object, use BACnetStack_AddTrendLogMultipleObject(), then call BACnetStack_AddLoggedObjectToTrendLogMultiple() once for each property. Each call appends, with no duplicate check. A Trend Log Multiple supports polled and triggered logging, not COV.

To back up and restore records from your application, use BACnetStack_ReadTrendLogRecord() and BACnetStack_InsertTrendLogRecord(), or BACnetStack_ReadTrendLogMultipleRecord() and BACnetStack_InsertTrendLogMultipleRecord(). A record read out with either read function can be written back with its insert function and keeps its value, whatever datatype was logged. Timestamps are UTC epoch seconds in both directions: the stack stores a record’s time as the Device’s local time, using the UTC_Offset your application serves for the Device, and converts it back with the offset served at the time of the read. A backup and restore therefore keeps each timestamp, but a record stored under the other Daylight_Savings_Status reads out one hour off. A Null value reads out as "null", so a successful read never returns 0.

To notify a recipient when a log has collected new records, arm BACnetStack_SetIntrinsicBufferReadyAlgorithm() on the Trend Log, after the alarm setup steps above. A notificationThreshold of 0 disables it.