Current section
Files
Jump to
Current section
Files
lib/bacnet/protocol/setpoint_reference.ex
defmodule BACnet.Protocol.SetpointReference do
@moduledoc """
A Setpoint Reference is a nullable pointer to another property that should be
treated as the setpoint for a control loop object (Loop, Analog Output, etc.).
When the reference is present, the control algorithm is expected to treat the
value of the referenced property as its current setpoint rather than using a
locally configured setpoint. This indirection is extremely useful in cascaded
control strategies, in demand-limiting applications, and anywhere the desired
setpoint is computed or selected by some other object in the system.
The reference uses the common Object Property Reference type so that it can
point at properties in the same device or, when the full Device + Object form
is used, at properties in other devices.
### Examples (Doc Test)
```elixir
iex> ref = %SetpointReference{
...> ref: %ObjectPropertyRef{
...> object_identifier: %ObjectIdentifier{type: :analog_value, instance: 5},
...> property_identifier: :present_value,
...> property_array_index: nil
...> }
...> }
iex> ref.ref.property_identifier
:present_value
```
"""
# TODO: Throw argument error in encode if not valid
alias BACnet.Protocol.ApplicationTags
alias BACnet.Protocol.ObjectPropertyRef
import BACnet.Protocol.Utility, only: [pattern_extract_tags: 4]
@typedoc """
Optionally references another property to be used as the setpoint for a control loop.
"""
@type t :: %__MODULE__{
ref: ObjectPropertyRef.t() | nil
}
@fields [
:ref
]
@enforce_keys @fields
defstruct @fields
@doc """
Encodes a BACnet setpoint reference into application tags encoding.
"""
@spec encode(t(), Keyword.t()) ::
{:ok, ApplicationTags.encoding_list()} | {:error, term()}
def encode(%__MODULE__{} = setpoint, opts \\ []) do
if setpoint.ref do
with {:ok, ref} <- ObjectPropertyRef.encode(setpoint.ref, opts) do
{:ok, [constructed: {0, ref, 0}]}
end
else
{:ok, []}
end
end
@doc """
Parses a BACnet setpoint reference from application tags encoding.
"""
@spec parse(ApplicationTags.encoding_list()) ::
{:ok, {t(), rest :: ApplicationTags.encoding_list()}} | {:error, term()}
def parse(tags) when is_list(tags) do
with {:ok, {:constructed, {0, ref_raw, _len}}, rest} when ref_raw != [] <-
pattern_extract_tags(tags, {:constructed, {0, _t, _l}}, nil, true),
{:ok, {ref, _rest}} <- ObjectPropertyRef.parse(List.wrap(ref_raw)) do
setp = %__MODULE__{
ref: ref
}
{:ok, {setp, rest}}
else
{:ok, {:constructed, {0, _raw, _len}}, rest} -> {:ok, {%__MODULE__{ref: nil}, rest}}
{:ok, nil, rest} -> {:ok, {%__MODULE__{ref: nil}, rest}}
{:error, _err} = err -> err
end
end
@doc """
Validates whether the given setpoint reference is in form valid.
It only validates the struct is valid as per type specification.
"""
@spec valid?(t()) :: boolean()
def valid?(t)
def valid?(
%__MODULE__{
ref: %ObjectPropertyRef{} = ref
} = _t
) do
ObjectPropertyRef.valid?(ref)
end
def valid?(%__MODULE__{ref: nil} = _t), do: true
def valid?(%__MODULE__{} = _t), do: false
end