defmodule BACnet.Protocol.EventInformation do @moduledoc """ Event Information is the rich data structure returned by the Get Event Information service (ASHRAE 135 Clause 13.12). It is the modern replacement for the older Get Alarm Summary service and is the recommended way for a client to obtain a complete picture of every object in a device that is capable of generating events. Each entry contains the object identifier, its current event state, the acknowledged transition bits, the timestamps of the three state transitions, the notify type, the event enable bits, and the three priority values that will be used for notifications at each transition. This level of detail allows a sophisticated operator workstation to present accurate alarm lists, compute alarm durations, and correctly route or escalate notifications. """ # TODO: Throw argument error in encode if not valid alias BACnet.Protocol.ApplicationTags alias BACnet.Protocol.Constants alias BACnet.Protocol.EventTimestamps alias BACnet.Protocol.EventTransitionBits alias BACnet.Protocol.ObjectIdentifier import BACnet.Protocol.Utility, only: [pattern_extract_tags: 4] @typedoc """ Represents a single entry in the response of a Get Event Information service. """ @type t :: %__MODULE__{ object_identifier: ObjectIdentifier.t(), event_state: Constants.event_state(), acknowledged_transitions: EventTransitionBits.t(), event_timestamps: EventTimestamps.t(), notify_type: Constants.notify_type(), event_enable: EventTransitionBits.t(), event_priorities: {to_offnormal :: byte(), to_fault :: byte(), to_normal :: byte()} } @fields [ :object_identifier, :event_state, :acknowledged_transitions, :event_timestamps, :notify_type, :event_enable, :event_priorities ] @enforce_keys @fields defstruct @fields @doc """ Encodes a BACnet event information into BACnet application tags encoding. """ @spec encode(t(), Keyword.t()) :: {:ok, ApplicationTags.encoding_list()} | {:error, term()} def encode(%__MODULE__{event_priorities: {ev_prio1, ev_prio2, ev_prio3}} = event, _opts \\ []) do with {:ok, obj, _header} <- ApplicationTags.encode_value({:object_identifier, event.object_identifier}), {:ok, event_state_c} <- Constants.by_name_with_reason( :event_state, event.event_state, {:unknown_state, event.event_state} ), {:ok, event_state, _header} <- ApplicationTags.encode_value({:enumerated, event_state_c}), {:ok, event_transbits, _header} <- ApplicationTags.encode_value( EventTransitionBits.to_bitstring(event.acknowledged_transitions) ), {:ok, timestamps} <- EventTimestamps.encode(event.event_timestamps), {:ok, notify_type_c} <- Constants.by_name_with_reason( :notify_type, event.notify_type, {:unknown_notify_type, event.notify_type} ), {:ok, notify_type, _header} <- ApplicationTags.encode_value({:enumerated, notify_type_c}), {:ok, event_enable, _header} <- ApplicationTags.encode_value(EventTransitionBits.to_bitstring(event.event_enable)) do params = [ {:tagged, {0, obj, byte_size(obj)}}, {:tagged, {1, event_state, byte_size(event_state)}}, {:tagged, {2, event_transbits, byte_size(event_transbits)}}, {:constructed, {3, timestamps, 0}}, {:tagged, {4, notify_type, byte_size(notify_type)}}, {:tagged, {5, event_enable, byte_size(event_enable)}}, {:constructed, {6, [ {:unsigned_integer, ev_prio1}, {:unsigned_integer, ev_prio2}, {:unsigned_integer, ev_prio3} ], 0}} ] {:ok, params} end end @doc """ Parses a BACnet event information 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 with {:ok, obj, rest} <- pattern_extract_tags(tags, {:tagged, {0, _c, _l}}, :object_identifier, false), {:ok, event_state, rest} <- pattern_extract_tags(rest, {:tagged, {1, _c, _l}}, :enumerated, false), {:ok, event_state_c} <- Constants.by_value_with_reason( :event_state, event_state, {:unknown_state, event_state} ), {:ok, {offnormal, fault, normal}, rest} <- pattern_extract_tags(rest, {:tagged, {2, _c, _l}}, :bitstring, false), {:ok, {:constructed, {3, event_timestamps_raw, _len}}, rest} <- pattern_extract_tags(rest, {:constructed, {3, _c, _l}}, nil, false), {:ok, {event_timestamps, _rest}} <- EventTimestamps.parse(event_timestamps_raw), {:ok, notify_type, rest} <- pattern_extract_tags(rest, {:tagged, {4, _c, _l}}, :enumerated, false), {:ok, notify_type_c} <- Constants.by_value_with_reason( :notify_type, notify_type, {:unknown_notify_type, notify_type} ), {:ok, {ev_offnormal, ev_fault, ev_normal}, rest} <- pattern_extract_tags(rest, {:tagged, {5, _c, _l}}, :bitstring, false), {:ok, {:constructed, {6, [ unsigned_integer: prio1, unsigned_integer: prio2, unsigned_integer: prio3 ], _len}}, rest} <- pattern_extract_tags(rest, {:constructed, {6, _c, _l}}, nil, false) do info = %__MODULE__{ object_identifier: obj, event_state: event_state_c, acknowledged_transitions: %EventTransitionBits{ to_offnormal: offnormal, to_fault: fault, to_normal: normal }, event_timestamps: event_timestamps, notify_type: notify_type_c, event_enable: %EventTransitionBits{ to_offnormal: ev_offnormal, to_fault: ev_fault, to_normal: ev_normal }, event_priorities: {prio1, prio2, prio3} } {:ok, {info, rest}} else {:ok, _term, _rest} -> {:error, :invalid_tags} {:error, :missing_pattern} -> {:error, :invalid_tags} {:error, _err} = err -> err end end @doc """ Validates whether the given event information 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_state: state, acknowledged_transitions: %EventTransitionBits{} = ack_trans, event_timestamps: %EventTimestamps{} = event, notify_type: type, event_enable: %EventTransitionBits{} = enable, event_priorities: {to_offnormal, to_fault, to_normal} } = _t ) when is_integer(to_offnormal) and to_offnormal >= 0 and to_offnormal <= 255 and is_integer(to_fault) and to_fault >= 0 and to_fault <= 255 and is_integer(to_normal) and to_normal >= 0 and to_normal <= 255 do ObjectIdentifier.valid?(obj_ref) and Constants.has_by_name(:event_state, state) and EventTransitionBits.valid?(ack_trans) and EventTimestamps.valid?(event) and Constants.has_by_name(:notify_type, type) and EventTransitionBits.valid?(enable) end def valid?(%__MODULE__{} = _t), do: false end