Jump to content

[TUTORIAL] Introduction to Lua tables (2nd edition)


Recommended Posts

 

**MAJOR UPDATE TO THIS TUTORIAL**

Hi,

This tutorial has been going through a major update and expansion since the first release. Special thanks deserved to @petergebruers and others for helping me understand many of the underlying table concepts and for also contributing a lot to this tutorial. It may be a good idea to paste some of the code in this tutorial into a test scene to understand how they work by reviewing the output

 

Topics covered with lots of sample code and expected outputs

  • Table basics
  • Creating tables and printing  (ordered,  non-ordered, numerical and non numerical keys)
  • #operator - pros and cons
  • kv pairs  - advantages
  • Updating tables - adding adding, inserting values
  • Nested Tables - printing, updating, inserting, 
  • Summary of key points 
  • Storing tables in global variables

 

I would have found this very useful when I started so hopefully some users will find it useful as well.

-frank & peter

 

Tables

A table is a tree structure, consisting of field and each field has a "key" and "value" pair.

The key can be any Lua value except nil and NaN. A table can store anything except nil.

The name of the table is the variable that stores a reference to the table so it can be accessed

 

Creating Tables and displaying the table data

a = {}

This is the smallest table and stores the reference to the table {} in the variable a

Consider the following table

print("table with numerical keys")
fav = {[1] = "4FM", [2] = "96FM", [3] = "Calm", [4] = "Heart", [5] = "Red_FM"}

fav is the table (or at least the name of the variable where the table is stored),

in the above table 'fav', it has 5 fields and each field consists of a key/value pair

4 is the key and 4FM is the value , 2 is a key and 96FM is the value, etc

 

You can display all the key/value pairs using the following ...

print("table with numerical keys and using k/v pairs to display the fields but they won't be in order")
fav = {[1] = "4FM", [2] = "96FM", [3] = "Calm", [4] = "Heart", [5] = "Red_FM"}

for k,v in pairs(fav) do print("k: " ..k..", v: "..v) end

the result is ...

 

result.PNG

 

and you'll notice that the results are NOT returned in order

 

if you have numerical keys [1], [2] then you will need to use ipairs to return them in order

print("table with numerical keys and using k/v ipairs to display the fields in order")

fav = {[1] = "4FM", [2] = "96FM", [3] = "Calm", [4] = "Heart", [5] = "Red_FM"}

for k,v in ipairs(fav) do print("k: " ..k..", v: "..v) end

 

#operator

The #operator (#tablename) is used by a lot of people to identify the length of the table and use this to iterate through the table but it is not always recommended

an example of where it will work is here 

print("table with numerical keys and using the #operator to iterate through the table")
radio = {"4FM", "96FM", "Calm", "Heart", "Red_FM"}

