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

lib/bacnet/protocol/limit_enable.ex

defmodule BACnet.Protocol.LimitEnable do
  @moduledoc """
  BACnet Limit Enable is a two-bit bit string that controls which of the intrinsic
  or algorithmic limit-detection mechanisms are active for an object (primarily
  Analog Input/Value, Large Analog Value, etc.).

  | Bit | Name               | When TRUE the corresponding limit algorithm runs        |
  |-----|--------------------|:--------------------------------------------------------|
  | 0   | LOW_LIMIT_ENABLE   | Low-limit (low-limit + deadband) detection is enabled   |
  | 1   | HIGH_LIMIT_ENABLE  | High-limit (high-limit + deadband) detection is enabled |

  The flags appear as the `limit_enable` property and directly affect whether
  `OUT_OF_RANGE` or `OUT_OF_RANGE` / `DOUBLE_OUT_OF_RANGE` events can be generated.

  ### BACnet Specification References

  - **ASN.1** (Clause 21): `BACnetLimitEnable ::= BIT STRING { low-limit-enable (0), high-limit-enable (1) }`
  - **Object types**: Used by Analog Input (12.2), Analog Value (12.4), Large Analog
    Value (12.39), and several other numeric objects that support intrinsic alarming.

  ### Examples (Doc Test)

  ```elixir
  iex> enable = %LimitEnable{low_limit_enable: true, high_limit_enable: true}
  iex> enable.low_limit_enable
  true
  ```

  ### See Also
  - `BACnet.Protocol.Constants`
  - `BACnet.Protocol.EventParameters`
  """

  alias BACnet.Protocol.ApplicationTags

  # TODO: Throw argument error in encode if not valid

  @typedoc """
  Two Boolean flags controlling low- and high-limit detection (see module docs).
  """
  @type t :: %__MODULE__{
          low_limit_enable: boolean(),
          high_limit_enable: boolean()
        }

  @fields [
    :low_limit_enable,
    :high_limit_enable
  ]
  @enforce_keys @fields
  defstruct @fields

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

  @doc """
  Parses a BACnet limit enable 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} = bs} | rest] -> {:ok, {from_bitstring(bs), rest}}
      _else -> {:error, :invalid_tags}
    end
  end

  @doc """
  Creates an application tag bitstring from a limit enable.
  """
  @spec to_bitstring(t()) :: BACnet.Protocol.ApplicationTags.primitive_encoding()
  def to_bitstring(%__MODULE__{} = t) do
    {:bitstring, {t.low_limit_enable, t.high_limit_enable}}
  end

  @doc """
  Validates whether the given limit enable is in form valid.

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

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