Current section
Files
Jump to
Current section
Files
lib/bacnet/protocol/action_list.ex
defmodule BACnet.Protocol.ActionList do
# TODO: Docs
# TODO: Throw argument error in encode if not valid
alias BACnet.Protocol.ApplicationTags
alias BACnet.Protocol.ActionCommand
alias BACnet.Protocol.Constants
require Constants
@type t :: %__MODULE__{
actions: [ActionCommand.t()]
}
@fields [:actions]
@enforce_keys @fields
defstruct @fields
@doc """
Encode a BACnet action list into application tag-encoded.
"""
@spec encode(t(), Keyword.t()) ::
{:ok, ApplicationTags.encoding_list()} | {:error, term()}
def encode(%__MODULE__{} = list, opts \\ []) do
with {:ok, cmds} <-
Enum.reduce_while(list.actions, {:ok, []}, fn
cmd, {:ok, acc} ->
case ActionCommand.encode(cmd, opts) do
{:ok, encoded} -> {:cont, {:ok, [encoded | acc]}}
{:error, _err} = err -> {:halt, err}
end
end) do
cmdlist =
cmds
|> Enum.reverse()
|> List.flatten()
{:ok, [{:constructed, {0, cmdlist, 0}}]}
else
{:error, _err} = err -> err
end
end
@doc """
Parse a BACnet action list 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
[{:constructed, {0, tags, _len}} | rest] ->
with {:ok, {actions, _rest}} <-
Enum.reduce_while(1..100_000//1, {:ok, {[], tags}}, fn
_ind, {:ok, {acc, []}} ->
{:halt, {:ok, {acc, []}}}
_ind, {:ok, {acc, tags}} ->
case ActionCommand.parse(tags) do
{:ok, {cmd, rest}} -> {:cont, {:ok, {[cmd | acc], rest}}}
{:error, _err} = err -> {:halt, err}
end
end) do
list = %__MODULE__{
actions: Enum.reverse(actions)
}
{:ok, {list, rest}}
else
{:error, _err} = err -> err
end
_term ->
{:error, :invalid_tags}
end
end
@doc """
Validates whether the given action list is in form valid.
It only validates the struct is valid as per type specification.
"""
@spec valid?(t()) :: boolean()
def valid?(
%__MODULE__{
actions: actions
} = _t
)
when is_list(actions) do
Enum.all?(actions, fn
%ActionCommand{} = cmd -> ActionCommand.valid?(cmd)
_else -> false
end)
end
def valid?(%__MODULE__{} = _t), do: false
end