print("#operator: "..#radio)
for i = 1, #radio do print(radio[i]) end

image 11.PNG

 

if the table keys are numerical and contiguous, it will work..

....but if they are not or you have inserted or removed values then it won't work

 

A good example of where it doesn't work very well is here

print("table where numerical keys are not explicitly defined but are still the table keys")
print(" example where the #operator doesnt return the correct count")
radio = {"4FM", "96FM", "Calm", "Heart", "Red_FM"}

for k,v in pairs(radio) do print("k: " ..k..", v: "..v) end
print("#operator: "..#radio)

-- remove one value, set it to nil
radio[3] = nil

for k,v in pairs(radio) do print("k: " ..k..", v: "..v) end
print("#operator: "..#radio)

image10.PNG

you can see that the second loop in the debug is showing an #operator value of 5 (because it is the last key but there are only 4 key/value pairs as we removed one

 

Another approach that is recommended to use is as follows

use the k/v approach to count the fields and then use the counter to iterate through..

print("table where numerical keys are not explicitly defined but are still the table keys")
print(" example using k/v pairs to calculate the correct table field count")

radio = {"4FM", "96FM", "Calm", "Heart", "Red_FM"}

counter = 0
for k,v in pairs(radio) do counter=counter+1 end
print("counter: "..counter)
for i = 1, counter do print(radio[i]) end

radio[3] = nil -- remove the row by setting the key to nil

counter = 0
for k,v in pairs(radio) do counter=counter+1 end
print("counter: "..counter)
for i = 1, counter do print(radio[i]) end

image12.PNG

 

and you can see that the counter is correct in both loops

 

Updating Tables

 

you can update a table by setting Key/Value pairs  or delete a value as follows

print("table where numerical keys are not explicitly defined but are still the table keys")
print("examples of updating the table")

fav = {"4FM", "96FM", "Calm", "Heart", "Red_FM"}

-- update one value add another value
fav[2] = "Soul"
fav[4] = "Classic"

-- delete value at key 1
fav[1] = nil

for k,v in pairs(fav) do print("k: " ..k..", v: "..v) end

if the k/v pair already exists it overwrites it or of the k/v pair didn't exist, it simply adds it as above

image16.PNG

 

 

This is the same example but the table has the numerical keys explicitly defined

print("updating a table")
fav = {
  [1] = "4FM",
  [2] = "96FM",
  [3] = "Calm",
}

fav[2] = "Soul"
fav[4] = "Classic"

for k,v in ipairs(fav) do print("k: " ..k..", v: "..v) end

image13.PNG

 

Example of adding key/value pairs to this table using table.insert

fav= {"4FM","96FM", "Soul", "Jazz"}
table.insert(fav, "Calm")
table.insert(fav, "Red_FM")
table.insert(fav, "Heart")
for k,v in pairs(fav) do print("k: " ..k..", v: "..v) end

The extra key/value pairs will be added as extra fields as you cannot specify the exact field you want to update as in the approach above

image14.PNG

 

Tables with non-numerical keys are also possible as shown below

print("Table initialiser with alpha keys")
fav = {
  ["a"] = "4FM",
  ["b"] = "96FM",
  ["c"] = "Calm",
  ["d"] = "Heart",
  ["e"] = "Red_FM"}
for k,v in pairs(fav) do print("k: " ..k..", v: "..v) end

image15.PNG

 

the same table can be represented in a simpler form

print("Table initialiser with alpha keys - simplified")
fav = {
  a = "4FM",
  b= "96FM",
  c= "Calm",
  d = "Heart",
  e= "Red_FM"}
for k,v in pairs(fav) do print("k: " ..k..", v: "..v) end

If you chose or have to use non numerical keys or a mix, then Lua makes no guarantee on order as the spec says random.

The only way to order a table with non-numerical keys (such as alphabetical order) is to use an external reference.

 

Nested Tables

Lets take the following table, it looks like each value in the key/value pair is a table in itself as if one table is nested inside the other

table = {
  [1] = {loc="utility_room", name="HeatingTemp"}, 
  [2] = {loc="bed_room", name="FreezerTemp"},
}

for k,v in pairs(table) do print(k,v) end

if you run the usual k/v code you will see the second/nested set of tables

image17.PNG

 

To list the k/v inside the second table you need to iterate through the first table

we use a counter like before to find out how many fields are in the outer most table

There are two tables nested within the outer most table 

  • table [1] which has two fields with k/v pairs
  • table [2] which has two fields with k/v pairs
print("Nested table example")
table = {
  [1] = {loc="utility_room", name="HeatingTemp"}, 
  [2] = {loc="bed_room", name="FreezerTemp"},
}
for k,v in pairs(table) do
  print("first level key: " ..k..", first level value: "..tostring(v))
  for k2,v2 in pairs(v) do -- This works because we KNOW v is a table.
    print(" second level key: " ..k2..", second level value: "..v2)
  end
end

we then use the counter to expose the k/v pairs at table[1] and table [2] with the following result

 

image18.PNG

 

You could also expose specific values (in the nested able) using the following 

table = {
  [1] = {loc="utility_room", name="HeatingTemp"}, 
  [2] = {loc="bed_room", name="FreezerTemp"},
}

for k,v in pairs(table) do

	print("location: "..table[k].loc)
  
end

with the following expected result

image19.PNG

 

You could also have used the #operator that we discussed earlier BUT only because the key's in the outer-most table are numerical and contingous

for i = 1, #table do  -- #list refers to the max number of items in the table
  
  fibaro:debug(table[i].ip, table[i].port)
    
end

 

Updating a value in a nested table

 

If you want to update a specific value in the nested table you need to use the k/v method we have discussed

fav = {
  [1] = {loc="utility_room", name="HeatingTemp"}, 
  [2] = {loc="bed_room", name="FreezerTemp"},
}

-- update nested table on the second field with a new value
  fav[2].loc = "kitchen" 
  fav[2].name = "Hobtemp"

-- or you could use the following format to do accomplish the same update
  fav[2]["loc"] = "kitchen" 
  fav[2]["name"] = "Hobtemp"


-- print out the updated table...

for k,v in pairs(fav) do
  
	for k,v in pairs(fav[k]) do print("k: " ..k..", v: "..v) end
  
end

-- second option to print out the updated table with more explanation
for k,v in pairs(fav) do
  print("first level key: " ..k..", first level value: "..tostring(v))
  for k2,v2 in pairs(v) do -- This works because we KNOW v is a table.
    print(" second level key: " ..k2..", second level value: "..v2)
  end
end

the result is 

image20.PNG

 

If you want to add/insert a new k/v pair in the nested level, you can use the table.insert method

fav = {
  [1] = {loc="utility_room", name="HeatingTemp"}, 
  [2] = {loc="bed_room", name="FreezerTemp"},
}

-- insert a new value
table.insert(fav,{loc="bedroom", name="sinktemp"})

-- print out the updated table...
for k,v in pairs(fav) do
  
	for k,v in pairs(fav[k]) do print("k: " ..k..", v: "..v) end
  
end

-- second option to print out the updated table with more explanation
for k,v in pairs(fav) do
  print("first level key: " ..k..", first level value: "..tostring(v))
  for k2,v2 in pairs(v) do -- This works because we KNOW v is a table.
    print(" second level key: " ..k2..", second level value: "..v2)
  end
end

and the result is three fields in the table

image21.PNG

 

So before we go to the last task of storing the table in a global variable, I thought it would be good to have a quick recap..

 

Summary

A table is a tree structure, consisting of field and each field has a "key" and "value" pair. 

The name of the table is the variable that stores a reference to the table so it can be accessed 
you can explicitly declare a numerical key in a table but you don't have to
    

  • for a table with a numerical key - use ipairs to print the table fields in order
  • #operator can be used but ONLY for tables with numerical keys that are contiguous
  • it is recommended to use the standard k/v approach to count the number of fields
  • remove a value by setting its key = nil
  • update a table using the table[key] method
  • add a new field using table.insert(table, "value") and will add a numerical key
  • table.insert will not work with tables with non-numerical keys
  • tables with non-numerical keys cannot be printed in order by ipairs, result will be random
  • nested tables require iteration to print the inner-most tables
  • multiple levels of iteration required for multi-nested tables
  • updating nested table - iterate with k/v and use table[key].nested-key = "value"
  • inserting into nested table - iterate with k/v and use table.insert(table,{key="value, key="value}
  • tables can be very confusing and frustrating to us newbie's , so take your time :-) 

 

 

 

Storing a table in a global variable

if you take the following table structure

Table = {
    group = {
        option1=1560,option2=1507,option3=881        
    },
  }

 

If you want to store this table in a global variable you will need to encode the table as a string. One of the most common formats to encode a table (structure) is json 

 

from json.org

"JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate."

 

To save the table you need to encode it  and then save it to the global variable

jTable = json.encode(table)
fibaro:setGlobal("StateTable", jTable)

 

In order to read back a single value you need to decode the string 

local jT = json.decode(fibaro:getGlobalValue("Table"))

once it's decoded you can then read one of the values or manipulate the table

fibaro:debug(jT.group.option1)

 

Updating a single value

- assign the new value, encode it and the store it.....

jT.group.option3 = 987
jTable = json.encode(jT)
fibaro:setGlobal("StateTable", jTable)

 

Making json strings/output easier to read...

Sometimes a json string can be difficult to read and understand its structure. Typically they would look something like this...

{"widget": {"debug": "on","window": {"title": "Sample Konfabulator Widget","name": "main_window","width": 500,"height": 500},"image": {"src": "Images/Sun.png","name": "sun1","hOffset": 250,        "vOffset": 250,"alignment": "center"},"text": {"data": "Click Here","size": 36,"style": "bold","name": "text1","hOffset": 250,"vOffset": 100,"alignment": "center","onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"}}}

There are a number of websites that will 'pretify' the string make it easier to read the structure and enable you to understand how you might navigate it. 

If you take json string above and paste it into http://jsonprettyprint.com/ it will display it more like a table

This format is easier to understand

 

{
  "widget": {
    "debug": "on",
    "window": {
      "title": "Sample Konfabulator Widget",
      "name": "main_window",
      "width": 500,
      "height": 500
    },
    "image": {
      "src": "Images\/Sun.png",
      "name": "sun1",
      "hOffset": 250,
      "vOffset": 250,
      "alignment": "center"
    },
    "text": {
      "data": "Click Here",
      "size": 36,
      "style": "bold",
      "name": "text1",
      "hOffset": 250,
      "vOffset": 100,
      "alignment": "center",
      "onMouseUp": "sun1.opacity = (sun1.opacity \/ 100) * 90;"
    }
  }
}

 

 

 

Finally....

There is a very good discussion on using a table in a predefined variables to store the ID's of scenes and devices for the HC2

https://forum.fibaro.com/index.php?/topic/23942-tutorial-using-a-hometable-to-store-device-and-scene-ids/


suggestions to improve welcome

-f

 

Edited by AutoFrank
  • Like 5
  • Thanks 2
Link to post
Share on other sites

I put this in one of my scenes:

 

lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}

    --print("#operator: "..#lamp_ON)
    for i = 1, #lamp_ON do 
        if  tonumber(fibaro:getValue(lamp_ON, "value")) > 0 then
              fibaro:debug("Forgotten to Turn this Lamp OFF: " ..lamp_ON);
        end
    end
  

Output in debug if light is ON:

Forgotten to Turn this Lamp OFF: 79 < Lamp ID

 

But instead of the Lamp ID I would like to have a text.. like:

Forgotten to Turn this Lamp OFF: Kitchen Table 

 

How to accomplish this?

 

Link to post
Share on other sites

i get this error:

 

[DEBUG] 14:41:48: line 115: attempt to concatenate local 'deviceId' (a table value)

 

-- table with Lamp ID's
lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}
 

     --print("#operator: "..#lamp_ON)
    for i = 1, #lamp_ON do 
        if  tonumber(fibaro:getValue(lamp_ON, "value")) == 0 then -- > 0 
              --fibaro:debug("Forgotten to Turn this Lamp OFF: " ..lamp_ON);
            fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:get(lamp_ON, 'name'));
        end
    end

 

Link to post
Share on other sites
9 minutes ago, samuel said:

i get this error:

 

[DEBUG] 14:41:48: line 115: attempt to concatenate local 'deviceId' (a table value)

 

-- table with Lamp ID's
lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}
 

     --print("#operator: "..#lamp_ON)
    for i = 1, #lamp_ON do 
        if  tonumber(fibaro:getValue(lamp_ON, "value")) == 0 then -- > 0 
              --fibaro:debug("Forgotten to Turn this Lamp OFF: " ..lamp_ON);
            fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:get(lamp_ON, 'name'));
        end
    end

 

 

Hi @samuel

As far as I can see you may need to use a call to the HC2 api using the device id to determine the device name

I don't have time this minute to do a full solution but here is the api if it helps

 

local http = net.HTTPClient()
  http:request("http://127.0.0.1:11111/api/devices/"..id.."", {
  options = { method = 'GET', headers = {}, timeout = 2000 },
      success = function(status)
      fibaro:debug("s-s: "..status.status)
      if status.status ~= 200 and status.status ~= 201 then print("failed"); end
   		response = status.data    	
  		print("res: "..response)
   		result  = json.decode(response)
   		      		
      	end,
      error = function(err)
      print('[ERROR] ' .. err)
     end
    })

you'll need to pass the 'id' to the http:request string 

'result' is the http response table that has the name you need.

 

Link to post
Share on other sites
2 hours ago, AutoFrank said:

 

Hi @samuel

As far as I can see you may need to use a call to the HC2 api using the device id to determine the device name

(...)

 

The API is certainly a generic solution, but HC has a builtin function to determine the name of a device. Like this (plus some other corrections to the code):

 

lamp_ON = {79, 148, 153, 238, 239, 284, 298, 299, 312, 313, 314, 314, 322}
for k,v in pairs(lamp_ON) do
  if  tonumber(fibaro:getValue(v, "value")) ~= 0 then -- > 0 
    --fibaro:debug("Forgotten to Turn this Lamp OFF: " ..v);
    fibaro:debug("Forgotten to Turn this Lamp OFF: "..(fibaro:getName(v) or ""));
  end
end

Edit: fix type of table values and add nil test.

Edited by petergebruers
Link to post
Share on other sites
5 minutes ago, petergebruers said:

 

The API is certainly a generic solution, but HC has a builtin function to determine the name of a device. Like this (plus some other corrections to the code):

 

lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}
for k,v in pairs(lamp_ON) do 
  if  tonumber(fibaro:getValue(v, "value")) ~= 0 then -- > Not 0 means "on"
    --fibaro:debug("Forgotten to Turn this Lamp OFF: " ..v);
    fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(v));
  end
