Page 1 of 1

Use string to get defined variable

Posted: Thursday 28 June 2018 22:56
by jake
I have declared 5 variables (which define the 5 micro solar inverter devices) like this:

Code: Select all

local inv1 = domoticz.devices('11000xxx power')
local inv2 = domoticz.devices('110007xxx power')
local inv3 = domoticz.devices('1100xxx2 power')
local inv4 = domoticz.devices('100xxxx9 power')
local inv5 = domoticz.devices('100Xxxx8 power')
then I would like to run a loop 'for i = 1, 5 do' (do some calculations with the daily generated energy per micro inverter, by comparing the daily energy generation from inverter 1 to 4 with the value of inverter 5).
So I set up my code like this:

Code: Select all

for i = 1,4 do
inverterName = 'inv'..i
inverterValue = inverterName.WhToday
etc
etc
Unfortunately the value of 'inverterValue' is 'nil', while it has the correct value when I use inv1.WhToday.
My conclusion is that 'inverterName' doesn't inherit the defined variable in the for..next loop.

I looked all over the place, found tips about using global variables (don't need those) or tables (don't understand how to apply them).
I could already add the '.WhToday' to my variable declaration, but I think it end up with the same trouble that the generated string inv1 is not equal to variable inv1.

Re: Use string to get defined variable

Posted: Thursday 28 June 2018 23:48
by waaren
jake wrote: Thursday 28 June 2018 22:56 ....
I looked all over the place, found tips about using global variables (don't need those) or tables (don't understand how to apply them).
....
Please have a look at below example of storing devices (objects ) in a Lua table and get some of the attributes of the devices in a for loop.
as an Array like Lua table

Code: Select all

local testArrayTable = {    domoticz.devices('Testje_1'),   -- This is a syntax shortcut for [1]= domoticz.devices('Testje_1')
                            domoticz.devices('Testje_2'),   -- This is a syntax shortcut for [2]= domoticz.devices('Testje_2')
                            domoticz.devices('Testje_3')    -- This is a syntax shortcut for [3]= domoticz.devices('Testje_3')
                        }
            
for i = 1,#testArrayTable do                  -- #testArrayTable is last element of testTable
          domoticz.log("Name of object (device): " .. testArrayTable[i].name,domoticz.LOG_INFO)
end
or as a Lua table with named keys

Code: Select all

local testKeyTable = {}                               --- In this table the key will be a string (test1, test2, etc)
testKeyTable.test1 = domoticz.devices('Testje_1')
testKeyTable.test2 = domoticz.devices('Testje_2')
testKeyTable.test3 = domoticz.devices('Testje_3')
            
for key,deviceObject in pairs(testKeyTable) do            -- Loop over all elements in the table
   domoticz.log("Name of key: object (device): " .. key .. ": " .. deviceObject.name,domoticz.LOG_INFO)
end

Re: Use string to get defined variable

Posted: Friday 29 June 2018 9:07
by jake
waaren wrote: Thursday 28 June 2018 23:48 Please have a look at below example of storing devices (objects ) in a Lua table and get some of the attributes of the devices in a for loop.
as an Array like Lua table
...
Thanks, this saved my day. I am glad with your help, because the online examples didn't work for me.

One more question:
Now I am able to read my inverter value in the for..next loop, I want to store it in a persistent data field in dzVents.
I have defined the different parts in the beginning of my script:

