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 examples who_is.exs
Raw

examples/who_is.exs

# This example starts all necessary processes and scans the BACnet network for devices.
# Each device will then have their device object scanned.
# The result is printed to the output.

alias BACnet.Protocol.BACnetArray
alias BACnet.Protocol.BACnetDateTime
alias BACnet.Protocol.Services.IAm
alias BACnet.Protocol.ObjectTypes.Device
alias BACnet.Stack.Client
alias BACnet.Stack.ClientHelper
alias BACnet.Stack.Segmentator
alias BACnet.Stack.SegmentsStore
alias BACnet.Stack.Transport.IPv4Transport

# Set Log level to info to not get flooded by debug messages
# If you have discovery issues, you might want to put this after the process starts,
# to find out on which network interface we're listening
Logger.configure(level: :info)

# Start all necessary processes
# See the documentation if you need to start the transport layer on a specific network adapter

{:ok, _pid} = IPv4Transport.open(Client, name: IPv4Transport)
{:ok, _pid} = Segmentator.start_link(name: Segmentator)
{:ok, _pid} = SegmentsStore.start_link(name: SegmentsStore)

{:ok, _pid} =
  Client.start_link(
    name: Client,
    segmentator: Segmentator,
    segments_store: SegmentsStore,
    transport: IPv4Transport
  )

IO.puts("Sending broadcast WhoIs service request to BACnet network...")

# Send broadcast Who-Is service request and wait 10 seconds for all responses
{:ok, iam_responses} = ClientHelper.who_is(Client, 10_000)

IO.puts("#{length(iam_responses)} device(s) responded, scanning now all found devices...")

# Now iterate over the responses and read the BACnet Device object to display some data
for {{dest_ip, dest_port}, %IAm{} = iam} <- iam_responses do
  case ClientHelper.read_object(Client, {dest_ip, dest_port}, iam.device,
         allow_unknown_properties: true
       ) do
    {:ok, %Device{} = device} ->
      objects_count = BACnetArray.size(device.object_list)

      IO.puts(
        "Found device #{device.object_name} (ID #{iam.device.instance}) on IP address #{:inet.ntoa(dest_ip)}:#{dest_port}:\n" <>
          "\t using BACnet protocol revision #{device.protocol_revision}, \n" <>
          "\t with segmentation supported #{device.segmentation_supported} (max. #{device.max_segments_accepted} segments), \n" <>
          "\t application uses firmware version #{device.firmware_revision}, \n" <>
          "\t it offers #{objects_count} object(s), \n" <>
          "\t the device is approximately running since " <>
          NaiveDateTime.to_iso8601(
            BACnetDateTime.to_naive_datetime!(device.time_of_device_restart.datetime)
          ) <> "\n"
      )

    {:error, err} ->
      IO.puts(
        "Found device with ID #{iam.device.instance} on IP address #{:inet.ntoa(dest_ip)}:#{dest_port}, " <>
          "however reading the device object failed for reason " <> inspect(err)
      )
  end
end