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:
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:
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:
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
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:
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.
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:
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:
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:
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:
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: