The Loop object is the standard BACnet representation of a closed-loop controller
(PID, PI, P, or any other feedback algorithm). It continuously computes a
present_value (the controlled variable or the output, depending on the
implementation) from a controlled_variable_reference, a setpoint_reference,
and the three tuning parameters proportional_constant, integral_constant,
and derivative_constant (plus bias and other parameters).
When intrinsic_reporting: true the FLOATING_LIMIT event algorithm is
available for alarming on deviation from setpoint.
Many additional properties (update_interval, deadband, etc.) control
the loop behaviour and event generation.
Object Description (ASHRAE 135)
The Loop object type defines a standardized object whose properties represent the externally visible characteristics of any form of feedback control loop.
Behaviour and Operation
Loop objects represent closed-loop controllers (PID or similar). The local
control engine (not the BACnet stack) is responsible for periodically reading the
controlled_variable_reference (and setpoint reference), running the control
algorithm using the proportional_constant, integral_constant,
derivative_constant, bias, action, etc., and writing the result into
present_value (and the manipulated variable).
The object merely stores the tuning parameters, references, and current state
(present_value, controlled_variable_value, etc.). BACnet clients can read the
state for monitoring and can write tuning parameters
(subject to any application-level interlocks).
When intrinsic reporting is enabled, the FLOATING_LIMIT algorithm can alarm on excessive deviation between the controlled variable and the setpoint.
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
present_value: The output of the control algorithm (manipulated variable or the loop's computed output). Dev must: Your control task (PID etc) reads the controlled var and setpoint (via the references), runs the math using the constants on the object, then writes the result to the manipulated_variable_reference (another object at priority) and/or to this present_value for monitoring. This is the live output.controlled_variable_reference,setpoint_reference,manipulated_variable_reference: The I/O wiring for the loop. Dev must: If remote refs, your task does the Read/WriteProperty. Local ones you can access directly. The loop object just holds the config.proportional_constant, etc. (with implicit _units),action,bias,outputlimits, etc.: Tuning and behaviour params. *Dev must: Your algorithm uses the values from the object.status_flags,out_of_service,reliability: Dev must:out_of_service: Hold output.in_alarm/fault/out_of_servicebits ofstatus_flagsare auto-updated by the object.Intrinsic event properties: Dev must: Re-eval after relevant changes.
See the "The control loop runs in your code" section below for the exact steps your task must perform on its schedule.
A Loop object is a data + parameter container for a control algorithm. The actual PID (or similar) math lives in your control task, not in the BACnet object.
The control loop runs in your code: On whatever schedule makes sense for the process you do:
- Read the current value of the
controlled_variable_reference(may be remote - you do the ReadProperty). - Read the
setpoint_reference(or the localsetpointproperty). - Read all the tuning constants,
bias,action(direct/reverse), min/max output, etc. from the Loop object. - Run your PID (or PI, P, fuzzy, …) algorithm.
- Write the result into the
manipulated_variable_reference(the thing you are really controlling - usually an Analog Output or an Analog Value) and into the Loop's ownpresent_value.
After you write to the manipulated variable (and to the Loop's PV) you should
also update controlled_variable_value (and setpoint if you are using the
local one) on the Loop object, so that a remote operator sees a consistent
snapshot of what the loop "thinks" is happening right now.
Priority interaction: Because the manipulated variable is often a
commandable Analog Output or Analog Value, the Loop should normally write at
a well-known priority (the Loop object itself exposes a priority_for_writing).
This way a schedule, an operator, or a fire interlock at a higher priority
can override the loop without the loop fighting it.
Intrinsic FLOATING_LIMIT: This algorithm is specifically designed for loops.
It typically alarms when the controlled variable is "floating" too far from
the setpoint for too long (using the time_delay, high_limit, low_limit,
deadband etc. that live on the Loop). After you update the controlled
variable or the setpoint on the object, run the FLOATING_LIMIT evaluation and
drive the event state machine.
Writing tuning parameters: A good HMI will let an operator tweak Kp, Ki, Kd, bias, etc. while the loop is running. Your control task simply picks up the new numbers on the next cycle.
Remote references: Both the controlled variable and the setpoint (and the manipulated variable) can be on other devices. Your control task becomes a mini gateway - it does the reads and writes on the network schedule that the loop requires.
Reliability: A loop can report :no_fault_detected, :process_error (the
actuator is not responding), :communication_failure (can't read the sensor),
:configuration_error (the references point at the wrong type of object), etc.
Your control task is the place that can detect most of these conditions.
The generated tables at the bottom of its moduledoc are the best place to see all the optional parameters (proportional_constant_units, integral_constant_units, …, maximum_output, minimum_output, update_interval, …) and which ones have implicit relationships (the constant + its unit field).
In short: the Loop object is where an operator or a configuration tool goes to see and change what the loop is doing and how it is tuned. The actual closed-loop arithmetic, the scheduling of the arithmetic, the reading of remote sensors and the writing of remote actuators, the bumpless transfer logic, and the safety interlocks all live in your control engine.
Intrinsic Reporting
When intrinsic_reporting: true is passed to create/4, the FLOATING_LIMIT
event algorithm and related properties become active.
Examples
Creating a Loop:
iex> {:ok, l} = BACnet.Protocol.ObjectTypes.Loop.create(1200, "TempPID", %{}); l.object_name
"TempPID"See Also
The following part has been automatically generated.
Click to expand
This module defines a BACnet object of the type `loop`. The following properties are defined: | Property | Revision | Required | Readonly | Protected | Intrinsic | |----------|----------|----------|----------|-----------|-----------| | acked_transitions | | | X | | X | | action | | X | | | | | bias | | | | | | | controlled_variable_reference | | X | | | | | controlled_variable_units | | X | | | | | controlled_variable_value | | X | | | | | cov_increment | | | | | | | deadband | | | | | X | | derivative_constant | | | | | | | derivative_constant_units | | | | | | | description | | | | | | | error_limit | | | | | 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 | | integral_constant | | | | | | | integral_constant_units | | | | | | | limit_enable | | | | | X | | manipulated_variable_reference | | X | | | | | max_output | | | | | | | min_output | | | | | | | notification_class | | | | | X | | notify_type | | | | | X | | object_instance | | X | X | | | | object_name | | X | X | | | | out_of_service | | X | | | | | output_units | | X | | | | | present_value | | X | X | | | | priority_for_writing | | X | | | | | profile_location | 19 | | | | | | profile_name | | | | | | | proportional_constant | | | | | | | proportional_constant_units | | | | | | | reliability | | | | | | | reliability_evaluation_inhibit | | | | | | | setpoint | | X | | | | | setpoint_reference | | X | | | | | status_flags | | X | X | | | | tags | 19 | | | | | | time_delay | | | | | X | | time_delay_normal | | | | | X | | update_interval | | | | | | The following properties have additional semantics: | Property | Has Default | Has Init | Implicit Relationships | Validators | Annotations | |----------|-------------|----------|------------------------|------------|-------------| | action | X | | | | | | controlled_variable_reference | X | | | | | | controlled_variable_units | X | | | | | | controlled_variable_value | X | | | | | | cov_increment | X | | | | | | deadband | X | | | | | | derivative_constant | | | derivative_constant_units | | | | error_limit | X | | | | | | event_algorithm_inhibit_ref | | | event_algorithm_inhibit | | | | integral_constant | | | integral_constant_units | | | | manipulated_variable_reference | X | | | | | | output_units | X | | | | | | present_value | X | | | | | | priority_for_writing | X | | | | | | profile_location | | | | Fun | `revision: 19` | | proportional_constant | | | proportional_constant_units | | | | reliability | | | reliability_evaluation_inhibit | | | | setpoint | X | | | | | | setpoint_reference | X | | | | | | tags | | | | | `revision: 19` | The following table shows the default values and/or init functions: | Property | Default Value | Init Function | |----------|---------------|---------------| | action | `:direct` | | | controlled_variable_reference | `%BACnet.Protocol.ObjectPropertyRef{...}` | | | controlled_variable_units | `:no_units` | | | controlled_variable_value | `0.0` | | | cov_increment | `0.1` | | | deadband | `0.0` | | | error_limit | `0.0` | | | manipulated_variable_reference | `%BACnet.Protocol.ObjectPropertyRef{...}` | | | output_units | `:no_units` | | | present_value | `0.0` | | | priority_for_writing | `16` | | | setpoint | `0.0` | | | setpoint_reference | `%BACnet.Protocol.SetpointReference{...}` | |Summary
Types
Common object options for creation - all are optional.
Options accepted when creating or configuring a Loop object.
Available property names for this object.
The structure for property errors.
Represents a Loop 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
@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 aConstants.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 typeatomare 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" (usuallyEncodingstructs).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. SeeBACnet.Protocol.Constants.protocol_revision/0for 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. Valuetrueneither type nor value are validated. Value:valuemeans the type is still validated and only the value validator is not run (if present). The property'svalidator_funwill also be skipped.
@type object_opts() :: common_object_opts()
Options accepted when creating or configuring a Loop object.
@type property_name() ::
:acked_transitions
| :action
| :bias
| :controlled_variable_reference
| :controlled_variable_units
| :controlled_variable_value
| :cov_increment
| :deadband
| :derivative_constant
| :derivative_constant_units
| :description
| :error_limit
| :event_algorithm_inhibit
| :event_algorithm_inhibit_ref
| :event_detection_enable
| :event_enable
| :event_message_texts
| :event_message_texts_config
| :event_state
| :event_timestamps
| :integral_constant
| :integral_constant_units
| :limit_enable
| :manipulated_variable_reference
| :max_output
| :min_output
| :notification_class
| :notify_type
| :object_instance
| :object_name
| :out_of_service
| :output_units
| :present_value
| :priority_for_writing
| :profile_location
| :profile_name
| :proportional_constant
| :proportional_constant_units
| :reliability
| :reliability_evaluation_inhibit
| :setpoint
| :setpoint_reference
| :status_flags
| :tags
| :time_delay
| :time_delay_normal
| :update_interval
Available property names for this object.
@type property_update_error() :: {:error, {error :: atom(), property :: BACnet.Protocol.Constants.property_identifier()}}
The structure for property errors.
@type t() :: %BACnet.Protocol.ObjectTypes.Loop{ _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, action: BACnet.Protocol.Constants.action() | (reserved_or_vendor_extension :: non_neg_integer()), bias: float() | nil, controlled_variable_reference: BACnet.Protocol.ObjectPropertyRef.t(), controlled_variable_units: BACnet.Protocol.Constants.engineering_unit() | (reserved_or_vendor_extension :: non_neg_integer()), controlled_variable_value: float(), cov_increment: float() | nil, deadband: float() | nil, derivative_constant: float() | nil, derivative_constant_units: BACnet.Protocol.Constants.engineering_unit() | (reserved_or_vendor_extension :: non_neg_integer()) | nil, description: String.t() | nil, error_limit: float() | nil, 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, integral_constant: float() | nil, integral_constant_units: BACnet.Protocol.Constants.engineering_unit() | (reserved_or_vendor_extension :: non_neg_integer()) | nil, limit_enable: BACnet.Protocol.LimitEnable.t() | nil, manipulated_variable_reference: BACnet.Protocol.ObjectPropertyRef.t(), max_output: float() | nil, min_output: float() | nil, notification_class: non_neg_integer() | 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(), out_of_service: boolean(), output_units: BACnet.Protocol.Constants.engineering_unit() | (reserved_or_vendor_extension :: non_neg_integer()), present_value: float(), priority_for_writing: 1..16, profile_location: String.t() | nil, profile_name: String.t() | nil, proportional_constant: float() | nil, proportional_constant_units: BACnet.Protocol.Constants.engineering_unit() | (reserved_or_vendor_extension :: non_neg_integer()) | nil, reliability: BACnet.Protocol.Constants.reliability() | (reserved_or_vendor_extension :: non_neg_integer()) | nil, reliability_evaluation_inhibit: boolean() | nil, setpoint: float(), setpoint_reference: BACnet.Protocol.SetpointReference.t(), status_flags: BACnet.Protocol.StatusFlags.t(), tags: BACnet.Protocol.BACnetArray.t(BACnet.Protocol.NameValue.t()) | nil, time_delay: non_neg_integer() | nil, time_delay_normal: non_neg_integer() | nil, update_interval: non_neg_integer() | nil }
Represents a Loop object. All keys should be treated as read-only,
all updates should go only through update_property/3.
Functions
@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.
@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.
By default, a default cov_increment of 0.1 is used.
It is strongly advised to change this to something reasonable for the application.
@spec get_all_properties() :: [BACnet.Protocol.Constants.property_identifier()]
Auto generated function to get the names of all properties this object supports.
@spec get_annotation(property_name()) :: [term()]
Auto generated function to get the annotations for the given property name.
@spec get_annotations() :: [{name :: property_name(), values :: [term()]}]
Auto generated function to get the list of annotations for each property.
@spec get_cov_properties() :: [BACnet.Protocol.Constants.property_identifier()]
Auto generated function to get the names of properties used for COV reporting.
@spec get_intrinsic_properties() :: [BACnet.Protocol.Constants.property_identifier()]
Auto generated function to get the names of intrinsic properties.
@spec get_object_identifier(t()) :: BACnet.Protocol.ObjectIdentifier.t()
Get the BACnet object identifier.
@spec get_optional_properties() :: [BACnet.Protocol.Constants.property_identifier()]
Auto generated function to get the names of optional properties.
@spec get_properties(t()) :: [BACnet.Protocol.Constants.property_identifier()]
Get the list of properties the object has.
@spec get_properties_type_map() :: map()
Auto generated function to get a map of property name to type.
@spec get_property( t(), BACnet.Protocol.Constants.property_identifier() | non_neg_integer() ) :: {:ok, term()} | property_update_error()
Get a property's value from an object.
@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.
@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.
@spec get_required_properties() :: [BACnet.Protocol.Constants.property_identifier()]
Auto generated function to get the names of required properties.
@spec has_property?(t(), BACnet.Protocol.Constants.property_identifier()) :: boolean()
Checks if the given object has the given property.
See BACnet.Protocol.ObjectsUtility.has_property?/2 for implementation details.
Checks if the given object has Intrinsic Reporting enabled.
@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.
@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.
@spec supports_intrinsic() :: boolean()
Auto generated function to check whether the object type supports intrinsic reporting.
@spec update_property(t(), BACnet.Protocol.Constants.property_identifier(), term()) :: {:ok, t()} | property_update_error()
Updates a property of an object.