BACnet.Protocol.ObjectTypes.TrendLogMultiple (bacstack v0.1.0-dev.1)

Copy Markdown View Source

The Trend Log Multiple object extends Trend Log to record several properties at once into each log entry. All monitored properties (listed in log_device_object_property) are sampled on the same trigger (periodic or explicit), producing a single record that contains the values of every member at that instant. This makes it ideal for capturing correlated data (e.g. supply/return temp + valve position + airflow at the same moment).

The object offers the same rich configuration as a regular Trend Log: clock-aligned logging, time windows, buffer management, enable/disable, and optional BUFFER_READY intrinsic reporting when intrinsic_reporting: true. Buffer Retrieval is performed with the BACnet.Protocol.Services.ReadRange service.

Object Description (ASHRAE 135)

A Trend Log Multiple object monitors one or more properties of one or more referenced objects, either in the same device as the Trend Log Multiple object or in an external device. When predefined conditions are met, the object saves ("logs") the value of the properties and a timestamp into an internal buffer for subsequent retrieval.

Trend Log Multiple objects that support intrinsic reporting shall apply the BUFFER_READY event algorithm.

Behaviour and Operation

Trend Log Multiple objects are like Trend Log but log a multiple of properties on each sample (all properties in log_device_object_property are captured). This produces correlated snapshots useful for analyzing cause-effect relationships.

The local logging engine must sample all referenced properties (local or remote) at the same trigger instant (periodic or explicit trigger) and store a single BACnet.Protocol.LogMultipleRecord containing the values.

Retrieval, buffer management, clock alignment, start/stop windows, and BUFFER_READY intrinsic reporting (when enabled) work the same as for a regular Trend Log.

Developer Implementation Notes (geared to device server / application authors)

The generated code handles storage + basic mechanics (validation, implicit_relationships, readonly annotations as hints to your server, etc.). You must drive "special" live properties and side effects yourself, analogous to maintaining present_value on inputs via update_property/3 (never direct mutation). Read notes below + generated tables for details.

Special / live properties and expected developer behaviour

(Very similar to TrendLog; see its notes for the full engine description.)

  • log_device_object_property (now a BACnetArray of refs for multiple): The properties being trended (can be on local or remote devices). Dev must: Your logging task(s) must periodically (polled) or on change (COV) read all the referenced properties (issuing ReadProperty or using COV subs for remotes) and produce LogMultipleRecord entries containing the array of values.

  • log_buffer ([LogMultipleRecord.t()]): The history. Dev must: Own the sampler: on timer or notification, read current values (with status), build record with timestamp + the array of values + status, append or overwrite per stop_when_full using update_property on the buffer (or manage internally and write whole). Enforce buffer_size.

  • record_count, total_record_count (some readonly): Counters. Dev must: Increment record_count on appends; total is cumulative (never resets). Special update overrides allow reset of record_count to 0 under conditions.

  • trigger: For logging_type :triggered. Dev must: Your code writes true to trigger a sample on demand.

  • logging_type, log_interval, cov_resubscription_interval, client_cov_increment: Control sampling mode. Dev must: Your engine reacts to changes (the object has overrides that e.g. flip logging_type on log_interval 0/non0 for compat). Re-schedule your task or COV subs accordingly. property_writable? prevents some changes while enabled.

  • buffer_size, stop_when_full, enable, start_time/stop_time, align_intervals etc: Config. Dev must: Your writer must respect enable/stop/start windows, and for aligned use proper next sample time calculation. buffer_size writes only allowed when !enable (enforced by override).

  • notification_threshold + intrinsic event fields (when enabled): BUFFER_READY. Dev must: On appends, manage records_since_notification, emit when crosses threshold.

  • reliability etc: Source or buffer full issues. Dev must: Set when a referenced prop can't be read, or buffer issues.

Your logging engine is responsible for all sampling and buffer management; the object is the config + storage + enforcement container. See TrendLog notes for more on clock aligned, remote sources, performance, etc.

The BACnet.Stack.TrendLogger module does a lot of this work.

Intrinsic Reporting

When intrinsic_reporting: true is passed to create/4, BUFFER_READY intrinsic reporting is enabled.

Examples

Creating a Trend Log Multiple:

iex> alias BACnet.Protocol.{DeviceObjectPropertyRef, ObjectIdentifier, BACnetArray}
iex> ref = %DeviceObjectPropertyRef{object_identifier: %ObjectIdentifier{type: :analog_input, instance: 1}, property_identifier: :present_value, property_array_index: nil, device_identifier: nil}
iex> {:ok, tlm} = BACnet.Protocol.ObjectTypes.TrendLogMultiple.create(1000, "MultiTrend", %{log_device_object_property: BACnetArray.from_list([ref]), buffer_size: 100, logging_type: :polled, log_interval: 60}); tlm.object_name
"MultiTrend"

With special options:

iex> alias BACnet.Protocol.{DeviceObjectPropertyRef, ObjectIdentifier, BACnetArray}
iex> ref = %DeviceObjectPropertyRef{object_identifier: %ObjectIdentifier{type: :analog_input, instance: 1}, property_identifier: :present_value, property_array_index: nil, device_identifier: nil}
iex> {:ok, tlm} = BACnet.Protocol.ObjectTypes.TrendLogMultiple.create(1001, "AlignedMulti", %{log_device_object_property: BACnetArray.from_list([ref]), buffer_size: 50, logging_type: :polled, log_interval: 300, align_intervals: false, interval_offset: 0}, intrinsic_reporting: true, clock_aligned_logging: true); tlm.object_name
"AlignedMulti"

See Also



The following part has been automatically generated.

Click to expand This module defines a BACnet object of the type `trend_log_multiple`. The following properties are defined: | Property | Revision | Required | Readonly | Protected | Intrinsic | |----------|----------|----------|----------|-----------|-----------| | acked_transitions | | | X | | X | | align_intervals | | | | | | | buffer_size | | X | X | | | | client_cov_increment | | | | | | | cov_resubscription_interval | | | | | | | description | | | | | | | enable | | X | | | | | event_algorithm_inhibit | | | | | X | | event_algorithm_inhibit_ref | | | | | X | | event_detection_enable | | | | | X | | event_enable | | | | | X | | event_message_texts | | | X | | X | | event_message_texts_config | | | | | X | | event_state | | X | | | | | event_timestamps | | | X | | X | | interval_offset | | | | | | | last_notify_record | | | | | X | | limit_enable | | | | | X | | log_buffer | | X | | | | | log_device_object_property | | X | | | | | log_interval | | X | | | | | logging_type | | X | | | | | notification_class | | | | | X | | notification_threshold | | | | | X | | notify_type | | | | | X | | object_instance | | X | X | | | | object_name | | X | X | | | | profile_location | 19 | | | | | | profile_name | | | | | | | record_count | | X | | | | | records_since_notification | | | | | X | | reliability | | | | | | | reliability_evaluation_inhibit | | | | | | | start_time | | X | | | | | status_flags | | X | X | | | | stop_time | | X | | | | | stop_when_full | | X | | | | | tags | 19 | | | | | | time_delay | | | | | X | | time_delay_normal | | | | | X | | total_record_count | | X | X | | | | trigger | | | | | | The following properties have additional semantics: | Property | Has Default | Has Init | Implicit Relationships | Validators | Annotations | |----------|-------------|----------|------------------------|------------|-------------| | align_intervals | | | | | `required_when: {:opts, :clock_aligned_logging}` | | buffer_size | | | | Fun/Type | | | cov_resubscription_interval | | | | Type | | | enable | X | | | | | | event_algorithm_inhibit_ref | | | event_algorithm_inhibit | | | | event_state | X | | | | | | interval_offset | | | | | `required_when: {:opts, :clock_aligned_logging}` | | last_notify_record | X | | | Type | | | log_buffer | X | | | Fun | | | logging_type | | | | Fun | | | notification_threshold | X | | | Type | | | profile_location | | | | Fun | `revision: 19` | | record_count | X | | | Type | | | records_since_notification | X | | | Type | | | reliability | | | reliability_evaluation_inhibit | | | | start_time | X | | | | | | stop_time | X | | | | | | tags | | | | | `revision: 19` | | total_record_count | X | | | Type | | The following table shows the default values and/or init functions: | Property | Default Value | Init Function | |----------|---------------|---------------| | enable | `true` | | | event_state | `:normal` | | | last_notify_record | `0` | | | log_buffer | `[]` | | | notification_threshold | `0` | | | record_count | `0` | | | records_since_notification | `0` | | | start_time | `%BACnet.Protocol.BACnetDateTime{...}` | | | stop_time | `%BACnet.Protocol.BACnetDateTime{...}` | | | total_record_count | `0` | |

