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