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

lib/bacnet/protocol/alarm_summary.ex

defmodule BACnet.Protocol.AlarmSummary do
  @moduledoc """
  An Alarm Summary entry is the data structure returned by the Get Alarm Summary
  confirmed service. It gives a client a compact, point-in-time list of every object
  inside a device that is currently in an alarm or fault condition and that the client
  is permitted to observe.

  Each summary contains three pieces of information: the Object Identifier of the
  alarming object, its current Event State (which may be FAULT or one of the alarm
  states such as HIGH_LIMIT or LIFE_SAFETY_ALARM), and the Acknowledged Transitions
  bit string. The acknowledged transitions bits are crucial for operator interfaces
  because they indicate which state changes still require human acknowledgment before
  the alarm can be considered fully cleared from the operator's perspective.

  Although Get Alarm Summary is one of the older summary services in BACnet, it is
  still widely supported for backward compatibility. Newer implementations are
  encouraged to use the more capable Get Event Information service, which returns
  timestamps, notification class, and other context. The Alarm Summary structure
  itself remains useful whenever a very small encoding is required.
  """

  # TODO: Throw argument error in encode if not valid

  alias BACnet.Protocol.ApplicationTags
  alias BACnet.Protocol.Constants
  alias BACnet.Protocol.EventTransitionBits
  alias BACnet.Protocol.ObjectIdentifier

  @typedoc """
  Represents a single entry in the response of a Get Alarm Summary service.
  """
  @type t :: %__MODULE__{
          object_identifier: ObjectIdentifier.t(),
          alarm_state: Constants.event_state(),
          acknowledged_transitions: EventTransitionBits.t()
        }

  @fields [:object_identifier, :alarm_state, :acknowledged_transitions]
  @enforce_keys @fields
  defstruct @fields

  @doc """
  Encodes a BACnet alarm summary into BACnet application tags encoding.
  """
  @spec encode(t(), Keyword.t()) ::
          {:ok, ApplicationTags.encoding_list()} | {:error, term()}
  def encode(
        %__MODULE__{
          object_identifier: %ObjectIdentifier{},
          acknowledged_transitions: %EventTransitionBits{}
        } = summary,
        _opts \\ []
      ) do
    with {:ok, state} <-
           Constants.by_name_with_reason(
             :event_state,
             summary.alarm_state,
             {:unknown_state, summary.alarm_state}
           ) do
      params = [
        {:object_identifier, summary.object_identifier},
        {:enumerated, state},
        EventTransitionBits.to_bitstring(summary.acknowledged_transitions)
      ]

      {:ok, params}
    end
  end

  @doc """
  Parses a BACnet alarm 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_state},
        {:bitstring, {offnormal, fault, normal}}
        | rest
      ] ->
        with {:ok, alarm_state} <-
               Constants.by_value_with_reason(
                 :event_state,
                 event_state,
                 {:unknown_state, event_state}
               ) do
          summary = %__MODULE__{
            object_identifier: obj,
            alarm_state: alarm_state,
            acknowledged_transitions: %EventTransitionBits{
              to_offnormal: offnormal,
              to_fault: fault,
              to_normal: normal
            }
          }

          {:ok, {summary, rest}}
        end

      _else ->
        {:error, :invalid_tags}
    end
  end

  @doc """
  Validates whether the given alarm 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,
          alarm_state: state,
          acknowledged_transitions: %EventTransitionBits{} = bits
        } = _t
      ) do
    ObjectIdentifier.valid?(obj_ref) and Constants.has_by_name(:event_state, state) and
      EventTransitionBits.valid?(bits)
  end

  def valid?(%__MODULE__{} = _t), do: false
end