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

lib/bacnet/protocol/address_binding.ex

defmodule BACnet.Protocol.AddressBinding do
# TODO: Docs
# TODO: Throw argument error in encode if not valid
alias BACnet.Protocol.ApplicationTags
alias BACnet.Protocol.Constants
alias BACnet.Protocol.NetworkLayerProtocolMessage
alias BACnet.Protocol.ObjectIdentifier
require Constants
@type t :: %__MODULE__{
device_identifier: ObjectIdentifier.t(),
network: NetworkLayerProtocolMessage.dnet(),
address: binary()
}
@fields [:device_identifier, :network, :address]
@enforce_keys @fields
defstruct @fields
@doc """
Encodes a BACnet address binding into BACnet application tags encoding.
"""
@spec encode(t(), Keyword.t()) ::
{:ok, ApplicationTags.encoding_list()} | {:error, term()}
def encode(
%__MODULE__{
device_identifier: %ObjectIdentifier{type: :device},
network: net,
address: mac
} = access_spec,
_opts \\ []
)
when is_integer(net) and is_binary(mac) do
if net >= 0 and ApplicationTags.valid_int?(net, 16) do
params = [
{:object_identifier, access_spec.device_identifier},
{:unsigned_integer, net},
{:octet_string, String.upcase(mac, :ascii)}
]
{:ok, params}
else
{:error, :invalid_network_value}
end
end
@doc """
Parses a BACnet address binding from BACnet 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
[
{:object_identifier, dev},
{:unsigned_integer, net},
{:octet_string, mac}
| rest
] ->
if ApplicationTags.valid_int?(net, 16) do
addr = %__MODULE__{
device_identifier: dev,
network: net,
address:
if(String.match?(mac, ~r/[a-zA-Z0-9]+/), do: String.upcase(mac, :ascii), else: mac)
}
{:ok, {addr, rest}}
else
{:error, :invalid_network_value}
end
_else ->
{:error, :invalid_tags}
end
end
@doc """
Validates whether the given address binding is in form valid.
It only validates the struct is valid as per type specification.
"""
@spec valid?(t()) :: boolean()
def valid?(
%__MODULE__{
device_identifier: %ObjectIdentifier{type: :device} = dev_ref,
network: net,
address: mac
} = _t
)
when is_integer(net) and net >= 0 and net <= 65_535 and is_binary(mac) do
ObjectIdentifier.valid?(dev_ref)
end
def valid?(%__MODULE__{} = _t), do: false
end