end

 

 

thanks @petergebruers - more like what @ReneNL was referring to .... :-)

 

Link to post
Share on other sites

 

 

39 minutes ago, petergebruers said:

 

The API is certainly a generic solution, but HC has a builtin function to determine the name of a device. Like this (plus some other corrections to the code):

 

lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}
for k,v in pairs(lamp_ON) do 
  if  tonumber(fibaro:getValue(v, "value")) ~= 0 then -- > Not 0 means "on"
    --fibaro:debug("Forgotten to Turn this Lamp OFF: " ..v);
    fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(v));
  end
end

 

 

i got an error: [DEBUG] 15:48:54: line 36: Assertion failed: Expected number

 Line 36 below:

fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(v));

 

 

in pairs, 

I tried the one from @ Autofrank with ..id.. ..(79).. for a try, then i got the whole json string as output... 

 

Edited by samuel
Link to post
Share on other sites

the table input are the id's from the lights

lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}
Link to post
Share on other sites

**MAJOR UPDATE TO THIS TUTORIAL**

Hi,

This tutorial has been going through a major overhaul and expansion since the first release. Special thanks deserved to @petergebruers and others for helping me understand many of the underlying table concepts and for also contributing a lot to this tutorial. It may be a good idea to paste some of the code in this tutorial into a test scene to understand how they work by reviewing the output

 

