Current section
Files
Jump to
Current section
Files
lib/bacnet/protocol/accumulator_record.ex
defmodule BACnet.Protocol.AccumulatorRecord do
# TODO: Docs
# TODO: Throw argument error in encode if not valid
alias BACnet.Protocol.ApplicationTags
alias BACnet.Protocol.BACnetDateTime
alias BACnet.Protocol.Constants
import BACnet.Protocol.Utility, only: [pattern_extract_tags: 4]
require Constants
@type t :: %__MODULE__{
timestamp: BACnetDateTime.t(),
present_value: non_neg_integer(),
accumulated_value: non_neg_integer(),
status: Constants.accumulator_status()
}
@fields [
:timestamp,
:present_value,
:accumulated_value,
:status
]
@enforce_keys @fields
defstruct @fields
@doc """
Encodes an accumulator record struct into application tags encoding.
"""
@spec encode(t(), Keyword.t()) ::
{:ok, ApplicationTags.encoding_list()} | {:error, term()}
def encode(%__MODULE__{timestamp: %BACnetDateTime{}} = record, opts \\ []) do
with {:ok, present_value, _header} <-
ApplicationTags.encode_value({:unsigned_integer, record.present_value}, opts),
{:ok, acc_value, _header} <-
ApplicationTags.encode_value({:unsigned_integer, record.accumulated_value}, opts),
{:ok, status_c} <-
Constants.by_name_with_reason(
:accumulator_status,
record.status,
{:unknown_status, record.status}
),
{:ok, status, _header} <- ApplicationTags.encode_value({:enumerated, status_c}, opts) do
{:ok,
[
constructed: {0, [date: record.timestamp.date, time: record.timestamp.time], 0},
tagged: {1, present_value, byte_size(present_value)},
tagged: {2, acc_value, byte_size(acc_value)},
tagged: {3, status, byte_size(status)}
]}
else
{:error, _err} = err -> err
end
end
@doc """
Parses a BACnet accumulator record into a struct.
"""
@spec parse(ApplicationTags.encoding_list()) ::
{:ok, {t(), rest :: ApplicationTags.encoding_list()}} | {:error, term()}
def parse(tags) when is_list(tags) do
with {:ok,
{:constructed,
{0,
[
date: date,
time: time
], _l}},
rest} <-
pattern_extract_tags(tags, {:constructed, {0, _t, _l}}, nil, false),
{:ok, present_value, rest} <-
pattern_extract_tags(rest, {:tagged, {1, _t, _l}}, :unsigned_integer, false),
{:ok, acc_value, rest} <-
pattern_extract_tags(rest, {:tagged, {2, _t, _l}}, :unsigned_integer, false),
{:ok, acc_status, rest} <-
pattern_extract_tags(rest, {:tagged, {3, _t, _l}}, :enumerated, false),
{:ok, acc_status_c} <-
Constants.by_value_with_reason(
:accumulator_status,
acc_status,
{:unknown_status, acc_status}
) do
acc = %__MODULE__{
timestamp: %BACnetDateTime{
date: date,
time: time
},
present_value: present_value,
accumulated_value: acc_value,
status: acc_status_c
}
{:ok, {acc, rest}}
else
{:error, :missing_pattern} -> {:error, :invalid_tags}
{:error, _err} = err -> err
end
end
@doc """
Validates whether the given accumulator record is in form valid.
It only validates the struct is valid as per type specification.
"""
@spec valid?(t()) :: boolean()
def valid?(
%__MODULE__{
timestamp: %BACnetDateTime{} = ts,
present_value: pv,
accumulated_value: av,
status: status
} = _t
)
when is_integer(pv) and pv >= 0 and is_integer(av) and av >= 0 do
BACnetDateTime.valid?(ts) and Constants.has_by_name(:accumulator_status, status)
end
def valid?(%__MODULE__{} = _t), do: false
end