Summary

Types

Common object options for creation - all are optional.

Options accepted when creating or configuring a Trend Log Multiple object.

Available property names for this object.

The structure for property errors.

t()

Represents a Trend Log Multiple object. All keys should be treated as read-only, all updates should go only through update_property/3.

Functions

Adds an optional property to an object. Remote objects can not be mutated using this operation.

Creates a new object struct with the defined properties. Optional properties are not created when not given, only required, given and dependency properties are created. Properties with a value of nil are ignored.

Auto generated function to get the names of all properties this object supports.

Auto generated function to get the annotations for the given property name.

Auto generated function to get the list of annotations for each property.

Auto generated function to get the names of properties used for COV reporting.

Auto generated function to get the names of intrinsic properties.

Get the BACnet object identifier.

Auto generated function to get the names of optional properties.

Get the list of properties the object has.

Auto generated function to get a map of property name to type.

Get a property's value from an object.

Auto generated function to get the names of protected properties.

Auto generated function to get the names of readonly properties.

Auto generated function to get the names of required properties.

Checks if the given object has the given property.

Checks if the given object has Intrinsic Reporting enabled.

Checks if the given property is writable.

Removes an optional property from an object. This function is idempotent. Remote objects can not be mutated using this operation.

Auto generated function to check whether the object type supports intrinsic reporting.

Updates a property of an object.

Types

common_object_opts()

@type common_object_opts() ::
  {:allow_numeric_constants, boolean()}
  | {:allow_unknown_properties, boolean()}
  | {:ignore_unknown_properties, boolean()}
  | {:revision, BACnet.Protocol.Constants.protocol_revision()}
  | {:skip_property_validation_remote_object, boolean() | :value}

Common object options for creation - all are optional.

  • allow_numeric_constants - Constants are atoms and thus unknown constants or vendor extensions are integers and thus are rejected. Enabling this option will allow integers (non_neg_integer()) for properties with a Constants.type() spec.
  • allow_unknown_properties - Properties that are unknown to the object implementation are usually rejected. With this option, unknown properties (numeric identifiers usually means we dont know them) are accepted and put into a separate map. This does mean we can not validate or write them. Types of the values can be anything at this point. While you can read unknown properties with atom or integer as property identifier, you can only remove numeric unknown property identifiers from an object. Property identifiers of type atom are only accepted, if it is a remote object (object implementation is only enforced if it is a local object). Numeric property identifiers are accepted regardless of remote object or not. For remote objects, this means you have to write "raw values" (usually Encoding structs).
  • ignore_unknown_properties - Properties that are unknown to the object implementation are usually rejected. With this option, unknown properties get ignored, as if they were not specified.
  • revision - The BACnet protocol revision to check required properties against. Optional properties are regardless of revision available. See BACnet.Protocol.Constants.protocol_revision/0 for the available revisions.
  • skip_property_validation_remote_object - Skips property validation for remote objects. Sometimes it is possible that the value is invalid as per BACnet specification (i.e. value 0 for a multistate object), but you still want those to be represented. Value true neither type nor value are validated. Value :value means the type is still validated and only the value validator is not run (if present). The property's validator_fun will also be skipped.

object_opts()

@type object_opts() ::
  {:clock_aligned_logging, boolean()}
  | {:intrinsic_reporting, boolean()}
  | common_object_opts()

Options accepted when creating or configuring a Trend Log Multiple object.

In addition to the common options, Trend Log Multiple supports:

  • clock_aligned_logging - Enables clock-aligned logging intervals.
  • intrinsic_reporting - Enables BUFFER_READY intrinsic reporting.

property_name()

@type property_name() ::
  :acked_transitions
  | :align_intervals
  | :buffer_size
  | :client_cov_increment
  | :cov_resubscription_interval
  | :description
  | :enable
  | :event_algorithm_inhibit
  | :event_algorithm_inhibit_ref
  | :event_detection_enable
  | :event_enable
  | :event_message_texts
  | :event_message_texts_config
  | :event_state
  | :event_timestamps
  | :interval_offset
  | :last_notify_record
  | :limit_enable
  | :log_buffer
  | :log_device_object_property
  | :log_interval
  | :logging_type
  | :notification_class
  | :notification_threshold
  | :notify_type
  | :object_instance
  | :object_name
  | :profile_location
  | :profile_name
  | :record_count
  | :records_since_notification
  | :reliability
  | :reliability_evaluation_inhibit
  | :start_time
  | :status_flags
  | :stop_time
  | :stop_when_full
  | :tags
  | :time_delay
  | :time_delay_normal
  | :total_record_count
  | :trigger

