This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm that you accept these cookies being set.

Processing KNX Addresses
#1
Hello to all, 

I'm new to the forum and lua scripting. I read some examples but didn't find anything suitable for my case.

I need to check multiple KNX addresses to determine a state change from 0 to 1. Specifically, I have the following logic: group/function/device. The number of devices varies for each group, but always starts at 11 and increases by 10.
 
If the state of address 1/2/11 or any of the addresses 1/2/X becomes 1, I need to change the state of all devices of type 1/3/Y and 1/5/Z. For example, if the address 1/2/11 is 1, then the addresses 1/3/11, 1/3/21, 1/3/31...(up to the number of objects entered) will change to 0, and the addresses 1/5/18, 1/5/28, 1/5/38...(up to the number of objects entered) will also change to 0. In other words, if any device from group 1 and function 2 has a state of 1, then all devices with functions 3 and 5 will change to a state of 0. If all devices from group 1 with function 2 have a state of 0 and control is allowed (the control address is X/2/0 for each group), then all devices with functions 3 and 5 will change to a state of 1.
 
What is the best practice for implementing this logic? I assume a resident script with a sleep of 0 is necessary, but with tags or first checking if the addresses exist, then using a for loop. Which solution will be most efficient and reliable for operation?
 
It is important to note that the groups range from 1 to 60, and the devices range from 11 to 161, always increasing by 10.
Reply
#2
Use an event script mapped to a tag that all the input objects also have. Don't tag output objects with the same tag or you will get an infinite loop.

event.dstraw variable can be used to calculate output group addresses.
This example will write an inverse binary value to a group address with the next middle address (A/B/C -> A/B+1/C).
Code:
123
value = event.getvalue() addr = event.dstraw + 256 grp.write(addr, not value)

You also need to clearly define your algorithm. If you say there are 60 input groups then there will be an overlap when writing to the output groups.
Reply
#3
This would lead to the possibility of many errors, can't it be done with a resident script that monitors states and processes events? I tried to write something similar, but apparently I don't understand the system's workings very well and it doesn't work for me.
Code:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
local function object_exists(obj_address)   local obj = grp.find(obj_address)   return obj ~= nil end -- table with the addresses of function_one local function_one = {} for i = 1, 60 do   for j = 11, 241, 10 do     local function_one_address = i .. "/2/" .. j     if object_exists(function_one_address) then       function_one[(i - 1) * 24 + (j - 11) / 10 + 1] = function_one_address     end   end end -- table with the addresses of group_numbers local group_numbers = {} for i = 1, 60 do   for j = 11, 241, 10 do     local group_numbers_address = i .. "/3/" .. j     if object_exists(group_numbers_address) then       group_numbers[(i - 1) * 24 + (j - 11) / 10 + 1] = group_numbers_address     end   end end -- create client client = require('localbus').new(0.1) local function update_group_numbers_for_group(group_number)   local has_true_function_one = false   for j, function_one_item in ipairs(function_one) do     if tonumber(function_one_item:match("(%d+)/2/%d+")) == group_number then       if grp.getvalue(function_one_item) == 1 then         has_true_function_one = true         break       end     end   end   for j, group_number_item in ipairs(group_numbers) do     if tonumber(group_number_item:match("(%d+)/3/%d+")) == group_number then       local current_group_number_state = grp.getvalue(group_number_item)       if has_true_function_one and current_group_number_state == 1 then         grp.write(group_number_item, 0)       elseif not has_true_function_one and current_group_number_state == 0 then         grp.write(group_number_item, 1)       end     end   end end -- set function to handle events from group addresses client:sethandler('groupwrite', function(event)   local address = event.dst   local value = tonumber(event.datahex, 16) or 0   -- check if the address is in the list of function_one addresses   for i, function_one_item in ipairs(function_one) do     if address == function_one_item then       local group_number = tonumber(function_one_item:match("(%d+)/2/%d+"))       update_group_numbers_for_group(group_number)       break     end   end end) -- start the client for data exchange on the local bus client:loop(1)
Reply
#4
client:loop(1) will exit after 1 second, you need this to keep the loop running:
Code:
123
while true do   client:loop(1) end

At the moment your code is very inefficient. Writing to any group address will cause the script to go through a big array checking each entry if it matches. Use group addresses as table keys to quickly check if it belongs to a certain group or not:
Code:
12345678
local group_ids = {} -- fill group address -> group id mapping table on init ... local address = event.dst local group_id = group_ids[ address ] if group_id then -- perform some actions here end