Topics covered with lots of sample code and expected outputs

  • Table basics
  • Creating tables and printing  (ordered,  non-ordered, numerical and non numerical keys)
  • #operator - pros and cons
  • kv pairs  - advantages
  • Updating tables - adding adding, inserting values
  • Nested Tables - printing, updating, inserting, 
  • Summary of key points 
  • Storing tables in global variables

 

I would have found this very useful when I started so hopefully some users will find it useful as well.

-frank & peter

Edited by AutoFrank
Link to post
Share on other sites
56 minutes ago, samuel said:

 

 

 

i got an error: [DEBUG] 15:48:54: line 36: Assertion failed: Expected number

 Line 36 below:

fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(v));

 

 

in pairs, 

I tried the one from @ Autofrank with ..id.. ..(79).. for a try, then i got the whole json string as output... 

 

 

@samuel

 

slight tweak - turning v into a number

Try this

 

lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}
for k,v in pairs(lamp_ON) do 
 if  tonumber(fibaro:getValue(v, "value")) == 0 then -- > Not 0 means "on"
	--fibaro:debug("Forgotten to Turn this Lamp OFF: " ..v);
    fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(tonumber(v)));
  end
end

 

Link to post
Share on other sites
1 hour ago, AutoFrank said:

 

@samuel

 