Available property names for this object.

property_update_error()

@type property_update_error() ::
  {:error,
   {error :: atom(),
    property :: BACnet.Protocol.Constants.property_identifier()}}

The structure for property errors.

t()

@type t() :: %BACnet.Protocol.ObjectTypes.TrendLogMultiple{
  _metadata: internal_metadata(),
  _unknown_properties: %{
    optional(atom() | non_neg_integer()) =>
      term()
      | BACnet.Protocol.ApplicationTags.Encoding.t()
      | [BACnet.Protocol.ApplicationTags.Encoding.t()]
  },
  acked_transitions: BACnet.Protocol.EventTransitionBits.t() | nil,
  align_intervals: boolean() | nil,
  buffer_size: BACnet.Protocol.ApplicationTags.unsigned32(),
  client_cov_increment: (float() | nil) | nil,
  cov_resubscription_interval: pos_integer() | nil,
  description: String.t() | nil,
  enable: boolean(),
  event_algorithm_inhibit: boolean() | nil,
  event_algorithm_inhibit_ref: BACnet.Protocol.ObjectPropertyRef.t() | nil,
  event_detection_enable: boolean() | nil,
  event_enable: BACnet.Protocol.EventTransitionBits.t() | nil,
  event_message_texts: BACnet.Protocol.EventMessageTexts.t() | nil,
  event_message_texts_config: BACnet.Protocol.EventMessageTexts.t() | nil,
  event_state:
    BACnet.Protocol.Constants.event_state()
    | (reserved_or_vendor_extension :: non_neg_integer()),
  event_timestamps: BACnet.Protocol.EventTimestamps.t() | nil,
  interval_offset: non_neg_integer() | nil,
  last_notify_record: BACnet.Protocol.ApplicationTags.unsigned32() | nil,
  limit_enable: BACnet.Protocol.LimitEnable.t() | nil,
  log_buffer: [BACnet.Protocol.LogMultipleRecord.t()],
  log_device_object_property:
    BACnet.Protocol.BACnetArray.t(BACnet.Protocol.DeviceObjectPropertyRef.t()),
  log_interval: non_neg_integer(),
  logging_type:
    BACnet.Protocol.Constants.logging_type()
    | (reserved_or_vendor_extension :: non_neg_integer()),
  notification_class: non_neg_integer() | nil,
  notification_threshold: BACnet.Protocol.ApplicationTags.unsigned32() | nil,
  notify_type:
    BACnet.Protocol.Constants.notify_type()
    | (reserved_or_vendor_extension :: non_neg_integer())
    | nil,
  object_instance: non_neg_integer(),
  object_name: String.t(),
  profile_location: String.t() | nil,
  profile_name: String.t() | nil,
  record_count: BACnet.Protocol.ApplicationTags.unsigned32(),
  records_since_notification:
    BACnet.Protocol.ApplicationTags.unsigned32() | nil,
  reliability:
    BACnet.Protocol.Constants.reliability()
    | (reserved_or_vendor_extension :: non_neg_integer())
    | nil,
  reliability_evaluation_inhibit: boolean() | nil,
  start_time: BACnet.Protocol.BACnetDateTime.t(),
  status_flags: BACnet.Protocol.StatusFlags.t(),
  stop_time: BACnet.Protocol.BACnetDateTime.t(),
  stop_when_full: boolean(),
  tags: BACnet.Protocol.BACnetArray.t(BACnet.Protocol.NameValue.t()) | nil,
  time_delay: non_neg_integer() | nil,
  time_delay_normal: non_neg_integer() | nil,
  total_record_count: BACnet.Protocol.ApplicationTags.unsigned32(),
  trigger: boolean() | nil
}

Represents a Trend Log Multiple object. All keys should be treated as read-only, all updates should go only through update_property/3.

Properties which are for Intrinsic Reporting are nil, if disabled. If Intrinsic Reporting is enabled on the object, then the properties can not be nil.

