Skip to content

BACnet Simulator Lua Script Tutorial

This tutorial will outline how to write basic lua scripts to control the simulator.

Incrementing Present Values

This section will cover how to size object instances and add some logic to update present value properties in both the Run and Update functions.

Create Object Instances

function Run()
  bacnet.createAnalogInputs(1)
  bacnet.createAnalogValues(1)
  bacnet.createAnalogOutputs(1)

  bacnet.setAnalogInputName(0, "ANALOG_INPUT_0")
  bacnet.setAnalogValueName(0, "ANALOG_VALUE_0")
  bacnet.setAnalogOutputName(0, "ANALOG_OUTPUT_0")
end

This code will create 1 instance of Analog Input, Analog Output and Analog Value objects and set the object names for each of them.

Please note that object instances are indexed from 0 and so the 1st instance of an object will be referenced using 0.

Add Some Logic to the Run Function

As the Run function operates on its own thread separate from the main simulator thread, it can be kept running using the isBacnetRunning function with a while loop.

local increment = 0
local priority = 1
local mod = 15

function sleep(time)
    local duration = os.time() + time
    while os.time() < duration do end
end

function Run()
  bacnet.createAnalogInputs(1)
  bacnet.createAnalogValues(1)
  bacnet.createAnalogOutputs(1)

  bacnet.setAnalogInputName(0, "ANALOG_INPUT_0")
  bacnet.setAnalogValueName(0, "ANALOG_VALUE_0")
  bacnet.setAnalogOutputName(0, "ANALOG_OUTPUT_0")

  while isBacnetRunning() do
    increment = math.fmod (increment, mod) + 1
    bacnet.setAnalogValuePresentValue(0, increment, priority)
    sleep(1)
  end
end

Every second this logic will increment a value by 1, setting the present value of instance 0 of the Analog Value object. Once the value reaches 15 it will wrap around to 1 again. As lua does not provide a standard sleep function, one must be included.

Add Some Logic to the Update Function

The Update function will be run on every process cycle of the simulator. As this is called from the main simulator thread, there should be no logic in here that blocks execution, ie sleeps etc.

function Update()
  value = bacnet.getAnalogOutputPresentValue(0)
  bacnet.setAnalogInputPresentValue(0, value, priority)
end

This logic will mirror whatever value that was written to the present value of the Analog Output object to the Analog Input instance.

Full code

local increment = 0
local priority = 1
local mod = 15

function sleep(time)
    local duration = os.time() + time
    while os.time() < duration do end
end

function Run()
  bacnet.createAnalogInputs(1)
  bacnet.createAnalogValues(1)
  bacnet.createAnalogOutputs(1)

  bacnet.setAnalogInputName(0, "ANALOG_INPUT_0")
  bacnet.setAnalogValueName(0, "ANALOG_VALUE_0")
  bacnet.setAnalogOutputName(0, "ANALOG_OUTPUT_0")

  while isBacnetRunning() do
    increment = math.fmod (increment, mod) + 1
    bacnet.setAnalogValuePresentValue(0, increment, priority)
    sleep(1)
  end
end

function Update()
  value = bacnet.getAnalogOutputPresentValue(0)
  bacnet.setAnalogInputPresentValue(0, value, priority)
end

Generating Events

This section will demonstrate how to generate mock events and direct them to your BACnet client.

Create Notification Class Object Instance and Register Recipient

function Run()
  bacnet.createNotificationClasses(1)
  bacnet.registerRecipient (101, 0, false)
end

This code will create 1 instance of the Notification Class object and then register a BACnet client with ID 101 to the recipient list of this Notification Class instance. Any events sent to this device will be unconfirmed.

Please note that object instances are indexed from 0 and so the 1st instance of an object will be referenced using 0.

Add Some Logic to Generate Events

The Update function will be used to generate the mock events for every type supported on a 15 second interval.

local waitDuration = 15
local lastUpdate = os.time()

function Update()
  if os.time() - lastUpdate > waitDuration then
    bacnet.generateEvent(EventType.CHANGE_OF_BITSTRING, 0)
    bacnet.generateEvent(EventType.CHANGE_OF_STATE, 0)
    bacnet.generateEvent(EventType.CHANGE_OF_VALUE, 0)
    bacnet.generateEvent(EventType.COMMAND_FAILURE, 0)
    bacnet.generateEvent(EventType.FLOATING_LIMIT, 0)
    bacnet.generateEvent(EventType.OUT_OF_RANGE, 0)
    bacnet.generateEvent(EventType.CHANGE_OF_LIFE_SAFETY, 0)
    bacnet.generateEvent(EventType.BUFFER_READY, 0)
    bacnet.generateEvent(EventType.UNSIGNED_RANGE, 0)
    bacnet.generateEvent(EventType.ACCESS_EVENT, 0)
    bacnet.generateEvent(EventType.DOUBLE_OUT_OF_RANGE, 0)
    bacnet.generateEvent(EventType.SIGNED_OUT_OF_RANGE, 0)
    bacnet.generateEvent(EventType.UNSIGNED_OUT_OF_RANGE, 0)
    bacnet.generateEvent(EventType.CHANGE_OF_CHARACTERSTRING, 0)
    bacnet.generateEvent(EventType.CHANGE_OF_TIMER, 0)
    lastUpdate = os.time()
  end
end

These events will be routed through instance 0 of the Notification Class that was created above. As the BACnet device client with ID 101 has been registered in the recipient list, these events will be sent to that device.

Full code

function Run()
  bacnet.createNotificationClasses(1)
  bacnet.registerRecipient (101, 0, false)
end

local waitDuration = 15
local lastUpdate = os.time()

function Update()
  if os.time() - lastUpdate > waitDuration then
    bacnet.generateEvent(EventType.CHANGE_OF_BITSTRING, 0)
    bacnet.generateEvent(EventType.CHANGE_OF_STATE, 0)
    bacnet.generateEvent(EventType.CHANGE_OF_VALUE, 0)
    bacnet.generateEvent(EventType.COMMAND_FAILURE, 0)
    bacnet.generateEvent(EventType.FLOATING_LIMIT, 0)
    bacnet.generateEvent(EventType.OUT_OF_RANGE, 0)
    bacnet.generateEvent(EventType.CHANGE_OF_LIFE_SAFETY, 0)
    bacnet.generateEvent(EventType.BUFFER_READY, 0)
    bacnet.generateEvent(EventType.UNSIGNED_RANGE, 0)
    bacnet.generateEvent(EventType.ACCESS_EVENT, 0)
    bacnet.generateEvent(EventType.DOUBLE_OUT_OF_RANGE, 0)
    bacnet.generateEvent(EventType.SIGNED_OUT_OF_RANGE, 0)
    bacnet.generateEvent(EventType.UNSIGNED_OUT_OF_RANGE, 0)
    bacnet.generateEvent(EventType.CHANGE_OF_CHARACTERSTRING, 0)
    bacnet.generateEvent(EventType.CHANGE_OF_TIMER, 0)
    lastUpdate = os.time()
  end
end