BACnet.Stack.ClientHelper (bacstack v0.1.0-dev.1)

Copy Markdown View Source

BACnet stack client helper functions for executing commands/queries.

Summary

Functions

Sends an I-Am service request to the destination, or optionally using :broadcast (or the real broadcast address) as local broadcast.

Read a BACnet object from a remote BACnet device and transform it into an object.

Read a single property from a remote BACnet object and transform the value.

Read multiple properties from a remote BACnet object at once and transform each value.

Scan the given device for available objects and read all objects. A map of objects will be returned on success.

Send a (UTC) Time Synchronization service APDU to the destination.

Subscribes for COV notification for a remote BACnet object property.

Sends a Who-Is service request to the network (local broadcast). The I-Am responses will be collected and returned.

Write to a single property of a remote BACnet object.

Write to multiple properties of a remote BACnet object.

Functions

i_am(server, destination, device, vendor_id, opts \\ [])

@spec i_am(
  GenServer.server(),
  term() | :broadcast,
  BACnet.Protocol.ObjectIdentifier.t(),
  non_neg_integer(),
  Keyword.t()
) :: :ok | {:error, term()}

Sends an I-Am service request to the destination, or optionally using :broadcast (or the real broadcast address) as local broadcast.

See also BACnet.Protocol.Services.IAm.

The Client.send/4 options are available.

read_object(server, destination, object, opts \\ [])

Read a BACnet object from a remote BACnet device and transform it into an object.

The required properties are always as a bare minimum read, only more properties can be read, never less.

The value is casted through the BACnet.Protocol.ObjectsUtility module based on the object modules. As such object types or properties that are not supported, will fail.

If you want to read a device object and don't know the proper device instance number, you can use 4_194_303 as instance number. By the BACnet specification that instance number will be treated by the remote BACnet device as if the instance number was locally correctly matched.

The following options are available:

properties and read_level are mutually excluse. If both are given, properties takes precedence.

read_property(server, destination, object, property, array_index \\ nil, opts \\ [])

Read a single property from a remote BACnet object and transform the value.

The value is casted through the BACnet.Protocol.ObjectsUtility module based on the object modules. As such object types or properties that are not supported, will fail, unless you specify the raw options, which will give you the Encoding struct (or list of) to handle yourself. Array indexes of 0 will return the array size as {:ok, non_neg_integer()}, if successfully read.

If you want to read a device object's property without needing to know before hand which instance number, you can use 4_194_303 as instance number. By the BACnet specification that instance number will be treated by the remote BACnet device as if the instance number was locally correctly matched.

The following options are available:

read_property_multiple(server, destination, object, properties, opts \\ [])

Read multiple properties from a remote BACnet object at once and transform each value.

The values are casted through the BACnet.Protocol.ObjectsUtility module based on the object modules. As such object types or properties that are not supported, will fail, unless you specify the raw options, which will give you a list of ReadAccessResults to handle yourself.

The following options are available:

reinitialize_device(server, destination, state \\ Constants.macro_assert_name(:reinitialized_state, :warmstart), password \\ nil, opts \\ [])

@spec reinitialize_device(
  GenServer.server(),
  term(),
  BACnet.Protocol.Constants.reinitialized_state(),
  String.t() | nil,
  Keyword.t()
) :: :ok | {:error, BACnet.Protocol.apdu()} | {:error, term()}

Send a Reinitialize-Device service request to a remote BACnet device.

Password must be an ASCII string between 1 to 20 characters, inclusive, or nil.

The following options are available:

scan_device(server, destination, device, opts \\ [])

Scan the given device for available objects and read all objects. A map of objects will be returned on success.

The device object identifier must be supplied, so that the correct device object properties can be read. This must be done, in case the destination has multiple "virtual" BACnet devices to select the correct device. If you know the destination has only one device object, then you can use 4_194_303 as instance number. By the BACnet specification that instance number will be treated by the remote BACnet device as if the instance number was locally correctly matched.

The scan process is parallelized through Task.async_stream/3 and thus the invoke_id is automatically being set. Since this implementation simply uses invoke_id in the range of 0..max_concurrency-1, it would be safest when the BACnet.Stack.Client implementation manages and overrides the invoke_id, so that an user does not have to care about possible collisions. The current "default" implementation of BACnet.Stack.Client does manage invoke_ids, but it can be deactivated, so care must be exercised if it is done. You need to be aware to not invoke/have parallel other requests to the same destination, as the invoke_id could be duplicated.

The values are casted through the BACnet.Protocol.ObjectsUtility module based on the object modules. As such object types or properties that are not supported, will fail the operation.

The following options are available:

exit_on_error and ignore_errors are mutually excluse. ignore_errors takes precedence, if set to true.

send_time_synchronization(server, destination \\ :broadcast, opts \\ [])

@spec send_time_synchronization(
  GenServer.server(),
  term(),
  Keyword.t()
) :: :ok | {:error, term()}

Send a (UTC) Time Synchronization service APDU to the destination.

