Posts: 32
Threads: 3
Joined: May 2023
Reputation:
0
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.
Posts: 7758
Threads: 42
Joined: Jun 2015
Reputation:
447
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: 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.
Posts: 32
Threads: 3
Joined: May 2023
Reputation:
0
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: 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)
Posts: 7758
Threads: 42
Joined: Jun 2015
Reputation:
447
client:loop(1) will exit after 1 second, you need this to keep the loop running:
Code: 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: 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: local value = tonumber(event.datahex, 16) ~= 0 -- 0 is false, all other values are true
Posts: 32
Threads: 3
Joined: May 2023
Reputation:
0
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?
Posts: 7758
Threads: 42
Joined: Jun 2015
Reputation:
447
Check for script errors in the Error log tab. Have you added while true loop as suggested?
Posts: 32
Threads: 3
Joined: May 2023
Reputation:
0
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.
Posts: 7758
Threads: 42
Joined: Jun 2015
Reputation:
447
Use this as a starting point and fill in the blanks
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: 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: 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: 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.
Posts: 32
Threads: 3
Joined: May 2023
Reputation:
0
I generally don't understand something. I tried using the advice you gave me, but apparently I'm not using them correctly.
Code: 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
Posts: 7758
Threads: 42
Joined: Jun 2015
Reputation:
447
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: 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
Posts: 32
Threads: 3
Joined: May 2023
Reputation:
0
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?
Posts: 7758
Threads: 42
Joined: Jun 2015
Reputation:
447
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/
Posts: 32
Threads: 3
Joined: May 2023
Reputation:
0
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?
Posts: 7758
Threads: 42
Joined: Jun 2015
Reputation:
447
Install System load app from the LM app store to monitor CPU/RAM usage.
Posts: 32
Threads: 3
Joined: May 2023
Reputation:
0
Thank you! System load is exactly what I needed.
Posts: 32
Threads: 3
Joined: May 2023
Reputation:
0
29.05.2023, 08:55
(This post was last modified: 29.05.2023, 08:56 by AISystem.)
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?
Posts: 7758
Threads: 42
Joined: Jun 2015
Reputation:
447
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.
Posts: 32
Threads: 3
Joined: May 2023
Reputation:
0
This is the script that loads 40%
Code: 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
Posts: 7758
Threads: 42
Joined: Jun 2015
Reputation:
447
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.
Posts: 32
Threads: 3
Joined: May 2023
Reputation:
0
Is this the right way?
Code: 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
|