Current section
Files
Jump to
Current section
Files
lib/bacnet/protocol/event_information.ex
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