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

lib/bacnet/protocol/result_flags.ex

defmodule BACnet.Protocol.ResultFlags do
  @moduledoc """
  BACnet Result Flags is a three-bit bit string returned by range-reading services
  (ReadRange, GetEventInformation, etc.) to describe the window of data that was
  actually returned.

  Bit meanings (MSB first):

  | Bit | Name       | Meaning when TRUE                                                |
  |-----|------------|:-----------------------------------------------------------------|
  | 0   | FIRST_ITEM | Response contains the first (oldest) item of the requested range |
  | 1   | LAST_ITEM  | Response contains the last (newest) item of the requested range  |
  | 2   | MORE_ITEMS | More items matched but could not fit in this PDU / segment       |

  The flags allow a client to know whether it has the beginning, the end, or is
  looking at a slice in the middle of a potentially large collection (trend-log
  buffer, alarm summary, event log, etc.).

  ### BACnet Specification References

  - **ASN.1** (Clause 21): `BACnetResultFlags ::= BIT STRING { first-item (0), last-item (1), more-items (2) }`
  - **Primary services**: ReadRange-ACK (15.8), GetEventInformation-ACK (13.11),
    and several alarm/event summary acknowledgments.

  ### Examples (Doc Test)

  ```elixir
  iex> flags = %ResultFlags{first_item: true, last_item: false, more_items: true}
  iex> flags.more_items
  true
  ```

  ### See Also
  - `BACnet.Protocol.ReadRange`
  """

  alias BACnet.Protocol.ApplicationTags

  # TODO: Throw argument error in encode if not valid

  @typedoc """
  Three Boolean flags describing a partial result set (see module docs for bit positions).
  """
  @type t :: %__MODULE__{
          first_item: boolean(),
          last_item: boolean(),
          more_items: boolean()
        }

  @fields [
    :first_item,
    :last_item,
    :more_items
  ]
  @enforce_keys @fields
  defstruct @fields

  @doc """
  Encodes a BACnet result flags 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 a result flags.
  """
  @spec from_bitstring(tuple()) :: t()
  def from_bitstring(bitstring) when is_tuple(bitstring) and tuple_size(bitstring) == 3 do
    %__MODULE__{
      first_item: elem(bitstring, 0),
      last_item: elem(bitstring, 1),
      more_items: elem(bitstring, 2)
    }
  end

  @doc """
  Parses a BACnet result flags 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 a result flags.
  """
  @spec to_bitstring(t()) :: BACnet.Protocol.ApplicationTags.primitive_encoding()
  def to_bitstring(%__MODULE__{} = t) do
    {:bitstring, {t.first_item, t.last_item, t.more_items}}
  end

  @doc """
  Validates whether the given result flags is in form valid.

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

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