View Categories

Chapter 8.4 – Automation (Logic) – Lua Scripts

The Base module lets you create and run your own scripts written in Lua. This is by far the most advanced feature in the Base module, requiring programming knowledge. Using scripts isn’t mandatory, and the vast majority of functionality in the DOMIQ system can be configured without them.


A description of the Lua language itself and its standard modules is not covered in this manual.
The Logic tab contains a built-in Lua script editor, with syntax highlighting and autocomplete for the code you enter. You can also use any other source code editor and simply paste the code into the Logic tab.
Below the editor window is a console window, where logs are displayed. In the logs, you’ll find information about any errors encountered while running the script (the line number along with a description of the error), and it also displays entries resulting from calls to the print function.


Above the script editor is a bar with buttons:

  1. Clear – Click to clear the console’s data.
  2. Copy – Copies the console’s contents to the clipboard.
  3. Stop – If this option is checked, entries in the console will not scroll automatically.
  4. Save – Click to save the script. Saving the code also reloads and runs it. This means all variable declarations, as well as lines containing function calls, will be executed.

Let’s say the editor contains the following code:

function test_fun()
     print('To jest funkcja testowa')
end

test_fun()

Every time you save the tab, the line test_fun() — a call to the test_fun function — will be executed. The result will be the console printing: To jest funkcja testowa.

A successful script reload is confirmed each time by the following console entry: INFO: SCRIPT RELOADED AT INFO: SCRIPT RELOADED AT <godzina_wykonania>

  • Restore – Click to restore the last saved version of the script.

1. General Notes
#

Familiarize yourself with the general rules for writing and executing code in the Logic tab:

  1. Variables declared outside of any function are global for the entire code in the Logic tab.
  2. Functions should not be defined as local functions using the local local.
  3. Functions and variables contained in scripts imported using the import function are global for the rest of the code in the Logic tab.
  4. Every time the Logic tab is saved, the code is compiled and the function calls contained in the code are executed.

2. Standard Modules and Functions
#

For the purposes of the Logic editor, the Lua language runs in a restricted environment. This means not all of the language’s modules are available for use. This is a security feature that prevents malicious code from being run.

Below you’ll find a list of standard Lua modules and functions that are available for use.

  1. Modules
    • table
    • string
    • math
  2. Functions:
    • print
    • time a function from theos module, called without theos. prefix.
    • pairs
    • ipairs
    • assert
    • date – a function from the os module; called without the os. prefix.
    • type
    • pcall
    • tostring
    • tonumber
    • unpack
    • setmetatable
    • getmetatable

3. Custom Modules and Functions
#

Custom modules and functions are our own implementation, extending the capabilities of the Lua language.

1. Modules
#

timer

The timer module lets you create and run asynchronous timers. An asynchronous timer is one that carries out its assigned functionality after a specified time has elapsed, without blocking the execution of the rest of the source code. The module provides two types of timers:

  1. one-shot timers
  2. periodic timers

One-shot timers

1.1. Creating a timer instance
#

A one-shot timer calls its assigned function after a specified time has elapsed. Calling the delay function from thetimer module returns a new timer instance (object):
zmienna = timer.delay(opóźnienie)

The delay is expressed in seconds and can be passed as a number or as a variable that contains a number. Fractional values can also be used.
Example usage: local some_timer = timer.delay(10) or
local some_timer = timer.delay(some_var)some_var, where some_var holds a numeric value.

At this stage, the timer has been created, but it’s in a suspended state — the countdown hasn’t started yet.
The timer should be assigned to a variable that is global from the script’s perspective. Study the examples below:

Correct declaration:

local tmr -- zadeklarowanie zmiennej na poziomie globalnym dla
całego skryptu w zakładce Logika
function some_function()
     tmr = timer.delay(30)
     tmr:on('timeout',function()
          -- kod do wykonania
     end)
     tmr:start()
end

Incorrect declaration:

function some_function()
      local tmr = timer.delay(30)
      tmr:on('timeout',function()
           -- kod do wykonania
      end)
      tmr:start()
end

