Current section
Files
Jump to
Current section
Files
lib/bacnet/protocol/services/create_object.ex
defmodule BACnet.Protocol.Services.CreateObject do
@moduledoc """
This module represents the BACnet Create Object service.
The Create Object service is used to dynamically create an object in the remote device.
Service Description (ASHRAE 135):
> The CreateObject service is used by a client BACnet-user to create a new instance of an object. This service may be used to
> create instances of both standard and vendor specific objects. The standard object types supported by this service shall be
> specified in the PICS. The properties of standard objects created with this service may be initialized in two ways: initial
> values may be provided as part of the CreateObject service request or values may be written to the newly created object using
> the BACnet WriteProperty services. The initialization of non-standard objects is a local matter. The behavior of objects
> created by this service that are not supplied, or only partially supplied, with initial property values is dependent upon the
> device and is a local matter.
"""
alias BACnet.Protocol
alias BACnet.Protocol.ApplicationTags
alias BACnet.Protocol.Constants
require Constants
@behaviour Protocol.Services.Behaviour
# TODO: Docs
# TODO: Add Service Procedure to docs
@type t :: %__MODULE__{
object_specifier: Protocol.ObjectIdentifier.t() | Constants.object_type(),
initial_values: [Protocol.PropertyValue.t()]
}
@fields [
:object_specifier,
:initial_values
]
@enforce_keys @fields
defstruct @fields
@service_name Constants.macro_assert_name(
:confirmed_service_choice,
:create_object
)
@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 Create Object Service.
"""
@spec from_apdu(Protocol.APDU.ConfirmedServiceRequest.t()) ::
{:ok, t()} | {:error, term()}
def from_apdu(
%Protocol.APDU.ConfirmedServiceRequest{
service: @service_name
} = request
) do
case request.parameters do
[
{:constructed, {0, {:tagged, {tag_num, object_specifier, _length2}}, _length}}
| rest
]
when tag_num in [0, 1] ->
object =
case tag_num do
0 ->
with {:ok, {:enumerated, enum}} <-
ApplicationTags.unfold_to_type(:enumerated, object_specifier),
{:ok, const_enum} <-
(case Constants.by_value(:object_type, enum) do
{:ok, _val} = val -> val
:error -> {:error, {:unknown_object_type, enum}}
end),
do: {:ok, const_enum}
1 ->
with {:ok, {:object_identifier, obj}} <-
ApplicationTags.unfold_to_type(:object_identifier, object_specifier),
do: {:ok, obj}
end
with {:ok, specifier} <- object,
{:ok, values} <-
(case rest do
[{:constructed, {1, [_hd | _tl] = list_of_initial, _length}} | _tl2]
when is_list(list_of_initial) ->
Protocol.PropertyValue.parse_all(list_of_initial)
[{:constructed, {1, [], _length}} | _tl2] ->
{:ok, []}
_else ->
{:ok, []}
end) do
obj = %__MODULE__{
object_specifier: specifier,
initial_values: values
}
{:ok, obj}
else
{:error, :missing_pattern} -> {:error, :invalid_request_parameters}
{:error, _err} = err -> err
end
_else ->
{:error, :invalid_request_parameters}
end
end
def from_apdu(%Protocol.APDU.ConfirmedServiceRequest{} = _request) do
{:error, :invalid_request}
end
@doc """
Get the Confirmed Service request for the given Create Object 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
specifier =
if is_struct(service.object_specifier, Protocol.ObjectIdentifier) do
{1, ApplicationTags.encode_value({:object_identifier, service.object_specifier})}
else
result =
with {:ok, object_type_c} <-
Constants.by_name_with_reason(
:object_type,
service.object_specifier,
{:unknown_object_type, service.object_specifier}
) do
ApplicationTags.encode_value({:enumerated, object_type_c})
end
{0, result}
end
case specifier do
{tag, {:ok, specifier, _hd}} ->
with {:ok, proplist} <- Protocol.PropertyValue.encode_all(service.initial_values) 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: [
{:constructed, {0, {:tagged, {tag, specifier, byte_size(specifier)}}, 0}}
| if(proplist == [], do: [], else: [{:constructed, {1, proplist, 0}}])
]
}
{:ok, req}
end
{_tag, term} ->
term
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