Functions

add_property(object, property, value)

@spec add_property(t(), BACnet.Protocol.Constants.property_identifier(), term()) ::
  {:ok, t()} | property_update_error()

Adds an optional property to an object. Remote objects can not be mutated using this operation.

Please note that properties of services can not be dynamically added and instead the object must be newly created using create/4.

create(instance_number, object_name, properties \\ %{}, opts \\ [])

@spec create(
  non_neg_integer(),
  String.t(),
  %{optional(property_name() | atom() | non_neg_integer()) => term()},
  [object_opts() | internal_metadata()]
) :: {:ok, t()} | property_update_error()

Creates a new object struct with the defined properties. Optional properties are not created when not given, only required, given and dependency properties are created. Properties with a value of nil are ignored.

Only properties that are required for specific services (i.e. Intrinsic Reporting) are automatically created.

get_all_properties()

@spec get_all_properties() :: [BACnet.Protocol.Constants.property_identifier()]

Auto generated function to get the names of all properties this object supports.

get_annotation(name)

@spec get_annotation(property_name()) :: [term()]

Auto generated function to get the annotations for the given property name.

get_annotations()

@spec get_annotations() :: [{name :: property_name(), values :: [term()]}]

Auto generated function to get the list of annotations for each property.

get_cov_properties()

@spec get_cov_properties() :: [BACnet.Protocol.Constants.property_identifier()]

Auto generated function to get the names of properties used for COV reporting.

get_intrinsic_properties()

@spec get_intrinsic_properties() :: [BACnet.Protocol.Constants.property_identifier()]

Auto generated function to get the names of intrinsic properties.

get_object_identifier(object)

@spec get_object_identifier(t()) :: BACnet.Protocol.ObjectIdentifier.t()

Get the BACnet object identifier.

get_optional_properties()

@spec get_optional_properties() :: [BACnet.Protocol.Constants.property_identifier()]

Auto generated function to get the names of optional properties.

get_properties(object)

@spec get_properties(t()) :: [BACnet.Protocol.Constants.property_identifier()]

Get the list of properties the object has.

get_properties_type_map()

@spec get_properties_type_map() :: map()

Auto generated function to get a map of property name to type.

get_property(object, property)

Get a property's value from an object.

get_protected_properties()

@spec get_protected_properties() :: [BACnet.Protocol.Constants.property_identifier()]

Auto generated function to get the names of protected properties.

Protected is an annotation and the object modules prevent writing to this property directly in code. The protected properties are either written on creation or updated automatically depending on other properties being written to. Some properties are only written once at creation and never updated.

get_readonly_properties()

@spec get_readonly_properties() :: [BACnet.Protocol.Constants.property_identifier()]

Auto generated function to get the names of readonly properties.

Readonly is only an annotation that the property should be write protected on the BACnet side, there is no actual write protection in the object. This is a hint to the device server. If you need actual write protection, see protected.

get_required_properties()

@spec get_required_properties() :: [BACnet.Protocol.Constants.property_identifier()]

Auto generated function to get the names of required properties.

has_property?(object, property)

Checks if the given object has the given property.

See BACnet.Protocol.ObjectsUtility.has_property?/2 for implementation details.

intrinsic_reporting?(object)

@spec intrinsic_reporting?(t()) :: boolean()

Checks if the given object has Intrinsic Reporting enabled.

property_writable?(object, property)

@spec property_writable?(t(), BACnet.Protocol.Constants.property_identifier()) ::
  boolean()

Checks if the given property is writable.

Check BACnet.Protocol.ObjectsUtility.property_writable?/2 for a basic run-down.

remove_property(object, property)

@spec remove_property(
  t(),
  BACnet.Protocol.Constants.property_identifier() | non_neg_integer()
) ::
  {:ok, t()} | property_update_error()

Removes an optional property from an object. This function is idempotent. Remote objects can not be mutated using this operation.

Please note that properties of services can not be dynamically removed and instead the object must be newly created using create/4. Required properties can not be removed.

supports_intrinsic()

@spec supports_intrinsic() :: boolean()

Auto generated function to check whether the object type supports intrinsic reporting.

update_property(object, property, value)

@spec update_property(t(), BACnet.Protocol.Constants.property_identifier(), term()) ::
  {:ok, t()} | property_update_error()

Updates a property of an object.