slight tweak - turning v into a number

Try this

 

lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}
for k,v in pairs(lamp_ON) do 
 if  tonumber(fibaro:getValue(v, "value")) == 0 then -- > Not 0 means "on"
	--fibaro:debug("Forgotten to Turn this Lamp OFF: " ..v);
    fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(tonumber(v)));
  end
end

 

 

Thanks. Yes... I didn't catch that because I tested the code on my HC2, and purely, out of habit, I defined the lua table like this:

 

lamp_ON={4,9}

You see the important difference? No quotes! So my values were all of type "number", while the original question defined them as strings! It's syntactic sugar, again!

 

 

I also fixed another issue, in case you use a wrong ID and your device doesn't exist:

 

lamp_ON = {79, 148, 153, 238, 239, 284, 298, 299, 312, 313, 314, 314, 322}
for k,v in pairs(lamp_ON) do
  if  tonumber(fibaro:getValue(v, "value")) ~= 0 then -- > 0 
    --fibaro:debug("Forgotten to Turn this Lamp OFF: " ..v);
    fibaro:debug("Forgotten to Turn this Lamp OFF: "..(fibaro:getName(v) or ""));
  end
end

This is very idiomatic Lua:

 

(fibaro:getName(v) or "")

 

and it means: get the name, or alternatively use an empty string if the name was nil. This is needed because the concatenation operator refuses to add nil to a string and aborts the script...

Link to post
Share on other sites

Thanks guys: did did the trick:

fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(tonumber(v)));
Link to post
Share on other sites
6 minutes ago, samuel said:

Thanks guys: did did the trick:

fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(tonumber(v)));

 

@samuel

 

Great news

if you use the rest of @petergebruers code it'll give you a bit more resilience

 

 

 

Link to post
Share on other sites

How to compare a value from a table, so that an action can be performed?

 

table = {
  [1] = "dim",
  [2] = "bright",
  [3] = "off",
}

 

if value from number [1] == "dim" then

-- code

end

 

thanks for your help

Link to post
Share on other sites
6 hours ago, samuel said:

How to compare a value from a table, so that an action can be performed?

(...)

 

Here's an educational example. Does this help?

 

local table = {
  [1] = "dim",
  [2] = "bright",
  [3] = "off",
}
local tmp
for k,v in ipairs(table) do
  if v  == "dim" then
    print ("Entry nr "..k.." in this table has value '"..v.."'")
    tmp=k -- if you want to use v or k outside the loop, assign it to an "outer variable"
    break -- exit the loop, if you only want the first match
  end
end
print("Variable tmp has value: "..(tmp or "nil")) -- "nil" means: the value is not in the table

 

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...