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

lib/bacnet/protocol/event_transition_bits.ex

defmodule BACnet.Protocol.EventTransitionBits do
  @moduledoc """
  Event Transition Bits is a three-bit bit string used throughout the BACnet event
  and alarm model. Each bit corresponds to one of the three possible state
  transitions an object can make:

  | Bit | Transition     | Meaning (when TRUE)                       |
  |-----|----------------|:------------------------------------------|
  | 0   | TO_OFFNORMAL   | Transition into an off-normal event state |
  | 1   | TO_FAULT       | Transition into a fault event state       |
  | 2   | TO_NORMAL      | Transition back to the normal state       |

  **Primary uses**:
  - `Event_Enable` property - which transitions should generate notifications
  - `Acked_Transitions` - which transitions still require operator acknowledgment
  - Various alarm/event summary services and notification parameters

  The bit positions and semantics are identical for intrinsic reporting,
  algorithmic reporting, and all the summary/notification services.

  ### BACnet Specification References

  - **ASN.1** (Clause 21):
    `BACnetEventTransitionBits ::= BIT STRING { to-offnormal (0), to-fault (1), to-normal (2) }`
  - **Object properties** (Clause 12): `event_enable`, `acked_transitions`,
    `event_time_stamps`, etc.
  - **Services**: GetAlarmSummary, GetEventInformation, Confirmed/UnconfirmedEventNotification.

  ### Examples (Doc Test)

  ```elixir
  iex> bits = %EventTransitionBits{to_offnormal: true, to_fault: true, to_normal: false}
  iex> bits.to_offnormal
  true
  ```

  ### See Also
  - `BACnet.Protocol.NotificationParameters`
  - `BACnet.Protocol.StatusFlags`
  """

  # TODO: Throw argument error in encode if not valid

  alias BACnet.Protocol.ApplicationTags

  @typedoc """
  Represents a bit string indicating which event state transitions are of interest
  (to-offnormal, to-fault, to-normal).
  """
  @type t :: %__MODULE__{
          to_offnormal: boolean(),
          to_fault: boolean(),
          to_normal: boolean()
        }

  @fields [:to_offnormal, :to_fault, :to_normal]
  @enforce_keys @fields
  defstruct @fields

  @doc """
  Encodes a BACnet event transition bits into application tags encoding.
  """
  @spec encode(t(), Keyword.t()) :: {:ok, ApplicationTags.encoding_list()} | {:error, term()}
  def encode(%__MODULE__{} = flags, _opts \\ []) do
    {:ok, [to_bitstring(flags)]}
  end

  @doc """
  Creates from an application tag bitstring an event transition bits.
  """
  @spec from_bitstring(tuple()) :: t()
  def from_bitstring(bitstring) when is_tuple(bitstring) and tuple_size(bitstring) == 3 do
    %__MODULE__{
      to_offnormal: elem(bitstring, 0),
      to_fault: elem(bitstring, 1),
      to_normal: elem(bitstring, 2)
    }
  end

  @doc """
  Parses a BACnet event transition bits from 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
      [{:bitstring, {_b1, _b2, _b3} = bs} | rest] -> {:ok, {from_bitstring(bs), rest}}
      _else -> {:error, :invalid_tags}
    end
  end

  @doc """
  Creates an application tag bitstring from an event transition bits.
  """
  @spec to_bitstring(t()) :: BACnet.Protocol.ApplicationTags.primitive_encoding()
  def to_bitstring(%__MODULE__{} = t) do
    {:bitstring, {t.to_offnormal, t.to_fault, t.to_normal}}
  end

  @doc """
  Validates whether the given event transition bits is in form valid.

  It only validates the struct is valid as per type specification.
  """
  @spec valid?(t()) :: boolean()
  def valid?(
        %__MODULE__{
          to_offnormal: to_offnormal,
          to_fault: to_fault,
          to_normal: to_normal
        } = _t
      )
      when is_boolean(to_offnormal) and is_boolean(to_fault) and is_boolean(to_normal),
      do: true

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