BACnet.Protocol.BACnetArray (bacstack v0.1.0-dev.1)

Copy Markdown View Source

A BACnet Array (BACnetARRAY in the standard) is an ordered, indexed collection of elements of the same datatype. It is one of the most important structured datatypes in BACnet and appears as the type of many standard object properties.

Key BACnet characteristics (per ASHRAE 135):

  • Elements are numbered starting at 1 (not 0).
  • Index 0 is special: reading index 0 returns the current size of the array (as an Unsigned). Index 0 is never writable via the normal array access rules.
  • Arrays may be fixed-size (e.g. Priority_Array is always exactly 16 entries) or variable-size (most log buffers, lists of recipients, etc.).
  • Fixed-size arrays are declared with a maximum size; attempts to grow them beyond that size are errors. Variable-size arrays can grow and shrink (subject to device limits).

In the protocol, a BACnetARRAY is usually realized as a SEQUENCE OF with an accompanying Unsigned size pseudo-element at index 0. This design allows a client to discover the length of a potentially large collection without reading every element.

Common Usage Examples

  • priority_array (fixed 16) on commandable objects (Analog Output, Binary Output, etc.)
  • date_list on Calendar objects (variable)
  • log_buffer on Trend Log objects (variable, often very large)
  • recipient_list, notification_class, many event and schedule properties
  • object_list on the Device object (variable)

Implementation in this Library

This module wraps Erlang's :array module to provide a faithful BACnet array abstraction with the following guarantees:

  • 1-based public indexing (index 0 returns the size, as specified by ASHRAE 135).
  • fixed_size field controls whether the array may grow or shrink.
  • truncate/1 (not set_item(0, ...)) is the supported way to clear a variable array.
  • Default values are used for "unwritten" slots in fixed-size arrays and for holes in sparse variable arrays.

Examples

iex> arr = BACnetArray.from_list(["first", "second", "third"], false)
iex> BACnetArray.get_item(arr, 0)
{:ok, 3}

Edge cases

Holes in variable arrays return the default value (or :error for :undefined):

iex> sparse = BACnetArray.from_list(["a", :undefined, "c"], false)
iex> BACnetArray.get_item(sparse, 2)
:error

Fixed-size array cannot grow:

iex> fixed = BACnetArray.new(2, nil)
iex> {:error, :array_full} = BACnetArray.set_item(fixed, nil, "too much")

Index 0 always reports current size (even for fixed arrays):

iex> fixed = BACnetArray.new(5, :empty)
iex> BACnetArray.get_item(fixed, 0)
{:ok, 5}

Summary

Types

Implementation detail and thus private API. Changes to it do not count towards Semantic Versioning.

t()

Base type for the BACnet array.

Base type with subtype for the BACnet array.

Representative type for the BACnet array.

Functions

Fetch an item from the array.

Check whether the BACnet array has a fixed size.

Create a new BACnet array from the given index list.

Create a new BACnet array from the given list.

Get the default value for the BACnet array.

Get the item from the specified position.

Creates a new array. When specifying a fixed size, the array can not grow or shrink.

Reduce the array items to an accumulator. See Enum.reduce_while/3.

Remove an item from the array. This function ignores positions greater than its capacity.

Inserts an item at the specified position into the array.

Get the size of the array.

Get all items as a list.

Truncates the array to size zero.

Validates whether the given BACnet array is in form valid. A type can be given to be verified, so that each entry is either the default value or of that type (see BACnet.BeamTypes.check_type/2).

Types

items(subtype)

@opaque items(subtype)

Implementation detail and thus private API. Changes to it do not count towards Semantic Versioning.

t()

@type t() :: t(term())

Base type for the BACnet array.

t(subtype)

@type t(subtype) :: t(subtype, nil)

Base type with subtype for the BACnet array.

t(subtype, fixed_size)

