Example: LogicMachine as gateway between RTI and KNX

Task

How to realize bi-directional communication between RTI T2-Cs+ universal controller and KNX bus devices?

RTI XP-6 driver

Data exchange between XP-6 controller and LogicMachine is realized with the driver (LogicMachine.rtidriver). In driver settings you need to specify IP address of LogicMachine and set up group addresses which status will be displayed on remotes.

Function for LogicMachine

The following function should be added in Scripting -> Common functions.

Source code    
  1. function updatestring(number)
  2. -- update of string
  3. if number == '1' then
  4. local value = grp.getvalue('3/0/1') —temperature request of the room
  5. local t = tostring(value)
  6. return string.format('%.1f', t) —converting to string to show on display
  7. End
  8. -- numbers of strings are set in interval from 1 to 250
  9. end
  10.  
  11. function updatebutton(number)
  12. return false
  13. end
  14.  
  15. function pushbutton(number)
  16. -- processing of button press
  17. if number == '1' then
  18. local value = grp.getvalue('3/0/2')
  19. if value < 37 then value = value + 0.1 — Chnage room temperature end grp.write('3/0/4', value) elseif number == '2' then local value = grp.getvalue('3/0/2') if value > 7 then
  20. value = value - 0.1 -- Chnage room temperature
  21. end
  22. grp.write('3/0/4', value)
  23. end
  24. -- Buttons numbers are set in interval from 1 to 250
  25. end
  26.  
  27. function sendstring(address,value)
  28. local t = tostring(value)
  29. local number = nil
  30. if address == '3/0/1' then
  31. number = 1
  32. end
  33. if number then
  34. udpsend("STRING",number,"STRING",string.format('%.1f', t))
  35. end
  36. end
  37.  
  38. function sendaddress(address,datatype,value)
  39. udpsend("WRITEADDRESS",address,datatype,value)
  40. end
  41.  
  42. function udpsend(command,address,datatype,value)
  43. require('socket')
  44.  
  45. local client = socket.udp()
  46. local message = '#RTI_Control#@'..command..'@%%'..address..'%%$'..datatype..'$~'..value..'~\r\n'
  47.  
  48. -- upd client ready, send to local port 12345
  49. if client then
  50. client:sendto(message, '127.0.0.1', 12345)
  51. client:close()
  52. end
  53. end

Resident script – server realization

Add the following script with Sleep interval “0”