Use grp.getvalue() to get the initial object value and cache them into a table, then update the values that you get from the event. For binary values change the event value decoding and all other checks to use true/false instead of numbers:
Code:
1
local value = tonumber(event.datahex, 16) ~= 0 -- 0 is false, all other values are true
Reply
#5
I think I have a global issue in the code, because it works once and doesn't work another time. Is there a way to monitor the resources and load of the LM? Is it better as a practice to mark the variables with tags as an option?
Reply
#6
Check for script errors in the Error log tab. Have you added while true loop as suggested?
Reply
#7
Yes, I added a while true loop, there are no errors in the log, but it still doesn't work. I assume my approach is wrong, but I don't know what the correct method is to traverse so many addresses.
Reply
#8
Use this as a starting point and fill in the blanks Smile

1. inputgroup maps group address to group id. groupobjects contains group address to value map for each group id. If object is not found then it won't be added.
Code:
12345678910111213141516
local inputgroup = {} local groupobjects = {} for i = 1, 60 do   groupobjects[ i ] = {}   for j = 11, 241, 10 do     local addr = i .. "/2/" .. j     local value = grp.getvalue(addr)     if value ~= nil then       inputgroup[ addr ] = i       groupobjects[ i ][ addr ] = value     end   end end

2. In groupwrite handler check if destination group address belongs to a certain group id. If so then new value is cached and updategroup is called.
Code:
12345678
local addr = event.dst local group = inputgroup[ addr ] if group then   local value = tonumber(event.datahex, 16) ~= 0   groupobjects[ group ][ addr ] = value   updategroup(group) end

3. updategroup checks if there's at least one group address in the group that is on (true). Place this function before the groupwrite handler.
Code:
12345678910111213
local function updategroup(group)   local objects = groupobjects[ group ]   local groupon = false   for addr, value in pairs(objects) do     if value then       groupon = true       break     end   end   log(groupon) end