:broadcast will be resolved to the local broadcast address.

The following options are available:

  • All options from BACnet.Stack.Client.send/4.
  • All options from BACnet.Protocol.Services.TimeSynchronziation.to_apdu/2 respectively BACnet.Protocol.Services.UtcTimeSynchronziation.to_apdu/2.
  • datetime: DateTime.t() - Optional. The timestamp to use for synchronization. It will be automatically shifted to UTC, if necessary. If omitted, DateTime.now!/1 will be used with Time Synchronization - if the default timezone is "Etc/UTC", then UTC Time Synchronization will be used. The utc option overrides the behaviour of the default timezone - you may use a non-UTC timezone and still be able to use UTC.
  • utc: boolean() - Optional. Whether to use UTC Time Synchronization.

subscribe_cov_property(server, destination, object, property, opts \\ [])

@spec subscribe_cov_property(
  GenServer.server(),
  term(),
  BACnet.Protocol.ObjectIdentifier.t(),
  BACnet.Protocol.Constants.property_identifier(),
  Keyword.t()
) :: :ok | {:error, BACnet.Protocol.apdu()} | {:error, term()}

Subscribes for COV notification for a remote BACnet object property.

When using confirmed COV notifications, the remote BACnet device requires you to send confirmations of the reception (BACnet.Protocol.Services.SimpleACK) - this is not done automatically.

The following options are available:

  • All options from BACnet.Stack.Client.send/4.
  • All options from BACnet.Protocol.Services.SubscribeCovProperty.to_apdu/2.
  • confirmed: boolean() - Optional. Request confirmed COV notifications. By default, COV notifications are requested to be unconfirmed.
  • cov_increment: float() - Optional. The COV increment to use for float properties.
  • lifetime: non_neg_integer() | nil - Optional. The COV subscription lifetime to use in seconds (defaults to 3600). To unsubscribe, use nil.

  • pid: non_neg_integer() - Optional. The process identifier to use. By default, this will be calculated from the caller PID (node bits 0-3 << 28 + pid_number << 13 + pid_serial).

who_is(server, timeout \\ 5000, opts \\ [])

@spec who_is(GenServer.server(), pos_integer(), Keyword.t()) ::
  {:ok, [{source_address :: term(), BACnet.Protocol.Services.IAm.t()}]}
  | {:error, term()}

Sends a Who-Is service request to the network (local broadcast). The I-Am responses will be collected and returned.

See also BACnet.Protocol.Services.WhoIs.

By default, it will collect all responses received until timeout. By using max opts, one can tell the function how many to receive and then stop prematurely. Either max or timeout will stop the collecting. Timeout must be minimum 10ms.

This function will by default spawn a new task and subscribe for BACnet notification messages and afterwards unsubscribe. This behaviour can be disabled through no_subscribe opts.

The following options are available, in addition the Client.send/4 options:

  • apdu_destination: term() - Optional. Overrides the APDU destination address.
  • high_limit: pos_integer() - Optional. The maximum BACnet device ID for the Who-Is query.
  • low_limit: pos_integer() - Optional. The minimum BACnet device ID for the Who-Is query.
  • max: pos_integer() - Optional. The maximum amount of IAm responses to collect.
  • no_subscribe: boolean() - Optional. Whether to spawn a new task.

write_property(server, destination, object, property, value, opts \\ [])

Write to a single property of a remote BACnet object.

Either the actual value of the property can be given and then the value will be automatically encoded through BACnet.Protocol.ObjectsUtility. Or an Encoding struct (or list of) can be given, which will be used directly without validation.

The following options are available:

write_property_multiple(server, destination, object, properties, opts \\ [])

@spec write_property_multiple(
  GenServer.server(),
  term(),
  BACnet.Protocol.ObjectIdentifier.t(),
  %{
    optional(property_identifier) =>
      value | {array_index :: non_neg_integer(), value}
  }
  | [
      {property_identifier, value | {array_index :: non_neg_integer(), value}}
      | {property_identifier, array_index :: non_neg_integer(), value}
      | BACnet.Protocol.AccessSpecification.Property.t()
    ],
  Keyword.t()
) ::
  :ok
  | {:error, BACnet.Protocol.Services.Error.WritePropertyMultipleError.t()}
  | {:error, BACnet.Protocol.apdu()}
  | {:error, term()}
when property_identifier:
       BACnet.Protocol.Constants.property_identifier() | non_neg_integer(),
     value:
       term()
       | BACnet.Protocol.ApplicationTags.Encoding.t()
       | [term() | BACnet.Protocol.ApplicationTags.Encoding.t()]

Write to multiple properties of a remote BACnet object.

Either the actual value of the property can be given and then the value will be automatically encoded through BACnet.Protocol.ObjectsUtility. Or an Encoding struct (or list of) can be given, which will be used directly without validation.

Prioritized property access is not possible with this function. Use write_property/6 instead if you need to write to a specific priority. You can however write to specific array indexes.

The following options are available: