Packages

BACstack is a low-level Elixir implementation for the ASHRAE standard 135, BACnet - Building Automation and Controller network. In its current published state, v0.0.x, it can (only) act as a BACnet client for other BACnet devices.

Current section

Files

Jump to
bacstack lib bacnet protocol services read_property_multiple.ex
Raw

lib/bacnet/protocol/services/read_property_multiple.ex

defmodule BACnet.Protocol.Services.ReadPropertyMultiple do
@moduledoc """
This module represents the BACnet Read Property Multiple service.
The Read Property Multiple service is used to read multiple properties of one or multiple objects.
Service Description (ASHRAE 135):
> The ReadPropertyMultiple service is used by a client BACnet-user to request the values of one or more specified properties
> of one or more BACnet Objects. This service allows read access to any property of any object, whether a BACnet-defined
> object or not. The user may read a single property of a single object, a list of properties of a single object, or any number of
> properties of any number of objects. A 'Read Access Specification' with the property identifier ALL can be used to learn the
> implemented properties of an object along with their values.
"""
alias BACnet.Protocol
alias BACnet.Protocol.Constants
require Constants
@behaviour Protocol.Services.Behaviour
# TODO: Docs
# TODO: Add Service Procedure to docs
@type t :: %__MODULE__{
list: [Protocol.AccessSpecification.t()]
}
@fields [:list]
@enforce_keys @fields
defstruct @fields
@service_name Constants.macro_assert_name(
:confirmed_service_choice,
:read_property_multiple
)
@doc """
Get the service name atom.
"""
@spec get_name() :: atom()
def get_name(), do: @service_name
@doc """
Whether the service is of type confirmed or unconfirmed.
"""
@spec is_confirmed() :: true
def is_confirmed(), do: true
@doc """
Converts the given Confirmed Service Request into a Read Property Multiple Service.
"""
@spec from_apdu(Protocol.APDU.ConfirmedServiceRequest.t()) ::
{:ok, t()} | {:error, term()}
def from_apdu(
%Protocol.APDU.ConfirmedServiceRequest{
service: @service_name
} = request
) do
result =
Enum.reduce_while(1..100_000//1, {request.parameters, []}, fn
_iter, {tags, acc} ->
case Protocol.AccessSpecification.parse(tags) do
{:ok, {item, []}} -> {:halt, {:ok, [item | acc]}}
{:ok, {item, rest}} -> {:cont, {rest, [item | acc]}}
{:error, _term} = term -> {:halt, term}
end
end)
case result do
{:ok, list} ->
readprop = %__MODULE__{
list: Enum.reverse(list)
}
{:ok, readprop}
term ->
term
end
end
def from_apdu(%Protocol.APDU.ConfirmedServiceRequest{} = _request) do
{:error, :invalid_request}
end
@doc """
Get the Confirmed Service request for the given Read Property Multiple Service.
See the `BACnet.Protocol.Services.Protocol` function documentation for more information.
"""
@spec to_apdu(t(), Keyword.t()) ::
{:ok, Protocol.APDU.ConfirmedServiceRequest.t()}
| {:error, term()}
def to_apdu(%__MODULE__{} = service, request_data) do
with {:ok, parameters} <-
Enum.reduce_while(service.list, {:ok, []}, fn
ras, {:ok, acc} ->
case Protocol.AccessSpecification.encode(ras) do
{:ok, encoded} -> {:cont, {:ok, [encoded | acc]}}
term -> {:halt, term}
end
end) do
req = %Protocol.APDU.ConfirmedServiceRequest{
segmented_response_accepted: request_data[:segmented_response_accepted] || true,
max_segments: request_data[:max_segments] || :more_than_64,
max_apdu:
request_data[:max_apdu] ||
Constants.macro_by_name(:max_apdu_length_accepted_value, :octets_1476),
invoke_id: request_data[:invoke_id] || 0,
sequence_number: nil,
proposed_window_size: nil,
service: @service_name,
parameters: List.flatten(Enum.reverse(parameters))
}
{:ok, req}
end
end
defimpl Protocol.Services.Protocol do
alias BACnet.Protocol
alias BACnet.Protocol.Constants
require Constants
@spec get_name(@for.t()) :: atom()
def get_name(%@for{} = _service), do: @for.get_name()
@spec is_confirmed(@for.t()) :: boolean()
def is_confirmed(%@for{} = _service), do: @for.is_confirmed()
@spec to_apdu(@for.t(), Keyword.t()) ::
{:ok, Protocol.APDU.ConfirmedServiceRequest.t()}
| {:error, term()}
def to_apdu(%@for{} = service, request_data), do: @for.to_apdu(service, request_data)
end
end