Current section
Files
Jump to
Current section
Files
lib/bacnet/protocol/status_flags.ex
defmodule BACnet.Protocol.StatusFlags do
@moduledoc """
BACnet Status Flags is a four-bit bit string (application tag 8) that provides a
compact summary of an object's health and operating mode. It is the `status_flags`
property on virtually every standard object type and appears in almost every
event-related structure.
Bit ordering (MSB first, per Clause 20.2.10):
| Bit | Name | Meaning when TRUE |
|-----|-----------------|:--------------------------------------------------------|
| 0 | IN_ALARM | `event_state` != NORMAL |
| 1 | FAULT | `reliability` indicates a fault |
| 2 | OVERRIDDEN | Local override (operator interface, physical switch, …) |
| 3 | OUT_OF_SERVICE | `out_of_service` property is TRUE |
The meanings are deliberately consistent across object types so that generic
workstations can interpret the flags without knowing the concrete object type.
### BACnet Specification References
- **Encoding** (20.2.10): Primitive bit string. First contents octet = number of
unused bits (0 for a 4-bit value). Bits are placed with the first defined
Boolean in bit 7 of the first subsequent octet.
- **ASN.1** (Clause 21): `BACnetStatusFlags ::= BIT STRING { in-alarm (0), fault (1), overridden (2), out-of-service (3) }`
- **Mandatory property**: Every object type defined in Clause 12 that has an
`event_state` or `reliability` property also has a `status_flags` property
whose value is a `BACnetStatusFlags`.
- **Event usage**: Carried inside `BACnetNotificationParameters` (Change-of-State,
Change-of-Reliability, …) and in GetAlarmSummary / GetEventInformation ACKs.
This module stores the four Booleans in a struct for ergonomic access while the
wire form is produced by the `to_bitstring/1` helper used during encoding.
### Examples (Doc Test)
#### Creating flags
```elixir
iex> flags = %StatusFlags{in_alarm: false, fault: false, overridden: true, out_of_service: false}
iex> flags.overridden
true
```
#### Round-tripping through encoding
```elixir
iex> flags = %StatusFlags{in_alarm: true, fault: false, overridden: false, out_of_service: false}
iex> {:ok, [encoded]} = StatusFlags.encode(flags)
iex> StatusFlags.from_bitstring(elem(encoded, 1))
%StatusFlags{in_alarm: true, fault: false, overridden: false, out_of_service: false}
```
### See Also
- `BACnet.Protocol.Constants` (for related `reliability` and `event_state` values)
- `BACnet.Protocol.EventTransitionBits`
- `BACnet.Protocol.NotificationParameters`
"""
# TODO: Throw argument error in encode if not valid
alias BACnet.Protocol.ApplicationTags
@typedoc """
Represents the four Boolean flags of a BACnet Status Flags bit string
(see table in the module documentation for bit positions and semantics).
"""
@type t :: %__MODULE__{
in_alarm: boolean(),
fault: boolean(),
overridden: boolean(),
out_of_service: boolean()
}
@fields [
:in_alarm,
:fault,
:overridden,
:out_of_service
]
@enforce_keys @fields
defstruct @fields
@doc """
Encodes a BACnet status 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 status flag.
"""
@spec from_bitstring(tuple()) :: t()
def from_bitstring(bitstring) when is_tuple(bitstring) and tuple_size(bitstring) == 4 do
%__MODULE__{
in_alarm: elem(bitstring, 0),
fault: elem(bitstring, 1),
overridden: elem(bitstring, 2),
out_of_service: elem(bitstring, 3)
}
end
@doc """
Parses a BACnet status 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, _b4} = bs} | rest] -> {:ok, {from_bitstring(bs), rest}}
_else -> {:error, :invalid_tags}
end
end
@doc """
Creates an application tag bitstring from a status flag.
"""
@spec to_bitstring(t()) :: BACnet.Protocol.ApplicationTags.primitive_encoding()
def to_bitstring(%__MODULE__{} = t) do
{:bitstring, {t.in_alarm, t.fault, t.overridden, t.out_of_service}}
end
@doc """
Validates whether the given status flags is in form valid.
It only validates the struct is valid as per type specification.
"""
@spec valid?(t()) :: boolean()
def valid?(
%__MODULE__{
in_alarm: alarm,
fault: fault,
overridden: overridden,
out_of_service: out
} = _t
)
when is_boolean(alarm) and is_boolean(fault) and is_boolean(overridden) and is_boolean(out),
do: true
def valid?(%__MODULE__{} = _t), do: false
end