Code: Select all

        data = {
                Ratio1to5 = { history = true, maxItems = 7 },
                Ratio1to5dev = { history = true, maxItems = 7, maxHours = 7*24 },
                Ratio2to5 = { history = true, maxItems = 7 },
                Ratio2to5dev = { history = true, maxItems = 7, maxHours = 7*24 },
                Ratio3to5 = { history = true, maxItems = 7 },
                Ratio3to5dev = { history = true, maxItems = 7, maxHours = 7*24 },
                Ratio4to5 = { history = true, maxItems = 7 },
                Ratio4to5dev = { history = true, maxItems = 7, maxHours = 7*24 },
and now try to call out the average values of the stored data (with a check for nil value afterwards to catch the initial call of the script with an empty table):

Code: Select all

        RatioName = 'Ratio'..i..'to5'
        print('Ratio name is '..RatioName)
        RatioAverage = domoticz.data.RatioName.avg()
        if RatioAverage == nil then RatioAverage = Ratio end
.
However, the domoticz log comes back with this error about the code line "RatioAverage = domoticz.data.RatioName.avg()":
"... attempt to index field 'RatioName' (a nil value)"

I'm guessing that the error is in the same ball field as the first question, but maybe slightly different? RatioName is nog recognized (for i = 1) as Ratio1to5 and therefore doesn't get the average out of the stored data either.

Re: Use string to get defined variable

Posted: Friday 29 June 2018 21:09
by waaren
jake wrote: Friday 29 June 2018 9:07 ....
One more question:
Now I am able to read my inverter value in the for..next loop, I want to store it in a persistent data field in dzVents.
....
I'm guessing that the error is in the same ball field as the first question, but maybe slightly different? RatioName is nog recognized (for i = 1) as Ratio1to5 and therefore doesn't get the average out of the stored data either.
@jake, according to google using dynamic names for Lua variable is possible but considered bad coding practice. I am not sure the tricks with _G will even work in dzVents.
I also would not know how to tackle your requirement with dzVents history aware persistent variables. I am sure there are other users on this forum that will know how to do that.
My approach to this is based on a table in dzVents persistent data. The code below does have its own housekeeping, to kind of mimic the behavior of dzVents history aware data.
Please have a look and feel free to ask when things are not clear. Of course I am also open for improvements of which I am sure they can be made.

Code: Select all

return {
    on  = {     devices     =   { "string2VarSwitch" }},  -- used during development and test
     
                data        =   { ratios = { initial = {}}},   -- Table 

               logging      =   {   level     =   domoticz.LOG_INFO,           -- Change to LOG_DEBUG to check what happens
                                    marker    =   "ratioTable"    },
                
    execute = function(dz, trigger)
        
        local houseKeepingDone    
        local secondsToKeep, numberToKeep = 3600, 10          -- during test and development max. 1 hour or 10 cycles whatever comes first
        
        local function removeRatios(secondsToKeep,numberToKeep)     -- based on time and number  
            secondsToKeep = secondsToKeep or ( 7 * 24 * 3600 )      -- defaults to a week
            numberToKeep = numberToKeep or 100                      -- defaults to 100 
        
            for i = #dz.data.ratios,1,-1 do   -- Backwards to make sure table.remove works for subsequent rows
                dz.data.ratios[i].sequence = dz.data.ratios[i].sequence + 1
                if  (dz.data.ratios[i].time < os.time(os.date('*t')) - secondsToKeep) or 
                    (dz.data.ratios[i].sequence > numberToKeep) then  
                   table.remove(dz.data.ratios,i)       -- table housekeeping
                end
            end
            return "Done"      -- You can return anything as long as it is not nil or false
        end  
    
        local function addRatio(parsedKey,parsedValue)   -- Add ratios with time stamps  
            if not(houseKeepingDone) then
                houseKeepingDone = removeRatios(secondsToKeep,numberToKeep)
            end 
            table.insert(dz.data.ratios,{
                                          sequence      = 0,                         -- Needed for housekeeping ( numbertoKeep will look at this)
                                          key           = parsedKey,
                                          value         = parsedValue,
                                          time          = os.time(os.date('*t'))  -- Needed for housekeeping ( secondsToKeep will look at this)
                                        })
        end						-- You will find the table in domoticz dir/dzvents/scripts/data 
          
        local function getAverageRatio(key,seconds)
            seconds = seconds or ( 7 * 24 * 3600 )  -- Defaults to a week
            local total,count = 0, 0
            for i = 1,#dz.data.ratios,1  do   
                if dz.data.ratios[i].key == key and  dz.data.ratios[i].time >= os.time(os.date('*t')) - seconds then  
                   total = total + dz.data.ratios[i].value
                   count = count + 1
                end
            end
            if count ~= 0 then return dz.utils.round(total / count,1)
            else return 0 end
        end              

        -- Add ratios (ratioName, ratioValue    
        for i =1,5 do
            addRatio('ratio'..i..'to5',math.random(100))            
        end

        for i =1,5 do
            dz.log("Average for: " .. "Ratio"..i.."to5: " ..getAverageRatio("ratio"..i.."to5"),dz.LOG_INFO)
        end
    end
}   

Re: Use string to get defined variable

Posted: Saturday 30 June 2018 13:40
by jake
@waaren, thanks for this example script. You put a lot of work in it.
I find it quite difficult to grasp the steps in your script, but I think I will just need to implement it and adapt to my original script needs.

@dannybloe I wonder if it is really necessary to add all the functions to each script to have more flexible persistent data access. Is there currently a way around, or is this for the dzVents 'wishlist'.