@type t(subtype, fixed_size) :: %BACnet.Protocol.BACnetArray{
  fixed_size: fixed_size,
  items: items(subtype),
  size: non_neg_integer()
}

Representative type for the BACnet array.

  • fixed_size: nil for variable-length arrays, or a positive integer for fixed-size arrays (e.g. 16 for Priority_Array).
  • size: current number of elements (for fixed-size arrays this is always equal to fixed_size).
  • Indexing is 1-based for real elements; index 0 is the special "array length" pseudo-element defined by BACnet.

Functions

fetch(array, position)

@spec fetch(t(subtype), non_neg_integer()) :: {:ok, subtype} | :error
when subtype: var

Fetch an item from the array.

This is implemented for the Access module.

fixed_size?(array)

@spec fixed_size?(t()) :: boolean()

Check whether the BACnet array has a fixed size.

from_indexed_list(collection, fixed_size \\ false, default_value \\ :undefined)

@spec from_indexed_list(Enumerable.t(subtype), boolean(), default) ::
  t(subtype | default) | no_return()
when default: term()

Create a new BACnet array from the given index list.

The indexed list is a list of {index, item}, where index is a positive integer. The indexes do not need to be consecutively or sequentially ordered. Note however that interleaved values leave the default value at the "holes", which you will get upon calling get_item/2. See also get_item/2.

The list will be iterated once to insert them into the array. Optionally the resulting array can have a fixed size (derived from the list length).

from_list(collection, fixed_size \\ false, default_value \\ :undefined)

@spec from_list(Enumerable.t(subtype), boolean(), default) ::
  t(subtype | default) | no_return()
when default: var

Create a new BACnet array from the given list.

Optionally the resulting array can have a fixed size (derived from the list length).

get_default(array)

@spec get_default(t()) :: term()

Get the default value for the BACnet array.

get_item(array, position)

@spec get_item(t(subtype), non_neg_integer()) :: {:ok, subtype} | :error
when subtype: var

Get the item from the specified position.

Arrays with interleaved values will typically use the default value, as such when getting interleave positions, you will get the default value. However :undefined is handled special and will return :error instead.

Position 0 conveniently returns the size (as specified by ASHRAE 135).

new(fixed_size \\ nil, default_value \\ :undefined)

@spec new(non_neg_integer() | nil, term()) :: t()

Creates a new array. When specifying a fixed size, the array can not grow or shrink.

There's no distinction between an unset value (an empty position) or an explicitely set value to the default value.

reduce_while(array, accumulator, callback)

@spec reduce_while(t(subtype), term(), (item :: subtype, accumulator :: term() ->
                                    {:cont, term()} | {:halt, term()})) ::
  term()

Reduce the array items to an accumulator. See Enum.reduce_while/3.

remove_item(array, position)

@spec remove_item(t(subtype), non_neg_integer()) ::
  {:ok, t(subtype)} | {:error, term()}

Remove an item from the array. This function ignores positions greater than its capacity.

Non-fixed size arrays get resized. Fixed size arrays will have the position reset to the default value.

set_item(array, position, item)

@spec set_item(t(subtype), non_neg_integer() | nil, subtype) ::
  {:ok, t(subtype)} | {:error, term()}
when subtype: var

Inserts an item at the specified position into the array.

Position nil can be used to append to the end of the array. Positions greater than the size of the array + 1 can not be used.

size(array)

@spec size(t()) :: non_neg_integer()

Get the size of the array.

to_list(array)

@spec to_list(t(subtype)) :: [subtype] when subtype: var

Get all items as a list.

truncate(array)

@spec truncate(t(subtype)) :: t(subtype)

Truncates the array to size zero.

valid?(t, type \\ :any)

@spec valid?(t(), BACnet.BeamTypes.typechecker_types()) :: boolean()

Validates whether the given BACnet array is in form valid. A type can be given to be verified, so that each entry is either the default value or of that type (see BACnet.BeamTypes.check_type/2).

If none or :any is given, no particular validation occurs.