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 i_am.ex
Raw

lib/bacnet/protocol/services/i_am.ex

defmodule BACnet.Protocol.Services.IAm do
@moduledoc """
This module represents the BACnet I-Am service.
The I-Am service is used as a response to the Who-Is service. It may also be used to announce itself (the device).
Service Description (ASHRAE 135):
> The I-Am service is used to respond to Who-Is service requests. However, the IAm service request may be issued at any time.
> It does not need to be preceded by the receipt of a Who-Is service request.
> In particular, a device may wish to broadcast an I-Am service request when it powers up. The network address is derived either
> from the MAC address associated with the I-Am service request, if the device issuing the request is on the local network, or
> from the NPCI if the device is on a remote network.
"""
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__{
device: Protocol.ObjectIdentifier.t(),
max_apdu: pos_integer(),
segmentation_supported: Constants.segmentation(),
vendor_id: ApplicationTags.unsigned16()
}
@fields [
:device,
:max_apdu,
:segmentation_supported,
:vendor_id
]
@enforce_keys @fields
defstruct @fields
@service_name Constants.macro_assert_name(:unconfirmed_service_choice, :i_am)
@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() :: false
def is_confirmed(), do: false
@doc """
Converts the given Unconfirmed Service Request into an I-Am Service.
See the `BACnet.Protocol.Services.Protocol` function documentation for more information.
"""
@spec from_apdu(Protocol.APDU.UnconfirmedServiceRequest.t()) ::
{:ok, t()} | {:error, term()}
def from_apdu(
%Protocol.APDU.UnconfirmedServiceRequest{
service: @service_name
} = request
) do
case request.parameters do
[
{:object_identifier, %Protocol.ObjectIdentifier{} = object_identifier},
{:unsigned_integer, apdu},
{:enumerated, segmented},
{:unsigned_integer, vendor_id} | _tail
] ->
with :ok <-
if(ApplicationTags.valid_int?(vendor_id, 16),
do: :ok,
else: {:error, :invalid_vendor_id_value}
),
{:ok, seg_c} <-
Constants.by_value_with_reason(
:segmentation,
segmented,
{:unknown_segmentation, segmented}
) do
iam = %__MODULE__{
device: object_identifier,
max_apdu: apdu,
segmentation_supported: seg_c,
vendor_id: vendor_id
}
{:ok, iam}
end
_term ->
{:error, :invalid_request_parameters}
end
end
def from_apdu(%Protocol.APDU.UnconfirmedServiceRequest{} = _request) do
{:error, :invalid_request}
end
@doc """
Get the Unconfirmed Service request for the given I-Am Service.
See the `BACnet.Protocol.Services.Protocol` function documentation for more information.
"""
@spec to_apdu(t(), Keyword.t()) ::
{:ok, Protocol.APDU.UnconfirmedServiceRequest.t()}
| {:error, term()}
def to_apdu(
%__MODULE__{
device: %Protocol.ObjectIdentifier{} = device,
max_apdu: apdu,
segmentation_supported: seg,
vendor_id: vendor
} = service,
_request_data
)
when is_integer(apdu) and apdu >= 0 and
is_integer(vendor) and vendor >= 0 do
with :ok <-
if(ApplicationTags.valid_int?(vendor, 16),
do: :ok,
else: {:error, :invalid_vendor_id_value}
),
{:ok, seg_c} <-
Constants.by_name_with_reason(:segmentation, seg, {:unknown_segmentation, seg}) do
req = %Protocol.APDU.UnconfirmedServiceRequest{
service: @service_name,
parameters: [
object_identifier: device,
unsigned_integer: service.max_apdu,
enumerated: seg_c,
unsigned_integer: service.vendor_id
]
}
{: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.UnconfirmedServiceRequest.t()}
| {:error, term()}
def to_apdu(%@for{} = service, request_data), do: @for.to_apdu(service, request_data)
end
end