Current section
Files
Jump to
Current section
Files
lib/bacnet/protocol/services/ack/read_range_ack.ex
defmodule BACnet.Protocol.Services.Ack.ReadRangeAck do
# TODO: Docs
alias BACnet.Protocol.APDU.ComplexACK
alias BACnet.Protocol.ApplicationTags
alias BACnet.Protocol.ResultFlags
alias BACnet.Protocol.Constants
import BACnet.Protocol.Utility, only: [pattern_extract_tags: 4]
require Constants
@type t :: %__MODULE__{
object_identifier: BACnet.Protocol.ObjectIdentifier.t(),
property_identifier: Constants.property_identifier() | non_neg_integer(),
property_array_index: non_neg_integer() | nil,
result_flags: ResultFlags.t(),
item_count: non_neg_integer(),
item_data: [ApplicationTags.Encoding.t()],
first_sequence_number: ApplicationTags.unsigned32() | nil
}
@fields [
:object_identifier,
:property_identifier,
:property_array_index,
:result_flags,
:item_count,
:item_data,
:first_sequence_number
]
@enforce_keys @fields
defstruct @fields
@service_name Constants.macro_assert_name(
:confirmed_service_choice,
:read_range
)
@spec from_apdu(ComplexACK.t()) :: {:ok, t()} | {:error, term()}
def from_apdu(%ComplexACK{service: @service_name} = ack) do
with {:ok, obj, rest} <-
pattern_extract_tags(
ack.payload,
{:tagged, {0, _t, _l}},
:object_identifier,
false
),
{:ok, property, rest} <-
pattern_extract_tags(rest, {:tagged, {1, _t, _l}}, :enumerated, false),
{:ok, array_index, rest} <-
pattern_extract_tags(rest, {:tagged, {2, _t, _l}}, :unsigned_integer, true),
{:ok, {first, last, more}, rest} <-
pattern_extract_tags(rest, {:tagged, {3, _t, _l}}, :bitstring, false),
{:ok, item_count, rest} <-
pattern_extract_tags(rest, {:tagged, {4, _t, _l}}, :unsigned_integer, false),
{:ok, {:constructed, {5, item_data, _len}}, rest} <-
pattern_extract_tags(rest, {:constructed, {5, _t, _l}}, nil, false),
{:ok, first_seq_num, _rest} <-
pattern_extract_tags(rest, {:tagged, {6, _t, _l}}, :unsigned_integer, true),
:ok <-
if(ApplicationTags.valid_int?(first_seq_num, 32),
do: :ok,
else: {:error, :invalid_first_sequence_number_value}
) do
struc = %__MODULE__{
object_identifier: obj,
property_identifier: Constants.by_value(:property_identifier, property, property),
property_array_index: array_index,
result_flags: %ResultFlags{
first_item: first,
last_item: last,
more_items: more
},
item_count: item_count,
item_data: Enum.map(item_data, &ApplicationTags.Encoding.create!/1),
first_sequence_number: first_seq_num
}
{:ok, struc}
else
{:error, :missing_pattern} -> {:error, :invalid_service_ack}
{:error, _err} = err -> err
end
end
def from_apdu(_ack) do
{:error, :invalid_service_ack}
end
@spec to_apdu(t(), 0..255) :: {:ok, ComplexACK.t()} | {:error, term()}
def to_apdu(ack, invoke_id \\ 0)
def to_apdu(%__MODULE__{} = ack, invoke_id) when invoke_id in 0..255 do
with :ok <-
if(ApplicationTags.valid_int?(ack.first_sequence_number, 32),
do: :ok,
else: {:error, :invalid_first_sequence_number_value}
),
{:ok, obj, _header} <-
ApplicationTags.encode_value({:object_identifier, ack.object_identifier}),
{:ok, property, _header} <-
ApplicationTags.encode_value(
{:enumerated, Constants.by_name_atom(:property_identifier, ack.property_identifier)}
),
{:ok, array_index} <-
(if ack.property_array_index do
with {:ok, index, _header} <-
ApplicationTags.encode_value({:unsigned_integer, ack.property_array_index}),
do: {:ok, {:tagged, {2, index, byte_size(index)}}}
else
{:ok, nil}
end),
{:ok, result_flags, _header} <- encode_result_flag(ack.result_flags),
{:ok, item_count, _header} <-
ApplicationTags.encode_value({:unsigned_integer, ack.item_count}),
{:ok, value} <- encode_item_data(ack.item_data),
{:ok, first_seq_num} <-
(if ack.first_sequence_number do
with {:ok, num, _header} <-
ApplicationTags.encode_value({:unsigned_integer, ack.first_sequence_number}),
do: {:ok, {:tagged, {6, num, byte_size(num)}}}
else
{:ok, nil}
end) do
parameters = [
{:tagged, {0, obj, byte_size(obj)}},
{:tagged, {1, property, byte_size(property)}},
array_index,
{:tagged, {3, result_flags, byte_size(result_flags)}},
{:tagged, {4, item_count, byte_size(item_count)}},
{:constructed, {5, value, 0}},
first_seq_num
]
new_ack = %ComplexACK{
invoke_id: invoke_id,
sequence_number: nil,
proposed_window_size: nil,
service: @service_name,
payload: Enum.reject(parameters, &is_nil/1)
}
{:ok, new_ack}
end
end
def to_apdu(%__MODULE__{} = _ack, _invoke_id) do
{:error, :invalid_parameter}
end
defp encode_result_flag(%ResultFlags{} = flags) do
ApplicationTags.encode_value(
{:bitstring, {flags.first_item, flags.last_item, flags.more_items}}
)
end
defp encode_item_data(list) when is_list(list) do
result =
Enum.reduce_while(list, {:ok, []}, fn item, {:ok, acc} ->
case ApplicationTags.Encoding.to_encoding(item) do
{:ok, val} -> {:cont, {:ok, [val | acc]}}
term -> {:halt, term}
end
end)
case result do
{:ok, list} ->
# TODO: Do we need to flatten the list?
# new_list =
# list
# |> Enum.reverse()
# |> List.flatten()
{:ok, Enum.reverse(list)}
term ->
term
end
end
end