Source code    
  1. if not ready then
  2. require('copas')
  3.  
  4. -- list of client sockets
  5. clients = {}
  6.  
  7. -- incoming data handler
  8. function datahandler(sock, data)
  9. local ip, port
  10. ip, port = sock:getpeername()
  11.  
  12. _,_,command,address,datatype,value = string.find(data, "^#RTI_Control#@([^@]+)@%%([^%%]+)%%%$([^%$]*)%$~([^~]*)~")
  13.  
  14. -- send reply
  15. if value == 'HELLO' then
  16. sock:send('#RTI_Control#@HEARTBEAT@%SYSTEM%$STRING$~HELLO TO YOU~\r\n')
  17. end
  18. if command == 'BUTTON' then
  19. --Button pressed. Call for function
  20. pushbutton(address, value)
  21. end
  22. if command == 'WRITEADDRESS' then
  23. --Write received value into group address
  24. grp.write(address, value)
  25. end
  26. if command == 'READSTRING' then
  27. --String value request
  28. value = updatestring(address)
  29. if value ~= nil then
  30. udpsend("UPDATESTRING",address,"STRING",value)
  31. end
  32. end
  33. if command == 'READBUTTON' then
  34. --Button status request
  35. value = updatebutton(address)
  36. if value ~= nil then
  37. udpsend("UPDATEBUTTON",address,"BOOLEAN",value)
  38. end
  39. end
  40. if command == 'READADDRESS' then
  41. --Group address status request
  42. value = grp.getvalue(address)
  43. if value ~= nil then
  44. if type(value) == 'boolean' and datatype == 'BOOLEAN' then
  45. udpsend("UPDATEADDRESS",address,"BOOLEAN",(value and 1 or 0))
  46. elseif type(value) == 'integer' and datatype == 'INTEGER' then
  47. udpsend("UPDATEADDRESS",address,"INTEGER",value)
  48. end
  49. end
  50. end
  51. end
  52.  
  53. -- connection handler
  54. function connhandler(sock)
  55. -- enable keep-alive to check for disconnect events
  56. sock:setoption('keepalive', true)
  57.  
  58. local ip, port, data, err, id
  59.  
  60. -- get ip and port from socket
  61. ip, port = sock:getpeername()
  62.  
  63. -- client id
  64. id = string.format('%s:%d', ip, port)
  65.  
  66. alert('[server] connection from %s', id)
  67.  
  68. -- save socket reference
  69. clients[ id ] = sock
  70.  
  71. -- main reader loop
  72. while true do
  73. -- wait for single line of data (until \n, \r is ignored)
  74. data, err = copas.receive(sock, '*l')
  75.  
  76. -- error while receiving
  77. if err then
  78. alert('[server] closed connection from %s:%d', ip, port)
  79. -- remove socket reference
  80. clients[ id ] = nil
  81. return
  82. end
  83.  
  84. -- handle data frame
  85. datahandler(sock, data)
  86. end
  87. end
  88.  
  89. -- bind to port 6789
  90. tcpserver = socket.bind('*', 6789)
  91.  
  92. -- error while binding, try again later
  93. if not tcpserver then
  94. os.sleep(5)
  95. error('[server] error: cannot bind')
  96. end
  97.  
  98. -- set server connection handler
  99. copas.addserver(tcpserver, connhandler)
  100.  
  101. -- create local udp server on port 12345
  102. udpserver = socket.udp()
  103. udpserver:setsockname('127.0.0.1', 12345)
  104. udpserver:settimeout(0.1)
  105.  
  106. ready = true
  107. end
  108.  
  109. -- perform server tasks for one second (5 x (0.1 + 0.1))
  110. for i = 1, 5 do
  111. message = udpserver:receive()
  112.  
  113. -- got message from udp, send to all active clients
  114. if message then
  115. --alert(message)
  116. for id, sock in pairs(clients) do
  117. sock:send(message .. '\r\n')
  118. end
  119. end
  120.  
  121. copas.step(0.1)
  122. end

The following commands can be received from RTI controller:

  • “BUTTON” – controller informs that the button is pressed. In this example the function pushbutton(address, value) will be called. Button numbers (address) are stored in interval from 1 to 250. Value=1 – button is pressed; value=0 – button is released
  • “WRITEADDRESS” – controller informs that the group address should be changed
  • “READSTRING” – request fro string content. In this example string is returned by function updatestring(address)
  • “READBUTTON” – request for button status. In this example button status is set from function updatebutton(address)
  • “READADDRESS” – request for address status

Event program

Event-based script will be based on group address 1/0/1. In case of object ON (lamp), the telegram “ON” will be sent to all controllers which are currently connected to LogicMachine. In case of object OFF, the telegram “OFF” will be sent.

Source code    
  1. address = event.dst
  2. value = knxdatatype.decode(event.datahex, dt.bool)
  3. sendaddress(address,'BOOLEAN',(value and 'ON' or 'OFF'))

The following event based program will track object 1/1/1. In case of temperature change in the room, the string with current temperature will be sent to all controllers which are currently connected to LogicMachine.

Source code    
  1. address = event.dst
  2. local value = knxdatatype.decode(event.datahex, dt.float16)
  3. alert('address = %s, value = %s', address, value)
  4. local t = tostring(value)
  5. alert('t = %s', value)
  6. sendstring(address, string.format('%.1f', t))

Demo driver of RTI and further information

Demo driver can be downloaded here.
In case of interest to receive full version of driver, please contact the creator of this example Alexey Krasovkiy
e-mail: kao@itservice-vrn.ru

Please note that driver licence is mapped to a specific controller. The count of drivers on one controller is unlimited thus count of group addresses, strings and buttons are not limited. For additional functionality please contact the developer.