Packages

BACstack is a low-level Elixir implementation for the ASHRAE standard 135, BACnet - Building Automation and Controller network. In its current published state, v0.0.x, it can (only) act as a BACnet client for other BACnet devices.

Current section

Files

Jump to
bacstack lib bacnet protocol fault_parameters.ex
Raw

lib/bacnet/protocol/fault_parameters.ex

defmodule BACnet.Protocol.FaultParameters do
@moduledoc """
BACnet has various different types of fault parameters.
Each of them is represented by a different module.
Consult the module `BACnet.Protocol.FaultAlgorithms` for
details about each fault's algorithm.
"""
# TODO: Docs
# TODO: Throw argument error in encode if not valid
alias BACnet.Protocol.ApplicationTags
alias BACnet.Protocol.Constants
alias BACnet.Protocol.DeviceObjectPropertyRef
require Constants
@typedoc """
Possible BACnet fault parameters.
"""
@type fault_parameter ::
__MODULE__.None.t()
| __MODULE__.FaultCharacterString.t()
| __MODULE__.FaultExtended.t()
| __MODULE__.FaultLifeSafety.t()
| __MODULE__.FaultState.t()
| __MODULE__.FaultStatusFlags.t()
defmodule None do
@moduledoc """
Represents the BACnet fault algorithm `None` parameters.
The NONE fault algorithm is a placeholder for the case where no fault algorithm is applied by the object.
This fault algorithm has no parameters, no conditions, and does not indicate any transitions of reliability.
For more specific information about the fault algorithm, consult ASHRAE 135 13.4.1.
"""
require BACnet.Protocol.Constants
@typedoc """
Representative type for the fault parameter.
"""
@type t :: %__MODULE__{}
defstruct []
@doc false
def get_tag_number(), do: 0
end
defmodule FaultCharacterString do
@moduledoc """
Represents the BACnet fault algorithm `FaultCharacterString` parameters.
The FAULT_CHRACTERSTRING fault algorithm detects whether the monitored value matches a
character string that is listed as a fault value. Fault values are of type
BACnetOptionalCharacterString and may also be NULL or an empty character string.
For more specific information about the fault algorithm, consult ASHRAE 135 13.4.2.
"""
import BACnet.Macro
require BACnet.Protocol.Constants
@typedoc """
Representative type for the fault parameter.
"""
typedstruct do
field(:fault_values, [String.t()], required: true)
end
@doc false
def get_tag_number(), do: 1
end
defmodule FaultExtended do
@moduledoc """
Represents the BACnet fault algorithm `FaultExtended` parameters.
The FAULT_EXTENDED fault algorithm detects fault conditions based on a
proprietary fault algorithm. The proprietary fault algorithm uses parameters
and conditions defined by the vendor. The algorithm is identified by a
vendor-specific fault type that is in the scope of the vendor's
vendor identification code. The algorithm may, at the vendor's discretion,
indicate a new reliability, a transition to the same reliability, or
no transition to the reliability-evaluation process.
For more specific information about the fault algorithm, consult ASHRAE 135 13.4.3.
"""
import BACnet.Macro
require BACnet.Protocol.Constants
@typedoc """
Representative type for the fault parameter.
"""
typedstruct do
field(:vendor_id, BACnet.Protocol.ApplicationTags.unsigned16(), required: true)
field(:extended_fault_type, non_neg_integer(), required: true)
field(:parameters, BACnet.Protocol.ApplicationTags.encoding_list(), required: true)
end
@doc false
def get_tag_number(), do: 2
end
defmodule FaultLifeSafety do
@moduledoc """
Represents the BACnet fault algorithm `FaultLifeSafety` parameters.
The FAULT_LIFE_SAFETY fault algorithm detects whether the monitored value equals
a value that is listed as a fault value.
The monitored value is of type BACnetLifeSafetyState. If internal operational
reliability is unreliable, then the internal reliability takes precedence over
evaluation of the monitored value.
In addition, this algorithm monitors a life safety mode value. If reliability is
MULTI_STATE_FAULT, then new transitions to MULTI_STATE_FAULT are indicated upon
change of the mode value.
For more specific information about the fault algorithm, consult ASHRAE 135 13.4.4.
"""
import BACnet.Macro
require BACnet.Protocol.Constants
@typedoc """
Representative type for the fault parameter.
"""
typedstruct do
field(:mode, BACnet.Protocol.DeviceObjectPropertyRef.t(), required: true)
field(:fault_values, [BACnet.Protocol.Constants.life_safety_state()], required: true)
end
@doc false
def get_tag_number(), do: 3
end
defmodule FaultState do
@moduledoc """
Represents the BACnet fault algorithm `FaultState` parameters.
The FAULT_STATE fault algorithm detects whether the monitored value
equals a value that is listed as a fault value. The monitored value
may be of any discrete or enumerated datatype, including Boolean.
If internal operational reliability is unreliable, then the
internal reliability takes precedence over evaluation of the monitored value.
For more specific information about the fault algorithm, consult ASHRAE 135 13.4.5.
"""
import BACnet.Macro
@typedoc """
Representative type for the fault parameter.
"""
typedstruct do
field(:fault_values, [BACnet.Protocol.PropertyState.t()], required: true)
end
@doc false
def get_tag_number(), do: 4
end
defmodule FaultStatusFlags do
@moduledoc """
Represents the BACnet fault algorithm `FaultStatusFlags` parameters.
The FAULT_STATUS_FLAGS fault algorithm detects whether the monitored
status flags are indicating a fault condition.
For more specific information about the fault algorithm, consult ASHRAE 135 13.4.6.
"""
import BACnet.Macro
require BACnet.Protocol.Constants
@typedoc """
Representative type for the fault parameter.
"""
typedstruct do
field(:status_flags, BACnet.Protocol.DeviceObjectPropertyRef.t(), required: true)
end
@doc false
def get_tag_number(), do: 5
end
# TODO: Docs
@spec encode(fault_parameter(), Keyword.t()) ::
{:ok, ApplicationTags.encoding()} | {:error, term()}
def encode(fault_params, opts \\ [])
def encode(%None{} = _params, _opts) do
{:ok, {:constructed, {0, {:null, nil}, 0}}}
end
def encode(%FaultCharacterString{} = params, _opts) do
with true <-
is_list(params.fault_values) and
Enum.all?(params.fault_values, &(is_binary(&1) and String.valid?(&1))),
favalues when is_list(favalues) <-
Enum.map(params.fault_values, &{:character_string, &1}) do
{:ok,
{:constructed,
{1,
[
constructed: {0, favalues, 0}
], 0}}}
else
false ->
{:error, :invalid_params}
# {:error, _err} = err -> err
end
end
def encode(%FaultExtended{} = params, opts) do
with true <-
is_integer(params.vendor_id) and params.vendor_id >= 0 and params.vendor_id <= 65_535,
true <- is_integer(params.extended_fault_type) and params.extended_fault_type >= 0,
{:ok, vendor_id, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.vendor_id}, opts),
{:ok, extended_fault_type, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.extended_fault_type}, opts) do
{:ok,
{:constructed,
{2,
[
tagged: {0, vendor_id, byte_size(vendor_id)},
tagged: {1, extended_fault_type, byte_size(extended_fault_type)},
constructed: {2, params.parameters, 0}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%FaultLifeSafety{} = params, opts) do
with true <- is_struct(params.mode, DeviceObjectPropertyRef),
true <- is_list(params.fault_values) and Enum.all?(params.fault_values, &is_atom/1),
{:ok, fault_values} <-
Enum.reduce_while(params.fault_values, {:ok, []}, fn enum, {:ok, acc} ->
case Constants.by_name(:property_state, enum) do
{:ok, val} -> {:cont, {:ok, [{:enumerated, val} | acc]}}
:error -> {:halt, {:error, {:unknown_property_State, enum}}}
end
end),
{:ok, mode} <- DeviceObjectPropertyRef.encode(params.mode, opts) do
{:ok,
{:constructed,
{3,
[
constructed: {0, fault_values, 0},
constructed: {1, mode, 0}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%FaultState{} = params, _opts) do
with true <- is_list(params.fault_values) and Enum.all?(params.fault_values, &is_atom/1),
{:ok, fault_values} <-
Enum.reduce_while(params.fault_values, {:ok, []}, fn enum, {:ok, acc} ->
case Constants.by_name(:property_state, enum) do
{:ok, val} -> {:cont, {:ok, [{:enumerated, val} | acc]}}
:error -> {:halt, {:error, {:unknown_property_State, enum}}}
end
end) do
{:ok,
{:constructed,
{4,
[
constructed: {0, Enum.reverse(fault_values), 0}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%FaultStatusFlags{} = params, opts) do
with true <- is_struct(params.status_flags, StatusFlags),
{:ok, flags} <- DeviceObjectPropertyRef.encode(params.status_flags, opts) do
{:ok,
{:constructed,
{5,
[
constructed: {0, flags, 0}
], 0}}}
end
end
# TODO: Docs
@spec parse(binary()) :: {:ok, fault_parameter()} | {:error, term()}
def parse(fault_values_tag)
# 0 = None
def parse({:constructed, {0, fault_values_tags, 0}}) do
case fault_values_tags do
{:null, nil} -> {:ok, %None{}}
_term -> {:error, :invalid_fault_values}
end
end
# 1 = Fault Character String
def parse({:constructed, {1, fault_values_tags, 0}}) do
case fault_values_tags do
[
constructed: {0, strings, _length2}
] ->
with favalues when is_list(favalues) <-
Enum.map(strings, fn {:character_string, str} -> str end) do
fault = %FaultCharacterString{
fault_values: favalues
}
{:ok, fault}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_fault_values}
end
end
# 2 = Extended
def parse({:constructed, {2, fault_values_tags, 0}}) do
case fault_values_tags do
[
tagged: {0, vendor_id_raw, _length},
tagged: {1, ext_fault_raw, _length2},
# TODO: May be not constructed (tagged)
constructed: {_con, 2, parameters, _length3}
] ->
with {:ok, {:unsigned_integer, vendor_id}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, vendor_id_raw),
:ok <-
if(ApplicationTags.valid_int?(vendor_id, 16),
do: :ok,
else: {:error, :invalid_vendor_id_value}
),
{:ok, {:unsigned_integer, ext_fault}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, ext_fault_raw) do
fault = %FaultExtended{
vendor_id: vendor_id,
extended_fault_type: ext_fault,
parameters: parameters
}
{:ok, fault}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_fault_values}
end
end
# 3 = Fault Life Safety
def parse({:constructed, {3, fault_values_tags, 0}}) do
case fault_values_tags do
[
constructed: {0, fault_values_raw, _length3},
constructed: {1, mode_raw, _length4}
] ->
with {:ok, fault_values} <-
Enum.reduce_while(fault_values_raw, {:ok, []}, fn pack, {:ok, acc} ->
case ApplicationTags.unfold_to_type(:enumerated, pack) do
{:ok, {:enumerated, value}} ->
with {:ok, value_c} <-
Constants.by_value_with_reason(
:property_state,
value,
{:unknown_property_state, value}
) do
{:cont, {:ok, [value_c | acc]}}
end
term ->
{:halt, term}
end
end),
{:ok, mode} <- DeviceObjectPropertyRef.parse(mode_raw) do
fault = %FaultLifeSafety{
mode: mode,
fault_values: Enum.reverse(fault_values)
}
{:ok, fault}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_fault_values}
end
end
# 4 = Fault State
def parse({:constructed, {4, fault_values_tags, 0}}) do
case fault_values_tags do
[
constructed: {0, seq_propstates, _length2}
] ->
with {:ok, fault_values} <-
Enum.reduce_while(seq_propstates, {:ok, []}, fn
term, acc ->
case BACnet.Protocol.PropertyState.parse(List.wrap(term)) do
{:ok, {state, _rest}} -> {:ok, [state | acc]}
_term -> {:halt, {:error, :invalid_fault_values_parameter}}
end
end) do
fault = %FaultState{
fault_values: Enum.reverse(fault_values)
}
{:ok, fault}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_fault_values}
end
end
# 5 = Fault Status Flags
def parse({:constructed, {5, fault_values_tags, 0}}) do
case fault_values_tags do
[
constructed: {0, status_flags_ref_raw, _length2}
] ->
with {:ok, status_flags_ref} <- DeviceObjectPropertyRef.parse(status_flags_ref_raw) do
fault = %FaultStatusFlags{
status_flags: status_flags_ref
}
{:ok, fault}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_fault_values}
end
end
def parse(_fault_values_tag) do
{:error, :invalid_tag}
end
@doc """
Validates whether the given fault parameter is in form valid.
It only validates the struct is valid as per type specification.
"""
@spec valid?(fault_parameter()) :: boolean()
def valid?(t)
for module <- [
__MODULE__.FaultCharacterString,
__MODULE__.FaultExtended,
__MODULE__.FaultLifeSafety,
__MODULE__.FaultState,
__MODULE__.FaultStatusFlags,
__MODULE__.None
] do
var = Macro.var(:t, __MODULE__)
def valid?(%unquote(module){} = unquote(var)) do
unquote(BACnet.Internal.generate_valid_clause(module, __ENV__))
end
end
end