Luup : Hue Energy (watts) Calculator
-
Hi all
I’ve been trying to use the Hue api and some of my Lua knowledge to create an energy calculator based on the brightness and wattage of specific models of hue bulb registered on the hub
I’ve pretty much got there, the only stumbling block I have is how I total up all the individual bulb wattages. Can anyone help ?
It’s likely an easy thing for someone more experienced to see, but as I only dip in and dip out now and then, the correct code/syntax does not always come to mind.
Note : as I’ve tried various routes, I’ve commented some things out, but you should be able to get the idea of what I’m trying to do..
- Create device json from Hue Hub
- Decode JSON and extract key values to a table
- Calculate approx watts based on bulb model and/or watts function
- Total up watts of all bulbs that are switch on.
Code below...
local hueBridgeIP = '192.168.1.29' local hueBridgeAPI = "80uCoXjnn2LNLhxEJ9PW6nmt-G5KWQ2uP3oONAvcm0j" function getHueLight() local http = require('socket.http') local ltn12 = require('ltn12') local json = require('dkjson') t = {} local url = string.format("http://%s/api/%s/lights", hueBridgeIP, hueBridgeAPI) b, c, h = http.request{url=url, sink = ltn12.sink.table(t), method='GET'} huestring = tostring(table.concat(t)) local hue, pos, err = json.decode(huestring, 1, nil) huelights = {} for k, v in pairs(hue) do local modelno = string.gsub(hue[k]['modelid'], "%s+", "") table.insert(huelights, {k, hue[k]['state']['on'], hue[k]['name'], hue[k]['state']['bri'], modelno, hue[k]['state']['reachable']}) print(k, hue[k]['state']['on'], hue[k]['name'], hue[k]['state']['bri'], modelno, hue[k]['state']['reachable']) end end local fourpointeightwattTable = { [ 1 ] = 0.6 , [ 2 ] = 0.7 , [ 3 ] = 0.8 , [ 4 ] = 0.9 , [ 5 ] = 1.2 , [ 6 ] = 1.4 , [ 7 ] = 1.7 , [ 8 ] = 2.0 , [ 9 ] = 2.3 , [ 10 ] = 2.6 , [ 11 ] = 2.9 , [ 12 ] = 3.1 , [ 13 ] = 3.5 , [ 14 ] = 4.0 , } local sevenwattTable = { [ 1 ] = 1.6 , [ 2 ] = 1.7 , [ 3 ] = 1.8 , [ 4 ] = 1.9 , [ 5 ] = 2.2 , [ 6 ] = 2.4 , [ 7 ] = 2.7 , [ 8 ] = 3.0 , [ 9 ] = 3.5 , [ 10 ] = 3.9 , [ 11 ] = 4.5 , [ 12 ] = 5.1 , [ 13 ] = 5.5 , [ 14 ] = 6.4 , } local ninewattTable = { [ 1 ] = 1.6 , [ 2 ] = 1.7 , [ 3 ] = 1.8 , [ 4 ] = 1.9 , [ 5 ] = 2.2 , [ 6 ] = 2.5 , [ 7 ] = 2.9 , [ 8 ] = 3.5 , [ 9 ] = 3.8 , [ 10 ] = 4.6 , [ 11 ] = 5.5 , [ 12 ] = 6.5 , [ 13 ] = 7.7 , [ 14 ] = 8.5 , } function fourpointeightgetWattage(hue) local index = math.floor(hue / 16.5) if index <= 0 then return 0.2 elseif index >= 15 then return 4.8 else return fourpointeightwattTable[index] end end function sevengetWattage(hue) local index = math.floor(hue / 16.5) if index <= 0 then return 0.4 elseif index >= 15 then return 7.0 else return sevenwattTable[index] end end function ninegetWattage(hue) local index = math.floor(hue / 16.5) if index <= 0 then return 0.4 elseif index >= 15 then return 9.0 else return ninewattTable[index] end end local total = 0 local Value = 0 local huewatts = {} for k,v in pairs(huelights) do if v[2] == true and v[5] == "RS125" and v[6] == true then local watts = fourpointeightgetWattage(v[4]) local Value = tonumber(watts) local total = total + Value print("four point eight watts") print(v[5], v[4], v[2], v[3], watts) table.insert(huewatts, {v[5], v[4], v[2], v[3], watts}) --print(watts) --print(total) elseif v[2] == true and v[5] == "LWA004" and v[6] == true then local watts = sevengetWattage(v[4]) local Value = tonumber(watts) local total = total + watts print("seven watts") print(v[5], v[4], v[2], v[3], watts) table.insert(huewatts, {v[5], v[4], v[2], v[3], watts}) --print(watts) --print(total) elseif v[2] == true and v[6] == true then local watts = ninegetWattage(v[4]) local Value = tonumber(watts) local total = total + Value print("nine watts") print(v[5], v[4], v[2], v[3], watts) table.insert(huewatts, {v[5], v[4], v[2], v[3], watts}) --print(watts) --print(total) end end print(total) print(huewatts) -- confirm if table it there (TBC)
-
Hi, still hoping someone can help
-
I few things that would help:
Some report of what went wrong: whether that be a log output or code test window output. Otherwise it's unlikely your code (IMHO) will be looked at by forum members.
General readability: All variables should be declared, if at all possible, at the start before all the functions are declared. Declaring global variables inside a function is not good eg huelights. Declare them local at the start. It's important to decide if your functions should be local or global. In most cases they should be local. The master code that gets everything going should also be in a function called startup() or initialise() or similar. Then have a look at what the api call returns. -
Many thanks @a-lurker that helps, and often I’m using snippets I’ve created or found elsewhere to build new blocks of code - so the etiquette of what code pieces to put where is not always first in mind, i just try and get it to run without any errors :-). - I’ll have a go at re-structuring it based on your advice, and as for what’s local vs global, i do still struggle with the nuances of that, but i agree in the most part everything should be local, so I’ll update that.
-
Hope this is easier to follow now..
local http = require('socket.http') local ltn12 = require('ltn12') local json = require('dkjson') local hueBridgeIP = '192.168.102.29' local hueBridgeAPI = "80uCoX2LNLhxEJ9tgPW6nmt-G5KWQ2uP3oONAvcm0j" local url = string.format("http://%s/api/%s/lights", hueBridgeIP, hueBridgeAPI) local t = {} local huelights = {} local total = 0 local Value = 0 local huewatts = {} local fourpointeightwattTable = { [ 1 ] = 0.6 , [ 2 ] = 0.7 , [ 3 ] = 0.8 , [ 4 ] = 0.9 , [ 5 ] = 1.2 , [ 6 ] = 1.4 , [ 7 ] = 1.7 , [ 8 ] = 2.0 , [ 9 ] = 2.3 , [ 10 ] = 2.6 , [ 11 ] = 2.9 , [ 12 ] = 3.1 , [ 13 ] = 3.5 , [ 14 ] = 4.0 } local sevenwattTable = { [ 1 ] = 1.6 , [ 2 ] = 1.7 , [ 3 ] = 1.8 , [ 4 ] = 1.9 , [ 5 ] = 2.2 , [ 6 ] = 2.4 , [ 7 ] = 2.7 , [ 8 ] = 3.0 , [ 9 ] = 3.5 , [ 10 ] = 3.9 , [ 11 ] = 4.5 , [ 12 ] = 5.1 , [ 13 ] = 5.5 , [ 14 ] = 6.4 } local ninewattTable = { [ 1 ] = 1.6 , [ 2 ] = 1.7 , [ 3 ] = 1.8 , [ 4 ] = 1.9 , [ 5 ] = 2.2 , [ 6 ] = 2.5 , [ 7 ] = 2.9 , [ 8 ] = 3.5 , [ 9 ] = 3.8 , [ 10 ] = 4.6 , [ 11 ] = 5.5 , [ 12 ] = 6.5 , [ 13 ] = 7.7 , [ 14 ] = 8.5 } local function getHueLight() local b, c, h = http.request{url=url, sink = ltn12.sink.table(t), method='GET'} local huestring = tostring(table.concat(t)) local hue, pos, err = json.decode(huestring, 1, nil) for k, v in pairs(hue) do local modelno = string.gsub(hue[k]['modelid'], "%s+", "") table.insert(huelights, {k, hue[k]['state']['on'], hue[k]['name'], hue[k]['state']['bri'], modelno, hue[k]['state']['reachable']}) --print(k, hue[k]['state']['on'], hue[k]['name'], hue[k]['state']['bri'], modelno, hue[k]['state']['reachable']) end end getHueLight() function fourpointeightgetWattage(hue) local index = math.floor(hue / 16.5) if index <= 0 then return 0.2 elseif index >= 15 then return 4.8 else return fourpointeightwattTable[index] end end function sevengetWattage(hue) local index = math.floor(hue / 16.5) if index <= 0 then return 0.4 elseif index >= 15 then return 7.0 else return sevenwattTable[index] end end function ninegetWattage(hue) local index = math.floor(hue / 16.5) if index <= 0 then return 0.4 elseif index >= 15 then return 9.0 else return ninewattTable[index] end end for k,v in pairs(huelights) do if v[2] == true and v[5] == "RS125" and v[6] == true then local watts = fourpointeightgetWattage(v[4]) local Value = tonumber(watts) local total = total + Value print("four point eight watts") print(v[5], v[4], v[2], v[3], watts) table.insert(huewatts, {v[5], v[4], v[2], v[3], watts}) --print(watts) --print(total) elseif v[2] == true and v[5] == "LWA004" and v[6] == true then local watts = sevengetWattage(v[4]) local Value = tonumber(watts) local total = total + watts print("seven watts") print(v[5], v[4], v[2], v[3], watts) table.insert(huewatts, {v[5], v[4], v[2], v[3], watts}) --print(watts) --print(total) elseif v[2] == true and v[6] == true then local watts = ninegetWattage(v[4]) local Value = tonumber(watts) local total = total + Value print("nine watts") print(v[5], v[4], v[2], v[3], watts) table.insert(huewatts, {v[5], v[4], v[2], v[3], watts}) --print(watts) --print(total) end end print(total) print(huewatts)
The print output is as follows.
LuaTest 1.7
Lua file: /etc/cmh-ludl/luatest.lua
Results
No errors
Runtime: 85.9 ms
Code returned: nilPrint output
nine watts LWB004 254 true Table Light 9 nine watts LWB010 254 true Wall Light 1 9 nine watts LWB006 254 true Wall Light 2 9 nine watts LWB006 254 true Wall Light 3 9 nine watts LWB010 254 true Wall Light 4 9 0 table: 0x28953e0
-
I would have thought that the code at:
for k,v in pairs(huelights) do etc, etc
would be in your startup() or initialise() function and getHueLight() would be at the start of that function. ie the first thing done is to get the info from the api. Now getHueLight() is effectively hidden away from view unless the code is carefully looked at. Also there not a single comment in the code - that's a fail.
In general all variables and all functions should be declared local. You still have global functions.
At the very end print() prints the pointer to the table. This will print its contents: print(pretty(huewatts))
Total is equal to zero. You need to work out why it's zero by the use of more debug code. "nine watts" prints so you that section of the code is being called.
-
Thanks, this is good stuff - my plan for this chunk of code was so I could eventually call the total Hue wattage value via a url handle, but I’m keen to learn the correct structure for this sort of thing.
If i was to list out the steps, (the order in which the blocks/sections of code should appear/occur) is this correct ?
- declare all variables first
- declare all functions next
- create a startup() or initialise() function to gather and process all any required information using the earlier functions a variables that were declared.
- add comments throughout to help explain what’s being done.
-
Your list above is generally how it's done but style does vary amongst programmers and programming languages. There is always room for improvement, no matter one's own skill level. I still feel like a beginner when I look at the quality of code and the depth of understanding that I sometimes come across in other people's work. It takes practice and consistency in approach.
-
Some relevant comments here…
…ironically, spelling mistakes are also an issue in coding, and the word in the title should, of course, be “harmful” !
-
Thanks all,
@a-lurker - I’ll see what I can do to update my original code so it aligns with those guidelines so I can get help with totalling things up.
@akbooer - I really struggle condensing my code down to address duplication, I’m just grateful to get things I want to do to work, every time I’ve tried to optimise, I’ve broken it and then I have to work my way back. I completely understand the logic and the benefit overall. -
At last - It works !!!
Any suggestions on structure or ways to optimise always appreciated.
local http = require('socket.http') local ltn12 = require('ltn12') local json = require('dkjson') local url = "http://192.168.102.29/api/80uCoX2LNLhxEJ9PW6nmt-G5KWQ2uP3oONAvcm0j/lights" -- tables local t = {} -- calculations local total = 0 local fourpointeightwattTable = { [ 1 ] = 0.6 , [ 2 ] = 0.7 , [ 3 ] = 0.8 , [ 4 ] = 0.9 , [ 5 ] = 1.2 , [ 6 ] = 1.4 , [ 7 ] = 1.7 , [ 8 ] = 2.0 , [ 9 ] = 2.3 , [ 10 ] = 2.6 , [ 11 ] = 2.9 , [ 12 ] = 3.1 , [ 13 ] = 3.5 , [ 14 ] = 4.0 } local sevenwattTable = { [ 1 ] = 1.6 , [ 2 ] = 1.7 , [ 3 ] = 1.8 , [ 4 ] = 1.9 , [ 5 ] = 2.2 , [ 6 ] = 2.4 , [ 7 ] = 2.7 , [ 8 ] = 3.0 , [ 9 ] = 3.5 , [ 10 ] = 3.9 , [ 11 ] = 4.5 , [ 12 ] = 5.1 , [ 13 ] = 5.5 , [ 14 ] = 6.4 } local ninewattTable = { [ 1 ] = 1.6 , [ 2 ] = 1.7 , [ 3 ] = 1.8 , [ 4 ] = 1.9 , [ 5 ] = 2.2 , [ 6 ] = 2.5 , [ 7 ] = 2.9 , [ 8 ] = 3.5 , [ 9 ] = 3.8 , [ 10 ] = 4.6 , [ 11 ] = 5.5 , [ 12 ] = 6.5 , [ 13 ] = 7.7 , [ 14 ] = 8.5 } local function fourpointeightgetWattage(hue) local index = math.floor(hue / 16.5) if index <= 0 then return 0.2 elseif index >= 15 then return 4.8 else return fourpointeightwattTable[index] end end local function sevengetWattage(hue) local index = math.floor(hue / 16.5) if index <= 0 then return 0.4 elseif index >= 15 then return 7.0 else return sevenwattTable[index] end end local function ninegetWattage(hue) local index = math.floor(hue / 16.5) if index <= 0 then return 0.4 elseif index >= 15 then return 9.0 else return ninewattTable[index] end end local function getHueLight() local b, c, h = http.request{url=url, sink = ltn12.sink.table(t), method='GET'} local huestring = tostring(table.concat(t)) local hue, pos, err = json.decode(huestring, 1, nil) for k, v in pairs(hue) do local modelno = string.gsub(hue[k]['modelid'], "%s+", "") local brightness = hue[k]['state']['bri'] local bulbname = hue[k]['name'] if hue[k]['state']['on'] == true and hue[k]['state']['reachable'] == true then if modelno == "RS125" then local watts = tonumber(fourpointeightgetWattage(brightness)) -- uncomment to debug bulb identification and wattage calculations -- print(bulbname, "is ON ", modelno, "should be a 4.8 watt RS125 bulb, at", brightness, "brightness, resulting in", watts, "watts being used", "Hue ID = "..k) total = total + watts elseif modelno == "LWA004" then local watts = tonumber(sevengetWattage(brightness)) -- uncomment to debug bulb identification and wattage calculations -- print(bulbname, "is ON ", modelno, "should be a 7 watt LWA004 bulb, at", brightness, "brightness, resulting in", watts, "watts being used", "Hue ID = "..k) total = total + watts else local watts = tonumber(ninegetWattage(brightness)) -- uncomment to debug bulb identification and wattage calculations -- print(bulbname, "is ON ", modelno, "should be a every other 9 watt bulb, at", brightness, "brightness, resulting in", watts, "watts being used", "Hue ID = "..k) total = total + watts end else -- uncomment to debug bulb identification -- print(bulbname, "is OFF", modelno, " Hue ID = ".. k) end -- uncomment to debug incremental calculations -- print("Sub total = ", total) end print("--------------------") print("Total wattage consumed by Hue lights = ", total) print("--------------------") end getHueLight()
-
Good news - glad it works for you now. Also looks a lot better than the original.
Now as akbooer pointed out; there is huge amount of redundancy in the code, with this as a starting point: fourpointeightgetWattage, sevengetWattage & ninegetWattage. Looks like you need one function that get's a 4.8, 7 or 9 passed to it?
Now as part of readability, is the use of indentation. The code is inconsistent - sometimes one tab has been used and sometimes two tabs plus there is mixture of spaces and tabs used for indentation. Personally I believe tabs are the root of all evil - I use spaces only but each to his/her own. Problem is that different editors equate a tab to different amount of spaces. So when you mix tabs and spaces and use a different editor; the indentation changes. Sounds pedantic - yes it is but then have a look at how Python works.
Also as part of readability is the use of capitals in identifiers. So fourpointeightgetWattage has one capital letter. A Watt does have a capital but I would expect fourPointEightGetWattage. Once again pedantic but it all adds up to good code readability.
-
@a-lurker points you in the right direction with their wise comments.
Nonetheless, previous experience tells me that you are often short of time to follow this type of advice, and so, doing much of the work for you, I'd suggest that something along these lines addresses many of those issues (although I have not tested this.) You'll note that the reduction in length and repetition, plus judicious use of Lua language features makes this very much more readable – albeit that it lacks error checking and comments – and perhaps less error-prone...
local WattTable = { four = {min = 0.2, max = 4.8; 0.6, 0.7, 0.8, 0.9, 1.2, 1.4, 1.7, 2.0, 2.3, 2.6, 2.9, 3.1, 3.5, 4.0}, seven = {min = 0.4, max = 7.0; 1.6, 1.7, 1.8, 1.9, 2.2, 2.4, 2.7, 3.0, 3.5, 3.9, 4.5, 5.1, 5.5, 6.4}, nine = {min = 0.4, max = 9.0; 1.6, 1.7 ,1.8 ,1.9, 2.2, 2.5, 2.9, 3.5, 3.8, 4.6, 5.5, 6.5, 7.7, 8.5}, } local Rating = {RS125 = "four", LWA004 = "seven"} local function getWattage(brightness, Watts) local index = math.floor(brightness / 16.5) return (index <= 0) and Watts.min or (index >= 15) and Watts.max or Watts[index] end local function getTotal (hue) local total = 0 for _, v in pairs(hue) do local modelno = v.modelid: gsub("%s+", "") local brightness = v.state.bri if v.state.on and v.state.reachable then local rating = Rating[modelno] or "nine" local watts = getWattage(brightness, WattTable[rating]) total = total + watts end end return total end local function getHueLight(url) local _, huestring = luup.inet.wget (url) local hue = json.decode(huestring) local total = getTotal (hue) print ("--------------------") print ("Total wattage consumed by Hue lights = ", total) print ("--------------------") end getHueLight "http://192.168.102.29/api/80uCoX2LNLhxEJ9PW6nmt-G5KWQ2uP3oONAvcm0j/lights"
-
parkercreplied to akbooer on Jun 13, 2021, 4:50 PM last edited by parkerc Jun 13, 2021, 12:53 PM
Thanks again @a-lurker and @akbooer !
@akbooer said in Luup : Hue Energy (watts) Calculator:
previous experience tells me that you are often short of time to follow this type of advice,
You’re correct, and not for any other reason other than I only have very limited personal time away from work or the family to progress anything in Lua, and even then - I’m often pulling together chunks of code I’ve created myself or found online, just to make something work (quick and dirty).
Quality is very much a nice to have for me, and due to the gaps between when I can work on these things, I’m often struggling to recall the basics of the language itself, let alone the quality aspects. But I’m improving gradually (at glacier speed for you all, I must admit
)
Hence I appreciate it must be very frustrating for those that understand it all, as my code must looks like a 5 year old’s drawing a picture - in that the idea/intent is there, but the attention to detail and perspective is severely lacking !
Just know that you make a grown man (with a 5 year old’s artistic (Lua) skills) very happy when you help me learn new things, but most importantly - is you’ve help me get something I’ve been dreaming about over various long days to finally work!
Thank you so much..
-
Final piece of the puzzle now is to make the watts visible via a luup.handler.request, and that’s where I’ve got an error I can’t work out..
I’ve updated the code as follows..
- Placed it in a new file call xxhuewatts.lua
- made the function call just the name not passing parameters
- added a ‘return ‘ the present the watts value in a html format
- updated the functions to all be global
- added the following handler into the startup Lua
local hueenergy = require("xxhuewatts") -- lua filename/module luup.register_handler ("hueenergy.getHueLight", "huewatts") -- module, function + handler name
Updated code is as follows
module("xxhuewatts", package.seeall) local json = require('dkjson') local WattTable = { four = {min = 0.2, max = 4.8; 0.6, 0.7, 0.8, 0.9, 1.2, 1.4, 1.7, 2.0, 2.3, 2.6, 2.9, 3.1, 3.5, 4.0}, seven = {min = 0.4, max = 7.0; 1.6, 1.7, 1.8, 1.9, 2.2, 2.4, 2.7, 3.0, 3.5, 3.9, 4.5, 5.1, 5.5, 6.4}, nine = {min = 0.4, max = 9.0; 1.6, 1.7 ,1.8 ,1.9, 2.2, 2.5, 2.9, 3.5, 3.8, 4.6, 5.5, 6.5, 7.7, 8.5}, } local Rating = {RS125 = "four", LWA004 = "seven"} function getWattage(brightness, Watts) local index = math.floor(brightness / 16.5) return (index <= 0) and Watts.min or (index >= 15) and Watts.max or Watts[index] end function getTotal (hue) local total = 0 for _, v in pairs(hue) do local modelno = v.modelid: gsub("%s+", "") local brightness = v.state.bri if v.state.on and v.state.reachable then local rating = Rating[modelno] or "nine" local watts = getWattage(brightness, WattTable[rating]) total = total + watts end end return total end function getHueLight() local _, huestring = luup.inet.wget ("http://192.168.102.29/api/80uCoX2LNLhxEJ9PW6nmt-G5KWQ2uP3oONAvcm0j/lights") local hue = json.decode(huestring) local total = getTotal (hue) print ("--------------------") print ("Total wattage consumed by Hue lights = ", total) print ("--------------------") local html = "<html><head>" .. "</head>" .. "<body>" .. "<b>" .. tostring(total) .. "</b>" .. "</body></html>" return html, "text/html" end --getHueLight()
It report ‘Handler failed’ and the error message bin the logs is as follows.
01 06/13/21 19:59:25.921 LuaInterface::CallFunction_Request function hueenergy.getHueLight name huewatts failed attempt to call a nil value <0x74d68520> 02 06/13/21 19:59:25.922 JobHandler_LuaUPnP::REQ_Handler handler failure for lr_huewatts <0x74d68520>
I know the code works when I run it, and I think I’ve tried everything I can think of,.. is there anything else anyone can suggest I check?
-
I don’t know, but I can try a guess… that it doesn’t like your compound syntax for the global function name.
Can you give this a go:
local hueenergy = require("xxhuewatts") -- lua filename/module GetHueLight = hueenergy.getHueLight luup.register_handler ("GetHueLight", "huewatts")
-
Thank you so much @akbooer , that made it work ! But what’s weird, is that I had tried various different ways too, with the following the closest to yours .
local hueenergy = require("xxhuewatts") -- lua filename/module huepower = hueenergy.getHueLight -- call lua filename/module and required function luup.register_handler ("huepower", "huewatts") -- register http handler
When it comes to the ‘syntax’ , why did it like “ GetHueLight” and not “ huepower”
Does Lua (Vera/OpenLuup?) require me to use capital letters at certain times ?
10/17