Current section
Files
Jump to
Current section
Files
lib/bacnet/protocol/enrollment_summary.ex
defmodule BACnet.Protocol.EnrollmentSummary do
@moduledoc """
An Enrollment Summary entry is produced by the Get Enrollment Summary service.
It lists the objects inside a device that have been configured to generate event
notifications (intrinsic or algorithmic) to a particular notification class or
recipient, filtered by the parameters supplied in the request.
Each entry carries the object identifier, the event type that the object is
configured to generate, the current event state, the priority that will be used
in notifications, and optionally the notification class. This information is
primarily useful for management stations that need to discover which objects
will send alarms to a given operator or system, or to audit the event
subscription configuration of a large site.
The service supports several different "acknowledgment filter" modes so that a
client can ask for only unacknowledged alarms, only acknowledged alarms, or all
enrollments regardless of acknowledgment state.
"""
# TODO: Throw argument error in encode if not valid
alias BACnet.Protocol.ApplicationTags
alias BACnet.Protocol.Constants
alias BACnet.Protocol.ObjectIdentifier
@typedoc """
Represents a single entry returned by the Get Enrollment Summary service.
"""
@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