For outputs you can simply use grp.checkwrite to prevent sending the same values.
Reply
#9
I generally don't understand something. I tried using the advice you gave me, but apparently I'm not using them correctly.
Code:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
local inputgroup = {} local groupobjects = {} for i = 1, 60 do   groupobjects[i] = {}   for j = 11, 241, 10 do     local addr = i .. "/2/" .. j     local status, value = pcall(grp.getvalue, addr)     if status and value ~= nil then       inputgroup[addr] = i       groupobjects[i][addr] = value     end   end end local function updategroup(group)   local objects = groupobjects[group]   local groupon = false   for addr, value in pairs(objects) do     if value then       groupon = true       break     end   end   log(groupon)   local controlAddress = group .. '/2/0'   local isControlEnabled = grp.getvalue(controlAddress)   if isControlEnabled and isControlEnabled ~= nil then     local changeState = not groupon     for i = 11, 161, 10 do       local address3 = group .. '/3/' .. i       local address5 = group .. '/5/' .. (i + 7)       if pcall(grp.getvalue, address3) then         grp.checkwrite(address3, changeState)       end       if pcall(grp.getvalue, address5) then         grp.checkwrite(address5, changeState)       end     end   end end local function onStateChange(event)   local addr = event.dst   local group = inputgroup[addr]   if group then     local value = tonumber(event.datahex, 16) ~= 0     groupobjects[group][addr] = value     updategroup(group)   end end client = require('localbus').new(0.1) if event then   for group = 1, 60 do     for device = 11, 161, 10 do       local address = group .. '/2/' .. device       if pcall(grp.getvalue, address) then         event.register(address, onStateChange, 'groupwrite')       end     end   end else   log('event not available') end while true do   client:loop(1) end
Reply
#10
Try this. If something is not working add log() calls to check what the values are in the called functions. pcall is not needed as grp functions do not stop execution in case of an error, they simply return nil. grp.checkwrite does not write to non-existing objects so checking if object exists beforehand is not needed.
Code:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
local inputgroup = {} local groupobjects = {} for i = 1, 60 do   groupobjects[i] = {}   for j = 11, 241, 10 do     local addr = i .. '/2/' .. j     local value = grp.getvalue(addr)     if value ~= nil then       inputgroup[addr] = i       groupobjects[i][addr] = value     end   end end local function updategroup(group)   local objects = groupobjects[group]   local groupon = false   for addr, value in pairs(objects) do     if value then       groupon = true       break     end   end   local controladdress = group .. '/2/0'   local controlenabled = grp.getvalue(controladdress)   if controlenabled then     local changestate = not groupon     for i = 11, 161, 10 do       local address3 = group .. '/3/' .. i       local address5 = group .. '/5/' .. (i + 7)       grp.checkwrite(address3, changestate)       grp.checkwrite(address5, changestate)     end   end end client = require('localbus').new(0.1) client:sethandler('groupwrite', function(event)   local addr = event.dst   local group = inputgroup[addr]   if group then     local value = tonumber(event.datahex, 16) ~= 0     groupobjects[group][addr] = value     updategroup(group)   end end) while true do   client:loop(1) end
Reply
#11
Thank you very much!
It works flawlessly! The code looks quite simple and works perfectly. It's different when you know the system in detail. Do you have any more detailed documentation that I can read to understand how the system works?
Reply
#12
It's impossible to document all possible scripting features so just ask if you can't find certain information.
There are many examples in this forum. Also check these links for docs and examples:
https://openrb.com/docs/
https://openrb.com/all-examples/
Reply
#13
Thank you very much for the information! I think I've already started to get my bearings.
Is there any way I can monitor CPU and ram usage to determine how efficient the script is?
Reply
#14
Install System load app from the LM app store to monitor CPU/RAM usage.
Reply
#15
Thank you! System load is exactly what I needed.
Reply
#16
What is the difference between what it says about CPU in the bottom corner https://ibb.co/r0vyDRX and what is in System load https://ibb.co/MDmrFck , what are the recommended values?
Reply
#17
CPU/IO shows the whole system load average: https://en.wikipedia.org/wiki/Load_(computing)
System load app shows the CPU/RAM consumption for each process separately. If your resident script constantly consumes around 40% CPU then most likely it needs some optimization.
Reply
#18
This is the script that loads 40%
Code:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
local inputgroup = {} local groupobjects = {} function readAndSetTemperature(group, j)   local base = group .. '/'   local readAddress = base .. '0/15'   local setAddress = base .. '4/' .. j   local temperature = grp.getvalue(readAddress)   if temperature ~= nil then     grp.checkwrite(setAddress, temperature)   end end client = require('localbus').new(0.1) for i = 0, 60 do   local group = i   groupobjects[group] = {}   local controlCheck = group .. '/0/11'   local controlValue = grp.getvalue(controlCheck)   local zeroCheck = group .. '/0/13'   local zeroValue = grp.getvalue(zeroCheck)   for j = 13, 113, 10 do     local addr = group .. '/4/' .. j     local value = grp.getvalue(addr)     if zeroValue == false or controlValue == false then       grp.checkwrite(addr, 0)     elseif value ~= nil then       inputgroup[addr] = group       groupobjects[group][addr] = value     end   end end while true do   for i = 0, 60 do     local group = i     local controlCheck = group .. '/0/11'     local controlValue = grp.getvalue(controlCheck)     local zeroCheck = group .. '/0/13'     local zeroValue = grp.getvalue(zeroCheck)     if zeroValue == false or controlValue == true then       for j = 17, 117, 10 do         local addr = group .. '/4/' .. j         grp.checkwrite(addr, 0)       end     else       for j = 17, 117, 10 do         readAndSetTemperature(group, j)       end     end   end   client:loop(1) end
Reply
#19
The script does not use group monitoring at all. client:loop(1) is more or less the same as os.sleep(1) in this case.
You have more than a hundred of grp.getvalue() calls each second. Instead of constantly polling values you should only react when the values change as in the previous script.
Reply
#20
Is this the right way?
Code:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
local inputgroup = {} local groupobjects = {} function readAndSetTemperature(group, j)   local base = group .. '/'   local readAddress = base .. '0/15'   local setAddress = base .. '4/' .. j   local temperature = grp.getvalue(readAddress)   if temperature ~= nil then     grp.checkwrite(setAddress, temperature)   end end client = require('localbus').new(0.1) client:sethandler('groupwrite', function(event)   local addr = event.dst   local group = inputgroup[addr]   if group then     local value = grp.getvalue(addr)     groupobjects[group][addr] = value     local controlCheck = group .. '/0/11'     local controlValue = grp.getvalue(controlCheck)     local zeroCheck = group .. '/0/13'     local zeroValue = grp.getvalue(zeroCheck)     if zeroValue == false or controlValue == true then       for j = 17, 117, 10 do         local addr = group .. '/4/' .. j         grp.checkwrite(addr, 0)       end     else       for j = 17, 117, 10 do         readAndSetTemperature(group, j)       end     end   end end) for i = 0, 60 do   local group = i   groupobjects[group] = {}   for j = 13, 113, 10 do     local addr = group .. '/4/' .. j     local value = grp.getvalue(addr)     if value ~= nil then       inputgroup[addr] = group       groupobjects[group][addr] = value     end   end end while true do   client:loop(1) end
Reply


Forum Jump: