Author Topic: Function Frustration - How to chain them & pass information from one to another  (Read 300 times)

Offline parkerc

  • Sr. Hero Member
  • ******
  • Posts: 2330
  • Karma: +32/-44
  • Life Moves Pretty Fast....
    • Node Central
Thanks to @akbooer for helping me with this topic in a related thread, but @akbooer and others, I still can't grasp how to link/chain functions together.

I can see why breaking up code into functions helps, but it's the linking them together that I can't get. So I created the following code to try to teach myself how to do it but I still get nil value errors - which I assume are because the output/results of a function is not being seen/passed to another.

The following code aims to watch for the "ArmMode" variable to change on the alarm panel, and then when it does it will do number of actions.

1) check who made the change - this will need to be a luup.call_delay as this code will run faster than the reference "LastUser" variable is updated (a second or two should be fine)
2) compose message based on what has changed and who changed it
3) write the message in (2) to file
4) send notification, which includes the message via Prowl

Code: [Select]
-- script is to be executed in the event that the alarm system's "ArmMode" variable changes

local dev = 344
local svc = "urn:micasaverde-com:serviceId:AlarmPartition2"

local alarmstatus = luup.variable_get(svc, "ArmMode", dev)
local timestr = os.date("%Y-%m-%d %H:%M:%S")
local last_user_num_str = luup.variable_get(svc, "LastUser", dev)
print("Last user #: " .. last_user_num_str, 1)

luup.variable_watch("who_interacted_with_the_alarm", svc,"ArmMode", dev)

local function who_interacted_with_the_alarm()

    users = {}
    users["0001"]="personA"
    users["0002"]="personB"
    users["0003"]="personC"
    users["0004"]="personD"
    users["0040"]="personE"

    local interaction_was_by = users[last_user_num_str], 1
    print("Last user: '" .. users[last_user_num_str] .. "'", 1)
    print(disarmed_by)
end

local function create_message ()
  text = ("On " .. timestr .. " - DSC alarm was " .. alarmstatus .. " - By User ID: " .. last_user_num_str .. " a.k.a " .. interaction_was_by .."\n")
 
end

local function write_alarm_interaction_message_to_file (text)

-- OPEN FILE.
local file = io.open("/www/alarm_interaction.txt", 'a')

-- WRITE NEW VALUE TO NEW LINE AND CLOSE FILE
file:write(text)
file:close()
end

local function send__alarm_interaction_notification_via_prowl (text)
  -- currently not working due to the need for text to be encoded in URL with a '+' used for any required spaces - how do I do that the provided text???
   
    local apikey = "powjfpjfqwpjfqpfjqpfjqfopj"
    local app = "DSC+Alarm+Panel"
    local event = "System+Interaction"
    local priority = "1"
   
        luup.inet.wget("https://api.prowlapp.com/publicapi/add?apikey=" .. apikey .. "&application=" .. app .. " &event=" .. event .. "&description=" .. text .. "&priority=" ..priority) 
end

   
luup.call_delay ('who_interacted_with_the_alarm',1)
texttosend = create_message ()
local writetofile = write_alarm_interaction_message_to_file (texttosend)
local sendprowl = send__alarm_interaction_notification_via_prowl (texttosend)

Luatest reports the following

Quote
LuaTest 1.5.2

Lua file: /nas/luatest/dsc_lastuser_prowl_file.lua

Results
Runtime error: Line 28: attempt to concatenate global 'interaction_was_by' (a nil value)

Locals
[create_message]
[main]
dev=344
svc="urn:micasaverde-com:serviceId:AlarmPartition2"
alarmstatus="Disarmed"
timestr="2017-09-12 13:52:21"
last_user_num_str="0002"
who_interacted_with_the_alarm=function
create_message=function
write_alarm_interaction_message_to_file=function
send__alarm_interaction_notification_via_prowl=function

Print output
Last user #: 0002     1 
« Last Edit: September 12, 2017, 12:24:23 pm by parkerc »

Offline parkerc

  • Sr. Hero Member
  • ******
  • Posts: 2330
  • Karma: +32/-44
  • Life Moves Pretty Fast....
    • Node Central
OK, the luup.call_delay was messing things up, but removing that for now - I still get the nil value - yet the print statement confirms that it's known.

Code: [Select]
-- script is to be executed in the event that the alarm system's "ArmMode" variable changes

local dev = 344
local svc = "urn:micasaverde-com:serviceId:AlarmPartition2"

local alarmstatus = luup.variable_get(svc, "ArmMode", dev)
local timestr = os.date("%Y-%m-%d %H:%M:%S")
local last_user_num_str = luup.variable_get(svc, "LastUser", dev)
print("Last user #: " .. last_user_num_str, 1)

luup.variable_watch("who_interacted_with_the_alarm", svc,"ArmMode", dev)

local function who_interacted_with_the_alarm()

    users = {}
    users["0001"]="personA"
    users["0002"]="personB"
    users["0003"]="personC"
    users["0004"]="personD"
    users["0040"]="personE"

    local interaction_was_by = users[last_user_num_str], 1
    print("Last user: '" .. users[last_user_num_str] .. "'", 1)
end

local function create_message ()
  text = ("On " .. timestr .. " - DSC alarm was " .. alarmstatus .. " - By User ID: " .. last_user_num_str .. " a.k.a " .. interaction_was_by .."\n")
 
end

local function write_alarm_interaction_message_to_file (text)

-- OPEN FILE.
local file = io.open("/www/alarm_interaction.txt", 'a')

-- WRITE NEW VALUE TO NEW LINE AND CLOSE FILE
file:write(text)
file:close()
end

local function send__alarm_interaction_notification_via_prowl (text)
  -- currently not working due to the need for text to be encoded in URL with a '+' used for any required spaces - how do I do that the provided text???
   
    local apikey = "powjfpjfqwpjfqpfjqpfjqfopj"
    local app = "DSC+Alarm+Panel"
    local event = "System+Interaction"
    local priority = "1"
   
        luup.inet.wget("https://api.prowlapp.com/publicapi/add?apikey=" .. apikey .. "&application=" .. app .. " &event=" .. event .. "&description=" .. text .. "&priority=" ..priority) 
end

   
who_interacted_with_the_alarm ()
-- luup.call_delay ('who_interacted_with_the_alarm',1)
texttosend = create_message ()
local writetofile = write_alarm_interaction_message_to_file (texttosend)
local sendprowl = send__alarm_interaction_notification_via_prowl (texttosend)

Quote
LuaTest 1.5.2

Lua file: /nas/luatest/dsc_lastuser_prowl_file.lua

Results
Runtime error: Line 27: attempt to concatenate global 'interaction_was_by' (a nil value)

Locals
[create_message]
[main]
dev=344
svc="urn:micasaverde-com:serviceId:AlarmPartition2"
alarmstatus="Disarmed"
timestr="2017-09-12 13:56:18"
last_user_num_str="0002"
who_interacted_with_the_alarm=function
create_message=function
write_alarm_interaction_message_to_file=function
send__alarm_interaction_notification_via_prowl=function

Print output
Last user #: 0002     1     
Last user: 'personB'     1   

« Last Edit: September 12, 2017, 09:01:51 am by parkerc »

Offline parkerc

  • Sr. Hero Member
  • ******
  • Posts: 2330
  • Karma: +32/-44
  • Life Moves Pretty Fast....
    • Node Central
Side point, for the (I assume it is encoding) of the text, I found something @Futzle shared a few years ago, I've just got to work out how to use it :-) - http://forum.micasaverde.com/index.php/topic,4508.msg140636.html#msg140636

... Lua has a table form of gsub() which makes your URLification much shorter:

Code: [Select]
rep = { [" "] = "%20", ["!"] = "%21",
  -- and so on
}
print s:gsub("[ !"#\$%...]", rep)

Offline RichardTSchaefer

  • Master Member
  • *******
  • Posts: 9571
  • Karma: +729/-136
    • RTS Services Plugins
You need to read up and understand scope ... the error message is telling you what the problem is.

Offline parkerc

  • Sr. Hero Member
  • ******
  • Posts: 2330
  • Karma: +32/-44
  • Life Moves Pretty Fast....
    • Node Central
Hi @RTS

Agreed, and hence the post :-)

The interaction_was_by variable value is being captured and it's presented via the print statement under the function called who_interacted_with_the_alarm() so I can see we have it (and it's not nil).

The other function create_message () also needs to make use of that variable too, but how can I get it to - without having to recreate the whole array/code under that function too ?   What I'm trying to grasp is how values etc created by one function can be passed/consumed by others ?

Sorry if I'm missing something obvious ?
« Last Edit: September 12, 2017, 01:20:56 pm by parkerc »

Offline parkerc

  • Sr. Hero Member
  • ******
  • Posts: 2330
  • Karma: +32/-44
  • Life Moves Pretty Fast....
    • Node Central
Hold on !   - I think I have missed something obvious...

Offline parkerc

  • Sr. Hero Member
  • ******
  • Posts: 2330
  • Karma: +32/-44
  • Life Moves Pretty Fast....
    • Node Central
No, I don't think I have.

Code: [Select]
-- script is to be executed in the event that the alarm system's "ArmMode" variable changes

local dev = 344
local svc = "urn:micasaverde-com:serviceId:AlarmPartition2"

local alarmstatus = luup.variable_get(svc, "ArmMode", dev)
local timestr = os.date("%Y-%m-%d %H:%M:%S")
local last_user_num_str = luup.variable_get(svc, "LastUser", dev)
print("Last user #: " .. last_user_num_str, 1)

luup.variable_watch("who_interacted_with_the_alarm", svc,"ArmMode", dev)

local function who_interacted_with_the_alarm()

    users = {}
    users["0001"]="personA"
    users["0002"]="personB"
    users["0003"]="personC"
    users["0004"]="personD"
    users["0040"]="personE"

    local interaction_was_by = users[last_user_num_str], 1
    print("Last user: '" .. users[last_user_num_str] .. "'", 1)
    print(interaction_was_by)
end

local function create_message ()
 
  text = ("On " .. timestr .. " - DSC alarm was " .. alarmstatus .. " - By User ID: " .. last_user_num_str .. " a.k.a " .. interaction_was_by .."\n")
  print(text)
end

local function write_alarm_interaction_message_to_file (text)

-- OPEN FILE.
local file = io.open("/www/alarm_interaction.txt", 'a')

-- WRITE NEW VALUE TO NEW LINE AND CLOSE FILE
file:write(text)
file:close()
end

local function send__alarm_interaction_notification_via_prowl (text)
  -- currently not working due to the need for text to be encoded in URL with a '+' used for any required spaces - how do I do that the provided text???
   
    local apikey = "powjfpjfqwpjfqpfjqpfjqfopj"
    local app = "DSC+Alarm+Panel"
    local event = "System+Interaction"
    local priority = "1"
   
        luup.inet.wget("https://api.prowlapp.com/publicapi/add?apikey=" .. apikey .. "&application=" .. app .. " &event=" .. event .. "&description=" .. text .. "&priority=" ..priority) 
end

   
who_interacted_with_the_alarm ()
-- luup.call_delay ('who_interacted_with_the_alarm',1)
texttosend = create_message ()
local writetofile = write_alarm_interaction_message_to_file (texttosend)
local sendprowl = send__alarm_interaction_notification_via_prowl (texttosend)

How can I get a value created under the who_interacted_with_the_alarm function to be a value used in the create_message function  ?

Offline BulldogLowell

  • Hero Member
  • *****
  • Posts: 1571
  • Karma: +190/-85
Code: [Select]
local function who_interacted_with_the_alarm()

    users = {}
    users["0001"]="personA"
    users["0002"]="personB"
    users["0003"]="personC"
    users["0004"]="personD"
    users["0040"]="personE"

    local interaction_was_by = users[last_user_num_str], 1
    print("Last user: '" .. users[last_user_num_str] .. "'", 1)
    print(interaction_was_by)
end

interaction_was_by(defined as local here) is destroyed when the function returns... therefore inaccessible in any other function...

you can try to define it with local scope outside a function, and before any function that uses that variable...

sort of like this:

Code: [Select]
local myVar

function someFunction()
  myVar = "This String"
end

function someOtherFunction()
  myVar = "Some Other String"
end

Offline rigpapa

  • Full Member
  • ***
  • Posts: 112
  • Karma: +18/-0
Functions can return values, and function callers can receive those values and pass them on to other functions and/or act on them locally.

Your function should be returning the value that it's working to create:

local function who_interacted_with_the_alarm()

    users = {}
    users["0001"]="personA"
    users["0002"]="personB"
    users["0003"]="personC"
    users["0004"]="personD"
    users["0040"]="personE"

    local interaction_was_by = users[last_user_num_str], 1
    print("Last user: '" .. users[last_user_num_str] .. "'", 1)
    print(interaction_was_by)
    return interaction_was_by
end


The caller can receive that value...

local whoDidIt = who_interacted_with_the_alarm()

This creates a variable in the caller's scope to store the value that is passed back. And you can then pass that value to another function...

local whoDidIt = who_interacted_with_the_alarm()
textToSend = create_message( whoDidIt )

And that function can be defined to receive it and use what it gets...

function create_message( person )
    return "The person who interacted with the alarm was " .. person
end


Functions can also return more than one value in Lua, and callers can receive any or all.
Author of Rachio, Deus Ex Machina II, SiteSensor, and Auto Virtual Thermostat plugins. Using Vera Plus (1.7.3015), Vera3 (1.7.855), 50 dimmers and 40 switches (mostly Leviton, some Linear and GE), a dozen sensors, a truckload of PLEG, and of course, my own plugins.

Offline reneboer

  • Hero Member
  • *****
  • Posts: 1091
  • Karma: +57/-30
Hi Parkerc,

Have a look at the LUA reference manual. There are good explanations on the scope of a variable. https://www.lua.org/manual/5.2/manual.html

Cheers Rene
2xVeraLite, VeraEdge, openLuup, ALTUI, 20 switches, 10 dimmers, 20 sensors, 10 scene controllers, 1 Harmony Hub, many plug-ins. Not enough time.

Offline parkerc

  • Sr. Hero Member
  • ******
  • Posts: 2330
  • Karma: +32/-44
  • Life Moves Pretty Fast....
    • Node Central
Many thanks @rigpapa

Educating me on how "return" is used within a function, and how that then allows the value/variable created (by that function) to be consumed elsewhere really helped.

I find that it also helps when I have examples that relate to using Vera, while I know the Lua manual is trying to be very generic and clear on how it explains their use - they just don't seem to resonate as well with me than real world (vera) examples..

If you have an example of how to return create multiple values/variables within a single function, and then assigning each of them as a local variable for consumption elsewhere - that would be interesting to see. 

FYI - Here's my code for a watched variable to look up the userID variable (number) to get the associated person's name to then generate a structured message which it them writes into a web accessible text file on Vera.

Code: [Select]
-- To be added to Lua Start Up - script is to be executed in the event that the alarm system's "ArmMode" variable changes

local dev = 344
local svc = "urn:micasaverde-com:serviceId:AlarmPartition2"
local alarmstatus = luup.variable_get(svc, "ArmMode", dev)
local dsctimestr = os.date("%Y-%m-%d %H:%M:%S")
local last_user_num_str = luup.variable_get(svc, "LastUser", dev)

local function who_interacted_with_the_alarm()

    users = {}
    users["0001"]="personA"
    users["0002"]="personB"
    users["0003"]="personC"
    users["0004"]="personD"
    users["0040"]="personE"

    local interaction_was_by = users[last_user_num_str], 1
    return interaction_was_by
end

local whoDidIt = who_interacted_with_the_alarm()

local function create_message (person)
 
  local text1 = ("On " .. dsctimestr .. " - DSC alarm was " .. alarmstatus .. " - By User ID: " .. last_user_num_str .. " a.k.a " .. person)
  return text1
end

local message_to_send = create_message (whoDidIt)

local function write_alarm_interaction_message_to_file(dscmessage)

local file = io.open("/www/alarm_interaction.txt", 'a')
file:write(dscmessage .. "\n")
file:close()
end

write_alarm_interaction_message_to_file(message_to_send)
luup.variable_watch("who_interacted_with_the_alarm", svc,"ArmMode", dev)
« Last Edit: September 18, 2017, 12:11:37 pm by parkerc »

Offline akbooer

  • Master Member
  • *******
  • Posts: 5129
  • Karma: +221/-67
  • "Less is more"
If you have an example of how to return create multiple values/variables within a single function, and then assigning each of them as a local variable for consumption elsewhere - that would be interesting to see. 

Code: [Select]
local function twoReturns ()
  return 1, "two"
end

local a,b = twoReturns()

print (a,b)
3x Vera Lite-UI5/Edge-UI7, 25x Fibaro, 23x TKB, 9x MiniMote, 2x NorthQ Power, 2x Netatmo, 1x Foscam FI9831P.
Razberry, MySensors Arduino, HomeWave, AltUI, DataYours, openLuup, ZWay, ZeroBrane Studio.

Offline parkerc

  • Sr. Hero Member
  • ******
  • Posts: 2330
  • Karma: +32/-44
  • Life Moves Pretty Fast....
    • Node Central
If you have an example of how to return create multiple values/variables within a single function, and then assigning each of them as a local variable for consumption elsewhere - that would be interesting to see. 

Code: [Select]
local function twoReturns ()
  return 1, "two"
end

local a,b = twoReturns()

print (a,b)

Thanks @akbooer. I think I understand your example ..

For some reason so far in my journey I've consistently struggled to grasp those sorts of 'a = b' type examples (relying on real Vera examples to help things to sink in !) - yet I'm sure the a+b=c approach is supposed to be much easier to follow.

Using return 1, "two" makes sense as an example, but if you're looking to process something with a little more detail within a function - lets says you wanted to check how many of your lights are on and how many are off via Vera, in order to return two variables - how best would you do that ?







Offline BulldogLowell

  • Hero Member
  • *****
  • Posts: 1571
  • Karma: +190/-85
The example he wrote actually explains that not only can a function return more than one value, they needn't be the same type. Both a number and a string in that example. A function can just as easily return an object like a table, for example.

If you want to know the total number of lights on and off, simply iterate over all of them and check if each is on...  the rest are off (simply subtract from the total number of devices).

Offline parkerc

  • Sr. Hero Member
  • ******
  • Posts: 2330
  • Karma: +32/-44
  • Life Moves Pretty Fast....
    • Node Central
If you want to know the total number of lights on and off, simply iterate over all of them and check if each is on...  the rest are off (simply subtract from the total number of devices).

Thanks and totally agree -  my interest earlier was only to try to highlight an example where you do something a little more complex to generate two or more values/variables from the same function.

And the key point I was looking to confirm (in my own head) was that the order in which you ?return? your required values/variables. Is the same order you need to call/define them to be consumed elsewhere.