In the example above, the tmr variable is local within the some_function function, so once the function finishes executing, the tmr variable will cease to exist.

1.2. Available methods: #

A timer instance provides the following methods:

  1. on(‘timeout’, function_to_execute)
    This is a method that lets you assign a function to be executed once the countdown ends.
instancja_timera:on('timeout',function()
    print("Odliczanie zakończone")
    -- tu wprowadź kod, który będzie wykonany po zakończeniu odliczania
end)

The function assigned to the timer can contain any code, e.g. calling another function, sending a command to a device in the DOMIQ system, sending a request to an external server, etc. The function passed as an argument to the on method has access to all the variables and functions in the surrounding environment, as well as to the global environment.

  1. start() – A method used to start the countdown.
  2. stop() – A method used to stop the countdown. A timer stopped using the stop method can be restarted using the again method.
  3. cancel() – Cancels the countdown, with no way of restarting it. After this method is called, the timer object is in an inactive state and must be reinitialized by creating a new instance.
  4. again() – Restarts the countdown from the beginning. This function can be called on a timer that has been stopped using the stop method. In that case, the timer restarts its countdown from the beginning. When the
    again again method is called on a timer whose countdown is already running, the countdown is reset and restarts from the beginning.
    Using the again method, you can create a timer that automatically calls itself again after completing its assigned tasks.

Examples of calling the individual methods:

instancja_timera:start()– starts the countdown;
instancja_timera:stop() – stops the countdown;
instancja_timera:cancel() – cancels the timer; the countdown cannot be restarted after this;
instancja_timera:again() – resets the countdown;

Automatically resuming a timer:


local tmr = timer.delay(10)
tmr:on('timeout',function()
    -- zadanie 1
    -- zadanie 2
    -- zadanie n
tmr:again() -- automatyczne wznowienie timera
end)
tmr:start()
1.3. Deleting a timer #

If a timer is no longer needed in the script, it should be deleted, and the contents of the variable it was assigned to should be cleared. Let’s assume the timer was assigned to the variable some_timer; in that case, call the following code:

some_timer:stop()
some_timer = nil

Periodic timers

Periodic timers carry out their countdown and assigned function repeatedly, until they are stopped.

  1. Creating a timer instance
    The same rules apply here as for one-shot timers. The periodic method is used to create periodic timers.
    zmienna = timer.periodic(opóźnienie)
    Example usage: local some_timer = timer.periodic(10), or local some_timer = timer.periodic(some_var)
    local some_timer = timer.periodic(some_var), where some_var holds a numeric value.
    The available methods are identical to those for one-shot timers, except for the again method, which is not available.

1. math #

Three additional functions are available, which we’ve added to the standard math module.

1.1 sum
A function that lets you calculate the sum of any number of elements. The arguments should be passed separated by commas.

local suma = sum(1,3,5,7)
print(suma) -- 16

1.2. avg
A function for calculating the arithmetic mean of the passed set of numbers.

local srednia = avg(1,3,5,7)
print(srednia) -- 4

1.3. round
A function that returns a number rounded to the nearest integer as its result.

local calkowita = round(16.052)
print(calkowita) -- 16
local calkowita = round(16.521)
print(calkowita) -- 17

2. tcp #

The tcp module lets you create asynchronous TCP sockets. An asynchronous socket is one that doesn’t block the rest of the code in the script while waiting to receive or send data. A TCP socket can operate as a client or as a server. Using this module, you can easily create your own integrations with external software, e.g. sending requests to external servers and handling their responses. The tcp module is widely used within the Base module’s own operating system.

2.1. Creating a socket #

To create a new socket, call the socket function from the tcp:
local sock = tcp.socket().
module:
local sock = tcp.socket().
A new socket instance has now been assigned to the sock variable.

2.2. Available methods #

A socket instance lets you call the methods described below. In all the descriptions, we assume the socket instance is assigned to the sock variable.

  1. connect(host,port) – A method used to establish a connection with the specified host, on the specified port.
    sock:connect('192.168.1.100',80)
    sock:connect('example.com',80)
  2. mode(mode)– A method used to select the operating mode. The following operating modes are available:
    • binary – Receiving a chunk of data triggers a ‘data’ event, which in turn calls the assigned handler function, passing it the received data. This is the socket’s default operating mode.
      Usage: sock:mode('binary')
    • line – Line mode. When the socket receives a \r (CR) or \n (LF) character, it triggers a 'line' event, which in turn calls the handler function, passing it the received data.
      Usage: sock:mode('line')
  3. send(data) – A method used to send data through the socket.
    sock:send(data)
  4. on(event,handler) – A method used to assign handler functions to events emitted by the socket. Handler functions can be defined inline within the on method — as a so-called anonymous function — or you can pass the name of a function defined elsewhere in the code.
    Study the two examples below:
    An anonymous function as the handler.
    sock:on('connect',function(client,addr,port)
    -- kod do wykonania
    end
    2. A function defined elsewhere in the code:
    function handle_connect(client,addr,port)
    -- kod do wykonania
    end
    sock:on('connect',handle_connect)


    A socket can emit the following types of events:
    • ‘connect’ – this event is emitted in two cases:
      • As a result of calling the connect method, when a connection with the specified host was successfully established — the socket acts as a client here.
        sock:on('connect',function()
        print('Socket Connected')
        -- reszta kodu do wykonania po nazwiązaniu połączenia
        end)
      • As a reaction to an incoming connection — the socket acts as a server here.
        function handle_connect(client,addr,port)
        -- kod do wykonania
        end
        sock:on('connect',handle_connect)

        The handler function receives, as arguments, the socket, and the address and port identifying the incoming connection.

    • IMPORTANT! All the events below should be handled inside the function assigned to handle the ‘connect‘ event. This ensures the functions are only declared and assigned once the connection has actually been established.
      • ‘data‘ – this event is emitted upon receiving a chunk of data, when the socket is operating in binary mode. The assigned handler receives the received data as an argument.

        sock:on('data',function(data)
        print('RECV DATA')
        -- kod przetwarzający odebrane dane
        end)
      • ‘line‘ – this event is emitted upon receiving a chunk of data, when the socket is operating in line mode.
        line. The assigned handler receives the received data as an argument.
        sock:on('line',function(data)
        print('RECV LINE',data)
        -- kod przetwarzający odebrane dane
        end)
      • ‘drain‘ – this event is emitted when the socket’s send buffer has been emptied. Data ends up in the send buffer when it couldn’t be sent immediately after the send method was called.
        send. This event is especially useful when you’re sending data in chunks, and the next chunk should only be sent once the previous one has been delivered.
      • ‘close‘ – an event emitted when the connection is closed. It’s emitted regardless of which side closed it. The assigned handler function receives, as an argument, a message about any errors.

        sock:on('close',function(msg)
        print("Socket Closed: "..msg)
        end)
        This event can be used, for example, to implement automatic reconnection in case the connection is closed.
      • ‘error‘ – an event emitted in the event of an error occurring on the connection. The connection will be automatically closed once an error occurs. The assigned handler function receives an error message as an argument.

        sock:on('error',function(err)
        print("Socket Error: "..err)
        end)
  5. off(event) – A method that is the opposite of the on method. It lets you cancel the handler function assigned to a given event. This is a rarely used feature, but sometimes very useful. One scenario is needing to immediately stop processing data before the connection is finally closed. In that case, calling the off method is the right solution.
    • sock:off('data')
      From this point on, the socket will no longer broadcast ‘data-type events.
  6. close() – A method used to close the connection.
    sock:close()
    If a connection is no longer needed, it’s recommended that you close it (call close()) and clear the contents of the variable the socket was assigned to (assign the value nil to the variable).
  7. bind(port) – A method that assigns the port number on which the socket will listen for incoming connections. The port number can be passed as a number, or as a variable that contains a number.
    sock:bind(1234)
    or
    local port = 1234
    sock:bind(port)
  8. listen() – A method that starts listening for incoming connections, on the port specified with the bind method.
2.3. Usage examples #

Finally, we present two examples: the first shows using a socket as a client-type connection, and the second shows using a TCP socket to implement server functionality.

SOCKET AS A CLIENT

This example shows establishing a connection with a server at the address example.com, sending an HTTP request, and handling the server’s response.

local socket
local sendRequest

function createConnection(req)
      socket = tcp.socket()
      socket:connect("example.com",80)
      socket:on('connect', function()
             tr:print('Example Server Connected')
             socket:mode('line')
             sendRequest(req)
             socket:on("close", function()
                   socket = nil
             end)
             socket:on('line', function(data)
                    print(data)
                    -- the rest of handler's code
             end)
      end)
end
sendRequest = function(req)
         if socket ~= nil then
              local qm = string.find(req,"?") -- check for a query mark
              local name,query
              if qm then
                    name,query = string.match(req,"([^?]+)%?([^?]+)") -- if
query mark present, split request string on it
                  else
                             name = req
                  end

                  local path = "http://example.com/somepath/"..name)
                  if query then
                        socket:send(string.format("GET %s?%s HTTP/
1.1\r\nHost:%s\r\nConnection: keep-alive\r\n\r\n",path, query, "example.com"))
                  else
                             socket:send(string.format("GET %s HTTP/
1.1\r\nHost:%s\r\nConnection: keep-alive\r\n\r\n",path,"example.com"))
                  end
     else
                  createConnection(req)
     end
end

sendRequest('sometestrequest')

3. udp #

The udp module lets you create asynchronous UDP sockets. An asynchronous socket is one that doesn’t block the rest of the code in the script while waiting to receive or send data. In its structure, the udp module is very similar to the tcp module. The differences between these two modules stem from the differences between the TCP and UDP protocols — UDP is a stateless protocol. So you won’t find a connect method here, nor events for handling incoming connections.
A UDP socket can operate as a client or as a server. Using this module, you can easily create your own integrations with external software, e.g. sending requests to external servers and handling their responses.

3.1. Creating a socket #

To create a new socket, call the socket function from the udp module:
local sock = udp.socket().

A new socket instance has now been assigned to the sock variable.

3.2. Available methods #

A socket instance lets you call the methods described below. In all the descriptions, we assume the socket instance is assigned to the sock variable.

  1. send(data) – A method used to send data through the socket.
    sock:send(data)
  2. on(event,handler) – A method used to assign handler functions to events emitted by the socket. Handler functions can be defined inline within the on method — as a so-called anonymous function — or you can pass the name of a function defined elsewhere in the code. Study the two examples:
    1. An anonymous function as the handler.
    sock:on('message',function(data,addr,port)
    -- kod do wykonania
    end)
    2. A function defined elsewhere in the code:
    function handle_message(data,addr,port)
    -- kod do wykonania
    end
    sock:on('message',handle_ message)


    A socket can emit the following types of events:
    • ‘message’ – this event is emitted upon receiving a chunk of data. The assigned handler function receives, as arguments, the received data, and the sender’s address and port number.

      sock:on('message',function(data,addr,port)
      print('RECV DATA')
      -- kod przetwarzający odebrane dane
      end)
    • ‘error‘ – an event emitted in the event of an error. The assigned handler function receives an error message as an argument.

      sock:on('error',function(err)
      print("Socket Error: "..err)
      end)
  3. off(event) – A method that is the opposite of the on method. It lets you cancel the handler function assigned to a given event.
    • sock:off(‘message’)
      From this point on, the socket will no longer broadcast ‘message’-type events.
  4. bind(port) – A method that assigns the port number on which the socket will listen for incoming data. The port number can be passed as a number, or as a variable that contains a number.

    sock:bind(1234)
    or
    local port = 1234
    sock:bind(port)
  5. addMembership(adres) – A method that lets you join a multicast group.
  6. dropMembership(adres) – A method that lets you leave a multicast group.
3.3. Usage examples #

SOCKET AS A SERVER

In the example below, the socket will listen on port 1234. Upon receiving data, it will send the same data back (an echo server).

sock = udp.socket()
sock:bind(1234)
sock:on('message',function(data,addr,port)
       print('RX',addr,port)
       print(data..'\n')
       sock:send(data,addr,port)
end)
sock:on('error',function(err)
      print('ERROR',sock,err)
end)
print("Listening",sock)

SOCKET AS A CLIENT

This example shows how to send data to any host.

sock = udp.socket()
sock:on('message',function(data,addr,port)
      print('RX',addr,port)
      print(data..'\n')
      -- the rest of handler's code
end)

sock:on('error',function(err)
      print('ERROR',sock,err)
end)

function sendData(data,addr,port)
      sock:send(data,addr,port)
end

sendData("some data",'192.168.1.100',44544)

4. webapi #

A module that lets you handle HTTP requests, from code in the Logic tab, via the HTTP server built into the Base module. This way, you can create your own HTTP API to integrate the Base module with any software. Each request address can have its own handler function defined for it.

  • public(name,handler) The public method is used to register handler functions for requests that do not require authorization.
    name – a name identifying the request
    handler – the name of the function called to handle the request.
    The request must always follow this structure:
    http://<host>/call/public?vm=LOGIC&call=&<nazwa_żądania>&<dowolne_zmienne>

Example:

http://192.168.1.30/call/public?
vm=LOGIC&call=examplerequest&message=somemessage&value=100

The above request will be received with the name examplerequest , along with two additional parameters: message with the value somemessage , and value with the value 100.

Code in the Logic tab:

function handleExampleRequest(params)
         -- params to tablica parametrów przekazanych w żądaniu
         print('message: '..params.message)
         print('value: '..params.value)
end
webapi.public('examplerequest',handleExampleRequest)


The above call to webapi.public registered the request under the name examplerequest and assigned the handler function handleExampleRequestto it. When called, the handler function receives, as an argument, a table of the parameters that were used in the request. If there were no additional parameters in the request, the function will receive an empty table.

  • secure(name,handler)
    The secure method works analogously to the publicmethod; however, it requires the request to be authorized using the configurator’s login and password. The authorization data should be sent in the Authorizationheader. The data can be encoded using the Basic or Digest method.
    Using the secure method only differs in one part of the request — instead of call/public
    call/secure. you need to call webapi.secure instead of webapi.public.
    Referring back to the example for the publicmethod, we then have:
    http://192.168.1.30/call/vm=LOGIC&call=examplerequest&message=somemessage&value=100
    and
    webapi.secure(‘examplerequest’,handleExampleRequest)

2. Functions
#

2.1. use #

This function is used to declare objects that let you interact with a specified identifier in the Base module’s state. Objects created this way can be used throughout the entire Logic tab, passed between functions, or used as global variables.

1. Declaring an object
#

The general syntax is as follows:
local somevariable = use'identyfikator', where identyfikator should be replaced with a specific identifier from the state.
local somevariable = use'LCN.output.0.10.1'

IMPORTANT! The object automatically creates a subscription to receive events about changes to the state of the specified identifier. This enables programmatic handling of events — see the onchange method.

2. Object properties
#
  1. value – The current value of the identifier assigned to the object.
  2. new – The new value of the identifier, received via the onchange method.
  3. old – The last known value of the identifier. This property is set when the onchange method is called. It makes it easier to compare the identifier’s current and previous values.
  4. age – The value, in seconds, since the state last changed.
3. Available methods #

For the purposes of describing the available methods, we’ll assume the object created in point 1 — an identifier referring to dimmable output No. 1 in the LCN module at address 10.

  1. set(value)– A method that lets you assign a new value to the identifier.
    The value passed to the set function can be anything; what matters is that it matches the type of identifier.
    General syntax: obj:set(value)
    • somevariable:set(100)
      The above call to the set method involves two actions:
      1. Assigns the value 100 to the identifier LCN.output.0.10.1 in the Base module’s state.
      2. Sends a command with the value 100 to dimmable output No. 1 in the LCN module.
  2. get() – A method that returns the current state of the identifier assigned to the object.
    • local currentValue = somevariable:get()
      print(currentValue) --> 100
  3. channel()– This method returns the name of the identifier assigned to the object.
    • local name = somevariable:channel()
      print(name) --> LCN.output.0.10.1
  4. onchange(new,old)– A method that lets you assign a handler function, which will be called every time the identifier changes its state. The handler function receives, as arguments, the identifier’s new state, and its value from before the change.
    • Both of the notations below are equivalent:
      function somevariable:onchange(new,old)
      print(“New value: “..new)
      print(“Old value: “..old)
      end

      somevariable.onchange = function(somevariable,new,old)
      print(“New value: “..new)
      print(“Old value: “..old)
      end
  5. ontick() – This method lets you assign a handler function that will be called every second. This is a feature similar to timer.periodic.
    • Both of the notations below are equivalent:
      function somevariable:ontick()
      print(“Ticking”)
      end

      somevariable.ontick = function()
      print(“Ticking”)
      end

2.2. age #

This function returns the time, expressed in seconds, since the given identifier’s state last changed. The function accepts, as an argument, a string containing the identifier’s name, or an object created using the usemethod.

Identifier as a string
local age = age('LCN.output.0.10.1')
print(age) --> 1234

Passing an objectlocal somevariable = use'LCN.output.0.10.1'
local age = age(somevariable)
print(age) --> 1234

2.4. systime #

This function returns the time since the Base module was started. This is a convenient tool for measuring the time elapsed between two events in the system, since the systime function, unlike the standard timefunction, returns time with a precision greater than whole seconds.

Przykład pomiaru czasu pomiędzy dwoma miejscami kodu
function veryResourceHungryFunction()
    -- some very time consuming operations go here
end


local start = systime()
veryResourceHungryFunction()
local end = systime()
print("Duration: "..end - start) --> 8.8125

2.5. command #

This function lets you send a command or broadcast an event with a specified value. Here, you’re required to use the C. (command) or E. (event) prefix
Two types of function calls are allowed; both are equivalent, though the first method is more optimal in terms of performance.


command(komenda/zdarzenie,wartość) or
command(komenda/zdarzenie=wartość)

  1. Turning on relay No. 1, for example, on the Satel INT-IORS module at address 3.

    command('C.EXP.output.3.1,1)
  2. Driving dimmable output No. 2 in the LCN module at address 10 to 100%.

    command('C.LCN.output.0.10.2,100)
  3. Broadcasting an event to switch the visualization screen in the Remote app.
    For the purposes of this example, let’s assume the screen is named test. When this action is executed, every Remote app currently displaying the visualization will show the screen named test.
    command('E.DISPLAY.screen,"test")

2.6. import #

This function is used to import scripts uploaded in the Resources tab into the code in Logic. The functions and variables of the imported script become global throughout the entire code in the Logic tab.

  • import’script_name’

2.7. set #

A function that lets you set the state of any identifier. The difference between the set function and the set method of an object created using the use function is as follows: the setfunction only sets the identifier’s value in the Base module’s state table, without sending a command to the device behind that identifier. The object’s set method, on the other hand, does both. So, if you want to change an identifier that is only a virtual variable, e.g. a VAR-type variable, it’s enough to use the setfunction.

  1. Changing the state of the VAR.test variable to the value 100.

    set('VAR.test',100)
  2. Compare the differences between the setfunction and the object’s set method

    set('LCN.output.0.10.1',100) -- ustawia stan na wartość 100

    local somevariable = use'LCN.output.0.10.1'
    somevariable:set(100) --> ustawia stan i wysyła komendę do
    modułu LCN

2.8. get #

This function returns the current state of the specified identifier.

  • local value = get(‘LCN.output.0.10.1’)
    print(value) –> wypisuje aktualną wartość wyjścia ściemnianego

2.9. set_attr #

This function is used to change the value of attributes for any identifier in the Base module’s state. Every identifier has a description attribute (description) and an ageattribute (time since the last change).
The function takes three arguments: the identifier’s name, the attribute’s name, the attribute’s new value

  • set_attr("LCN.output.0.10.1","description","lampa sufit")

2.10. get_attr #

This function returns the value of the specified identifier attribute. You need to pass the identifier’s name and the attribute’s name to the function.

  • local description = get_attr("IDS.input.10","description")

Powered by BetterDocs