Current section
Files
Jump to
Current section
Files
lib/bacnet/protocol/event_parameters.ex
defmodule BACnet.Protocol.EventParameters do
@moduledoc """
BACnet event parameters describe the specific configuration and thresholds
used by each event algorithm (see `BACnet.Protocol.EventAlgorithms`).
Every object that supports intrinsic or algorithmic event reporting uses one
of these parameter structures inside its `event_parameters` property. The
choice of parameter type determines what conditions will cause the object to
transition between NORMAL, OFFNORMAL, FAULT, and other event states, and what
additional data is included in the resulting event notifications.
This module provides the top-level encoding and parsing functions for all
supported event parameter variants. The individual parameter modules
(ChangeOfBitstring, OutOfRange, BufferReady, etc.) contain the actual fields
and logic for each algorithm.
Note: The `AccessEvent` algorithm and its parameters are not supported.
### Examples
Common event parameter configurations (these are CHOICE variants):
```elixir
# OutOfRange (very common for analog alarming)
iex> %EventParameters.OutOfRange{
...> time_delay: 30,
...> low_limit: 10.0,
...> high_limit: 90.0,
...> deadband: 2.0
...> }
# ChangeOfState
iex> %EventParameters.ChangeOfState{
...> time_delay: 0,
...> alarm_values: [true]
...> }
# BufferReady (for trend logs)
iex> %EventParameters.BufferReady{
...> threshold: 100,
...> previous_count: 0
...> }
```
These are placed inside the object's `event_parameters` property. Different objects support different subsets of these parameter types.
"""
# TODO: Throw argument error in encode if not valid
alias BACnet.Protocol.ApplicationTags
alias BACnet.Protocol.Constants
alias BACnet.Protocol.DeviceObjectPropertyRef
alias BACnet.Protocol.EventParameters.BufferReady
alias BACnet.Protocol.EventParameters.ChangeOfBitstring
alias BACnet.Protocol.EventParameters.ChangeOfCharacterString
alias BACnet.Protocol.EventParameters.ChangeOfLifeSafety
alias BACnet.Protocol.EventParameters.ChangeOfState
alias BACnet.Protocol.EventParameters.ChangeOfStatusFlags
alias BACnet.Protocol.EventParameters.ChangeOfValue
alias BACnet.Protocol.EventParameters.CommandFailure
alias BACnet.Protocol.EventParameters.DoubleOutOfRange
alias BACnet.Protocol.EventParameters.Extended
alias BACnet.Protocol.EventParameters.FloatingLimit
alias BACnet.Protocol.EventParameters.None
alias BACnet.Protocol.EventParameters.OutOfRange
alias BACnet.Protocol.EventParameters.SignedOutOfRange
alias BACnet.Protocol.EventParameters.UnsignedOutOfRange
alias BACnet.Protocol.EventParameters.UnsignedRange
alias BACnet.Protocol.PropertyState
alias BACnet.Protocol.StatusFlags
@typedoc """
Possible BACnet event parameters.
"""
@type event_parameter ::
ChangeOfBitstring.t()
| ChangeOfState.t()
| ChangeOfValue.t()
| CommandFailure.t()
| FloatingLimit.t()
| OutOfRange.t()
| ChangeOfLifeSafety.t()
| Extended.t()
| BufferReady.t()
| UnsignedRange.t()
| DoubleOutOfRange.t()
| SignedOutOfRange.t()
| UnsignedOutOfRange.t()
| ChangeOfCharacterString.t()
| ChangeOfStatusFlags.t()
| None.t()
@doc """
Encodes an EventParameters struct (any of the algorithm variants) into the application-tag encoding.
"""
@spec encode(event_parameter(), Keyword.t()) ::
{:ok, ApplicationTags.encoding()} | {:error, term()}
def encode(event_params, opts \\ [])
def encode(%ChangeOfBitstring{} = params, opts) do
with true <- is_integer(params.time_delay) and params.time_delay >= 0,
true <- is_tuple(params.bitmask),
true <- is_list(params.alarm_values) and Enum.all?(params.alarm_values, &is_tuple/1),
{:ok, time_delay, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.time_delay}, opts),
{:ok, bitmask, _header} <-
ApplicationTags.encode_value({:bitstring, params.bitmask}, opts),
{:ok, alarm_values} <-
Enum.reduce_while(params.alarm_values, {:ok, []}, fn bitstr, {:ok, acc} ->
{:cont, {:ok, [{:bitstring, bitstr} | acc]}}
end) do
{:ok,
{:constructed,
{0,
[
tagged: {0, time_delay, byte_size(time_delay)},
tagged: {1, bitmask, byte_size(bitmask)},
constructed: {2, Enum.reverse(alarm_values), 0}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%ChangeOfState{} = params, opts) do
with true <- is_integer(params.time_delay) and params.time_delay >= 0,
true <-
is_list(params.alarm_values) and
Enum.all?(params.alarm_values, &is_struct(&1, PropertyState)),
{:ok, time_delay, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.time_delay}, opts),
{:ok, alarm_values} <-
Enum.reduce_while(params.alarm_values, {:ok, []}, fn enum, {:ok, acc} ->
case PropertyState.encode(enum) do
{:ok, [val]} -> {:cont, {:ok, [val | acc]}}
{:error, _err} = err -> {:halt, {:error, err}}
end
end) do
{:ok,
{:constructed,
{1,
[
tagged: {0, time_delay, byte_size(time_delay)},
constructed: {1, Enum.reverse(alarm_values), 0}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%ChangeOfValue{} = params, opts) do
with true <- is_integer(params.time_delay) and params.time_delay >= 0,
true <- is_tuple(params.bitmask) or is_nil(params.bitmask),
true <- is_float(params.increment) or is_nil(params.increment),
{:ok, time_delay, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.time_delay}, opts),
{:ok, bits} <-
(if params.bitmask do
case ApplicationTags.encode_value({:bitstring, params.bitmask}) do
{:ok, bytes, _header} -> {:ok, {:tagged, {0, bytes, byte_size(bytes)}}}
term -> term
end
else
{:ok, nil}
end),
{:ok, float} <-
(if params.increment do
case ApplicationTags.encode_value({:real, params.increment}) do
{:ok, bytes, _header} -> {:ok, {:tagged, {1, bytes, byte_size(bytes)}}}
term -> term
end
else
{:ok, nil}
end) do
{:ok,
{:constructed,
{2,
[
tagged: {0, time_delay, byte_size(time_delay)},
constructed: {1, bits || float, 0}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%CommandFailure{} = params, opts) do
with true <- is_integer(params.time_delay) and params.time_delay >= 0,
true <- is_struct(params.feedback_value, DeviceObjectPropertyRef),
{:ok, time_delay, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.time_delay}, opts),
{:ok, feedback} <- DeviceObjectPropertyRef.encode(params.feedback_value) do
{:ok,
{:constructed,
{3,
[
tagged: {0, time_delay, byte_size(time_delay)},
constructed: {1, feedback, 0}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%FloatingLimit{} = params, opts) do
with true <- is_integer(params.time_delay) and params.time_delay >= 0,
true <- is_struct(params.setpoint, DeviceObjectPropertyRef),
true <- is_float(params.low_diff_limit),
true <- is_float(params.high_diff_limit),
true <- is_float(params.deadband),
{:ok, time_delay, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.time_delay}, opts),
{:ok, setpoint} <- DeviceObjectPropertyRef.encode(params.setpoint, opts),
{:ok, low_diff_limit, _header} <-
ApplicationTags.encode_value({:real, params.low_diff_limit}, opts),
{:ok, high_diff_limit, _header} <-
ApplicationTags.encode_value({:real, params.high_diff_limit}, opts),
{:ok, deadband, _header} <-
ApplicationTags.encode_value({:real, params.deadband}, opts) do
{:ok,
{:constructed,
{4,
[
tagged: {0, time_delay, byte_size(time_delay)},
constructed: {1, setpoint, 0},
tagged: {2, low_diff_limit, byte_size(low_diff_limit)},
tagged: {3, high_diff_limit, byte_size(high_diff_limit)},
tagged: {4, deadband, byte_size(deadband)}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%OutOfRange{} = params, opts) do
with true <- is_integer(params.time_delay) and params.time_delay >= 0,
true <- is_float(params.low_limit),
true <- is_float(params.high_limit),
true <- is_float(params.deadband),
{:ok, time_delay, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.time_delay}, opts),
{:ok, low_limit, _header} <-
ApplicationTags.encode_value({:real, params.low_limit}, opts),
{:ok, high_limit, _header} <-
ApplicationTags.encode_value({:real, params.high_limit}, opts),
{:ok, deadband, _header} <-
ApplicationTags.encode_value({:real, params.deadband}, opts) do
{:ok,
{:constructed,
{5,
[
tagged: {0, time_delay, byte_size(time_delay)},
tagged: {1, low_limit, byte_size(low_limit)},
tagged: {2, high_limit, byte_size(high_limit)},
tagged: {3, deadband, byte_size(deadband)}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%ChangeOfLifeSafety{} = params, opts) do
with true <- is_integer(params.time_delay) and params.time_delay >= 0,
true <- is_struct(params.mode, DeviceObjectPropertyRef),
true <- is_list(params.alarm_values) and Enum.all?(params.alarm_values, &is_atom/1),
true <-
is_list(params.life_safety_alarm_values) and
Enum.all?(params.life_safety_alarm_values, &is_atom/1),
{:ok, time_delay, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.time_delay}, opts),
{:ok, ls_alarm_values} <-
Enum.reduce_while(params.life_safety_alarm_values, {:ok, []}, fn enum, {:ok, acc} ->
case Constants.by_name(:life_safety_state, enum) do
{:ok, val} -> {:cont, {:ok, [{:enumerated, val} | acc]}}
:error -> {:halt, {:error, {:unknown_life_safety_alarm_value, enum}}}
end
end),
{:ok, alarm_values} <-
Enum.reduce_while(params.alarm_values, {:ok, []}, fn enum, {:ok, acc} ->
case Constants.by_name(:life_safety_state, enum) do
{:ok, val} -> {:cont, {:ok, [{:enumerated, val} | acc]}}
:error -> {:halt, {:error, {:unknown_alarm_value, enum}}}
end
end),
{:ok, mode} <- DeviceObjectPropertyRef.encode(params.mode, opts) do
{:ok,
{:constructed,
{8,
[
tagged: {0, time_delay, byte_size(time_delay)},
constructed: {1, ls_alarm_values, 0},
constructed: {2, alarm_values, 0},
constructed: {3, mode, 0}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%Extended{} = params, opts) do
with true <-
is_integer(params.vendor_id) and params.vendor_id >= 0 and params.vendor_id <= 65_535,
true <- is_integer(params.extended_event_type) and params.extended_event_type >= 0,
{:ok, vendor_id, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.vendor_id}, opts),
{:ok, extended_event_type, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.extended_event_type}, opts) do
{:ok,
{:constructed,
{9,
[
tagged: {0, vendor_id, byte_size(vendor_id)},
tagged: {1, extended_event_type, byte_size(extended_event_type)},
constructed: {2, params.parameters, 0}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%BufferReady{} = params, opts) do
with true <-
is_integer(params.threshold) and params.threshold >= 0 and
ApplicationTags.valid_int?(params.threshold, 32),
true <- is_integer(params.previous_count) and params.previous_count >= 0,
{:ok, threshold, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.threshold}, opts),
{:ok, previous_count, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.previous_count}, opts) do
{:ok,
{:constructed,
{10,
[
tagged: {0, threshold, byte_size(threshold)},
tagged: {1, previous_count, byte_size(previous_count)}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%UnsignedRange{} = params, opts) do
with true <- is_integer(params.time_delay) and params.time_delay >= 0,
true <- is_integer(params.low_limit) and params.low_limit >= 0,
true <- is_integer(params.high_limit) and params.high_limit >= 0,
{:ok, time_delay, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.time_delay}, opts),
{:ok, low_limit, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.low_limit}, opts),
{:ok, high_limit, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.high_limit}, opts) do
{:ok,
{:constructed,
{11,
[
tagged: {0, time_delay, byte_size(time_delay)},
tagged: {1, low_limit, byte_size(low_limit)},
tagged: {2, high_limit, byte_size(high_limit)}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%DoubleOutOfRange{} = params, opts) do
with true <- is_integer(params.time_delay) and params.time_delay >= 0,
true <- is_float(params.low_limit),
true <- is_float(params.high_limit),
true <- is_float(params.deadband),
{:ok, time_delay, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.time_delay}, opts),
{:ok, low_limit, _header} <-
ApplicationTags.encode_value({:double, params.low_limit}, opts),
{:ok, high_limit, _header} <-
ApplicationTags.encode_value({:double, params.high_limit}, opts),
{:ok, deadband, _header} <-
ApplicationTags.encode_value({:double, params.deadband}, opts) do
{:ok,
{:constructed,
{14,
[
tagged: {0, time_delay, byte_size(time_delay)},
tagged: {1, low_limit, byte_size(low_limit)},
tagged: {2, high_limit, byte_size(high_limit)},
tagged: {3, deadband, byte_size(deadband)}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%SignedOutOfRange{} = params, opts) do
with true <- is_integer(params.time_delay) and params.time_delay >= 0,
true <- is_integer(params.low_limit),
true <- is_integer(params.high_limit),
true <- is_integer(params.deadband) and params.deadband >= 0,
{:ok, time_delay, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.time_delay}, opts),
{:ok, low_limit, _header} <-
ApplicationTags.encode_value({:signed_integer, params.low_limit}, opts),
{:ok, high_limit, _header} <-
ApplicationTags.encode_value({:signed_integer, params.high_limit}, opts),
{:ok, deadband, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.deadband}, opts) do
{:ok,
{:constructed,
{15,
[
tagged: {0, time_delay, byte_size(time_delay)},
tagged: {1, low_limit, byte_size(low_limit)},
tagged: {2, high_limit, byte_size(high_limit)},
tagged: {3, deadband, byte_size(deadband)}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%UnsignedOutOfRange{} = params, opts) do
with true <- is_integer(params.time_delay) and params.time_delay >= 0,
true <- is_integer(params.low_limit) and params.low_limit >= 0,
true <- is_integer(params.high_limit) and params.high_limit >= 0,
true <- is_integer(params.deadband) and params.deadband >= 0,
{:ok, time_delay, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.time_delay}, opts),
{:ok, low_limit, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.low_limit}, opts),
{:ok, high_limit, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.high_limit}, opts),
{:ok, deadband, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.deadband}, opts) do
{:ok,
{:constructed,
{16,
[
tagged: {0, time_delay, byte_size(time_delay)},
tagged: {1, low_limit, byte_size(low_limit)},
tagged: {2, high_limit, byte_size(high_limit)},
tagged: {3, deadband, byte_size(deadband)}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%ChangeOfCharacterString{} = params, opts) do
with true <- is_integer(params.time_delay) and params.time_delay >= 0,
true <-
is_list(params.alarm_values) and
Enum.all?(
params.alarm_values,
&(is_nil(&1) or (is_binary(&1) and String.valid?(&1)))
),
{:ok, time_delay, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.time_delay}, opts),
alvalues when is_list(alvalues) <-
Enum.map(params.alarm_values, fn
nil -> {:null, nil}
str -> {:character_string, str}
end) do
{:ok,
{:constructed,
{17,
[
tagged: {0, time_delay, byte_size(time_delay)},
constructed: {1, alvalues, 0}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%ChangeOfStatusFlags{} = params, opts) do
with true <- is_integer(params.time_delay) and params.time_delay >= 0,
true <- is_struct(params.selected_flags, StatusFlags),
{:ok, time_delay, _header} <-
ApplicationTags.encode_value({:unsigned_integer, params.time_delay}, opts),
{:ok, flags, _header} <-
ApplicationTags.encode_value(StatusFlags.to_bitstring(params.selected_flags), opts) do
{:ok,
{:constructed,
{18,
[
tagged: {0, time_delay, byte_size(time_delay)},
tagged: {1, flags, byte_size(flags)}
], 0}}}
else
false -> {:error, :invalid_params}
{:error, _err} = err -> err
end
end
def encode(%None{} = _params, _opts) do
{:ok, {:constructed, {20, {:null, nil}, 0}}}
end
@doc """
Parses the encoded event values from a notification or Event Enrollment object
back into the corresponding EventParameters variant struct.
"""
@spec parse(binary()) :: {:ok, event_parameter()} | {:error, term()}
def parse(event_values_tag)
# 0 = Change Of Bitstring
def parse({:constructed, {0, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, time_delay_raw, _length},
tagged: {1, bitmask_raw, _length2},
constructed: {2, seq_bitstrings, _length3}
] ->
with {:ok, {:unsigned_integer, time_delay}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, time_delay_raw),
{:ok, {:bitstring, bitmask}} <-
ApplicationTags.unfold_to_type(:bitstring, bitmask_raw),
{:ok, alarm_values} <-
Enum.reduce_while(List.wrap(seq_bitstrings), {:ok, []}, fn
{:bitstring, bits}, {:ok, acc} -> {:cont, {:ok, [bits | acc]}}
_term, _acc -> {:halt, {:error, :invalid_alarm_values_parameter}}
end) do
event = %ChangeOfBitstring{
alarm_values: Enum.reverse(alarm_values),
bitmask: bitmask,
time_delay: time_delay,
time_delay_normal: nil
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 1 = Change Of State
def parse({:constructed, {1, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, time_delay_raw, _length},
constructed: {1, seq_propstates, _length2}
] ->
with {:ok, {:unsigned_integer, time_delay}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, time_delay_raw),
{:ok, alarm_values} <-
Enum.reduce_while(seq_propstates, {:ok, []}, fn
term, {:ok, acc} ->
case BACnet.Protocol.PropertyState.parse(List.wrap(term)) do
{:ok, {state, _rest}} -> {:cont, {:ok, [state | acc]}}
_term -> {:halt, {:error, :invalid_alarm_values_parameter}}
end
end) do
event = %ChangeOfState{
alarm_values: Enum.reverse(alarm_values),
time_delay: time_delay,
time_delay_normal: nil
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 2 = Change Of Value
def parse({:constructed, {2, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, time_delay_raw, _length},
constructed: {1, cov_criteria_raw, _length2}
] ->
with {:ok, {:unsigned_integer, time_delay}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, time_delay_raw),
{:ok, {increment, bitmask}} <-
(case cov_criteria_raw do
{:tagged, {0, _con, _len}} ->
with {:ok, {:bitstring, bitmask}} <-
ApplicationTags.unfold_to_type(:bitstring, cov_criteria_raw),
do: {:ok, {nil, bitmask}}
{:tagged, {1, _con, _len}} ->
with {:ok, {:real, increment}} <-
ApplicationTags.unfold_to_type(:real, cov_criteria_raw),
do: {:ok, {increment, nil}}
_term ->
{:error, :invalid_cov_criteria}
end) do
event = %ChangeOfValue{
increment: increment,
bitmask: bitmask,
time_delay: time_delay,
time_delay_normal: nil
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 3 = Command Failure
def parse({:constructed, {3, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, time_delay_raw, _length},
constructed: {1, feedback_value, _length3}
] ->
with {:ok, {:unsigned_integer, time_delay}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, time_delay_raw),
{:ok, {feedback_value, _tags}} <- DeviceObjectPropertyRef.parse(feedback_value) do
event = %CommandFailure{
feedback_value: feedback_value,
time_delay: time_delay,
time_delay_normal: nil
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 4 = Floating Limit
def parse({:constructed, {4, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, time_delay_raw, _length},
constructed: {1, setpoint_ref_raw, _length2},
tagged: {2, low_diff_raw, _length3},
tagged: {3, high_diff_raw, _length4},
tagged: {4, deadband_raw, _length5}
] ->
with {:ok, {:unsigned_integer, time_delay}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, time_delay_raw),
{:ok, {setpoint_ref, _tags}} <- DeviceObjectPropertyRef.parse(setpoint_ref_raw),
{:ok, {:real, low_diff}} <-
ApplicationTags.unfold_to_type(:real, low_diff_raw),
{:ok, {:real, high_diff}} <-
ApplicationTags.unfold_to_type(:real, high_diff_raw),
{:ok, {:real, deadband}} <-
ApplicationTags.unfold_to_type(:real, deadband_raw) do
event = %FloatingLimit{
setpoint: setpoint_ref,
low_diff_limit: low_diff,
high_diff_limit: high_diff,
deadband: deadband,
time_delay: time_delay,
time_delay_normal: nil
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 5 = Out Of Range
def parse({:constructed, {5, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, time_delay_raw, _length},
tagged: {1, low_limit_raw, _length2},
tagged: {2, high_limit_raw, _length3},
tagged: {3, deadband_raw, _length4}
] ->
with {:ok, {:unsigned_integer, time_delay}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, time_delay_raw),
{:ok, {:real, low_limit}} <-
ApplicationTags.unfold_to_type(:real, low_limit_raw),
{:ok, {:real, high_limit}} <-
ApplicationTags.unfold_to_type(:real, high_limit_raw),
{:ok, {:real, deadband}} <-
ApplicationTags.unfold_to_type(:real, deadband_raw) do
event = %OutOfRange{
low_limit: low_limit,
high_limit: high_limit,
deadband: deadband,
time_delay: time_delay,
time_delay_normal: nil
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 8 = Change Of Life Safety
def parse({:constructed, {8, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, time_delay_raw, _length},
constructed: {1, life_safety_state_raw, _length2},
constructed: {2, alarm_values_raw, _length3},
constructed: {3, mode_raw, _length4}
] ->
with {:ok, {:unsigned_integer, time_delay}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, time_delay_raw),
{:ok, ls_alarm_values} <-
Enum.reduce_while(life_safety_state_raw, {:ok, []}, fn pack, {:ok, acc} ->
case ApplicationTags.unfold_to_type(:enumerated, pack) do
{:ok, {:enumerated, value}} ->
case Constants.by_value_with_reason(
:life_safety_state,
value,
{:unknown_life_safety_alarm_value, pack}
) do
{:ok, value_c} -> {:cont, {:ok, [value_c | acc]}}
{:error, _err} = err -> {:halt, err}
end
term ->
{:halt, term}
end
end),
{:ok, alarm_values} <-
Enum.reduce_while(alarm_values_raw, {:ok, []}, fn pack, {:ok, acc} ->
case ApplicationTags.unfold_to_type(:enumerated, pack) do
{:ok, {:enumerated, value}} ->
case Constants.by_value_with_reason(
:life_safety_state,
value,
{:unknown_alarm_value, pack}
) do
{:ok, value_c} -> {:cont, {:ok, [value_c | acc]}}
{:error, _err} = err -> {:halt, err}
end
term ->
{:halt, term}
end
end),
{:ok, {mode, _tags}} <- DeviceObjectPropertyRef.parse(mode_raw) do
event = %ChangeOfLifeSafety{
mode: mode,
alarm_values: Enum.reverse(alarm_values),
life_safety_alarm_values: Enum.reverse(ls_alarm_values),
time_delay: time_delay,
time_delay_normal: nil
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 9 = Extended
def parse({:constructed, {9, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, vendor_id_raw, _length},
tagged: {1, ext_event_raw, _length2},
# TODO: May be not constructed (tagged)
constructed: {2, parameters, _length3}
] ->
with {:ok, {:unsigned_integer, vendor_id}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, vendor_id_raw),
:ok <-
if(ApplicationTags.valid_int?(vendor_id, 16),
do: :ok,
else: {:error, :invalid_vendor_id_value}
),
{:ok, {:unsigned_integer, ext_event}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, ext_event_raw) do
event = %Extended{
vendor_id: vendor_id,
extended_event_type: ext_event,
parameters: parameters
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 10 = Buffer Ready
def parse({:constructed, {10, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, threshold_raw, _length},
tagged: {1, previous_count_raw, _length2}
] ->
with {:ok, {:unsigned_integer, threshold}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, threshold_raw),
{:ok, {:unsigned_integer, previous_count}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, previous_count_raw),
:ok <-
if(ApplicationTags.valid_int?(previous_count, 32),
do: :ok,
else: {:error, :invalid_previous_count_value}
) do
event = %BufferReady{
threshold: threshold,
previous_count: previous_count
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 11 = Unsigned Range
def parse({:constructed, {11, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, time_delay_raw, _length},
tagged: {1, low_limit_raw, _length2},
tagged: {2, high_limit_raw, _length3}
] ->
with {:ok, {:unsigned_integer, time_delay}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, time_delay_raw),
{:ok, {:unsigned_integer, low_limit}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, low_limit_raw),
{:ok, {:unsigned_integer, high_limit}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, high_limit_raw) do
event = %UnsignedRange{
low_limit: low_limit,
high_limit: high_limit,
time_delay: time_delay,
time_delay_normal: nil
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 13 = Access Event
def parse({:constructed, {13, _event_values, 0}}) do
{:error, :not_supported_event_type}
end
# 14 = Double Out Of Range
def parse({:constructed, {14, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, time_delay_raw, _length},
tagged: {1, low_limit_raw, _length2},
tagged: {2, high_limit_raw, _length3},
tagged: {3, deadband_raw, _length4}
] ->
with {:ok, {:unsigned_integer, time_delay}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, time_delay_raw),
{:ok, {:double, low_limit}} <-
ApplicationTags.unfold_to_type(:double, low_limit_raw),
{:ok, {:double, high_limit}} <-
ApplicationTags.unfold_to_type(:double, high_limit_raw),
{:ok, {:double, deadband}} <-
ApplicationTags.unfold_to_type(:double, deadband_raw) do
event = %DoubleOutOfRange{
low_limit: low_limit,
high_limit: high_limit,
deadband: deadband,
time_delay: time_delay,
time_delay_normal: nil
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 15 = Signed Out Of Range
def parse({:constructed, {15, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, time_delay_raw, _length},
tagged: {1, low_limit_raw, _length2},
tagged: {2, high_limit_raw, _length3},
tagged: {3, deadband_raw, _length4}
] ->
with {:ok, {:unsigned_integer, time_delay}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, time_delay_raw),
{:ok, {:signed_integer, low_limit}} <-
ApplicationTags.unfold_to_type(:signed_integer, low_limit_raw),
{:ok, {:signed_integer, high_limit}} <-
ApplicationTags.unfold_to_type(:signed_integer, high_limit_raw),
{:ok, {:unsigned_integer, deadband}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, deadband_raw) do
event = %SignedOutOfRange{
low_limit: low_limit,
high_limit: high_limit,
deadband: deadband,
time_delay: time_delay,
time_delay_normal: nil
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 16 = Unsigned Out Of Range
def parse({:constructed, {16, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, time_delay_raw, _length},
tagged: {1, low_limit_raw, _length2},
tagged: {2, high_limit_raw, _length3},
tagged: {3, deadband_raw, _length4}
] ->
with {:ok, {:unsigned_integer, time_delay}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, time_delay_raw),
{:ok, {:unsigned_integer, low_limit}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, low_limit_raw),
{:ok, {:unsigned_integer, high_limit}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, high_limit_raw),
{:ok, {:unsigned_integer, deadband}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, deadband_raw) do
event = %UnsignedOutOfRange{
low_limit: low_limit,
high_limit: high_limit,
deadband: deadband,
time_delay: time_delay,
time_delay_normal: nil
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 17 = Change Of Character String
def parse({:constructed, {17, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, time_delay_raw, _length},
constructed: {1, strings, _length2}
] ->
with {:ok, {:unsigned_integer, time_delay}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, time_delay_raw),
alvalues when is_list(alvalues) <-
Enum.map(strings, fn
{:null, _nil} -> nil
{:character_string, str} -> str
end) do
event = %ChangeOfCharacterString{
alarm_values: alvalues,
time_delay: time_delay,
time_delay_normal: nil
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 18 = Change Of Status Flags
def parse({:constructed, {18, event_values, 0}}) do
case List.wrap(event_values) do
[
tagged: {0, time_delay_raw, _length},
tagged: {1, sel_flags_raw, _length2}
] ->
with {:ok, {:unsigned_integer, time_delay}} <-
ApplicationTags.unfold_to_type(:unsigned_integer, time_delay_raw),
{:ok, {:bitstring, sel_flags_bs}} <-
ApplicationTags.unfold_to_type(:bitstring, sel_flags_raw),
%StatusFlags{} = sel_flags <- StatusFlags.from_bitstring(sel_flags_bs) do
event = %ChangeOfStatusFlags{
selected_flags: sel_flags,
time_delay: time_delay,
time_delay_normal: nil
}
{:ok, event}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_event_values}
end
end
# 20 = None
def parse({:constructed, {20, event_values, 0}}) do
case List.wrap(event_values) do
[{:null, nil}] -> {:ok, %None{}}
_term -> {:error, :invalid_event_values}
end
end
# 20 = None
def parse({:tagged, {20, "", 0}}) do
{:ok, %None{}}
end
def parse(_event_values_tag) do
{:error, :invalid_tag}
end
@doc """
Validates whether the given event parameter is in form valid.
It only validates the struct is valid as per type specification.
"""
@spec valid?(event_parameter()) :: boolean()
def valid?(t)
for module <- [
ChangeOfBitstring,
ChangeOfState,
ChangeOfValue,
CommandFailure,
FloatingLimit,
OutOfRange,
ChangeOfLifeSafety,
Extended,
BufferReady,
UnsignedRange,
DoubleOutOfRange,
SignedOutOfRange,
UnsignedOutOfRange,
ChangeOfCharacterString,
ChangeOfStatusFlags,
None
] do
var = Macro.var(:t, __MODULE__)
def valid?(%unquote(module){} = unquote(var)) do
unquote(BACnet.BeamTypes.generate_valid_clause(module, __ENV__))
end
end
end