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 enrollment_summary.ex
Raw

lib/bacnet/protocol/enrollment_summary.ex

defmodule BACnet.Protocol.EnrollmentSummary do
# TODO: Docs
# TODO: Throw argument error in encode if not valid
alias BACnet.Protocol.ApplicationTags
alias BACnet.Protocol.Constants
alias BACnet.Protocol.ObjectIdentifier
require Constants
@type t :: %__MODULE__{
object_identifier: ObjectIdentifier.t(),
event_type: Constants.event_type(),
event_state: Constants.event_state(),
priority: byte(),
notification_class: non_neg_integer() | nil
}
@fields [
:object_identifier,
:event_type,
:event_state,
:priority,
:notification_class
]
@enforce_keys @fields
defstruct @fields
@doc """
Encodes a BACnet enrollment summary into BACnet application tags encoding.
"""
@spec encode(t(), Keyword.t()) ::
{:ok, ApplicationTags.encoding_list()} | {:error, term()}
def encode(
%__MODULE__{} = summary,
_opts \\ []
) do
with :ok <-
if(ApplicationTags.valid_int?(summary.priority, 8),
do: :ok,
else: {:error, :invalid_priority_value}
),
{:ok, event_type} <-
Constants.by_name_with_reason(
:event_type,
summary.event_type,
{:unknown_type, summary.event_type}
),
{:ok, event_state} <-
Constants.by_name_with_reason(
:event_state,
summary.event_state,
{:unknown_state, summary.event_state}
) do
tail =
if summary.notification_class do
[{:unsigned_integer, summary.notification_class}]
else
[]
end
params = [
{:object_identifier, summary.object_identifier},
{:enumerated, event_type},
{:enumerated, event_state},
{:unsigned_integer, summary.priority}
| tail
]
{:ok, params}
end
end
@doc """
Parses a BACnet enrollment summary from BACnet application tags encoding.
"""
@spec parse(ApplicationTags.encoding_list()) ::
{:ok, {t(), rest :: ApplicationTags.encoding_list()}}
| {:error, term()}
def parse(tags) when is_list(tags) do
case tags do
[
{:object_identifier, obj},
{:enumerated, event_type},
{:enumerated, event_state},
{:unsigned_integer, priority}
# Parse optional notification class in function body
| rest
] ->
with :ok <-
if(ApplicationTags.valid_int?(priority, 8),
do: :ok,
else: {:error, :invalid_priority_value}
),
{:ok, event_type_c} <-
Constants.by_value_with_reason(
:event_type,
event_type,
{:unknown_type, event_type}
),
{:ok, event_state_c} <-
Constants.by_value_with_reason(
:event_state,
event_state,
{:unknown_state, event_state}
) do
{notif_class, rest} =
case rest do
[{:unsigned_integer, notif_class} | rest] -> {notif_class, rest}
_term -> {nil, rest}
end
summary = %__MODULE__{
object_identifier: obj,
event_type: event_type_c,
event_state: event_state_c,
priority: priority,
notification_class: notif_class
}
{:ok, {summary, rest}}
end
_else ->
{:error, :invalid_tags}
end
end
@doc """
Validates whether the given enrollment summary is in form valid.
It only validates the struct is valid as per type specification.
"""
@spec valid?(t()) :: boolean()
def valid?(
%__MODULE__{
object_identifier: %ObjectIdentifier{} = obj_ref,
event_type: type,
event_state: state,
priority: priority,
notification_class: class
} = _t
)
when is_integer(priority) and priority >= 0 and priority <= 255 and
(is_nil(class) or (is_integer(class) and class >= 0 and class <= 4_294_967_295)) do
ObjectIdentifier.valid?(obj_ref) and Constants.has_by_name(:event_type, type) and
Constants.has_by_name(:event_state, state)
end
def valid?(%__MODULE__{} = _t), do: false
end