Hi
I’ve been investing a lot of time recently trying to digitise everything I can, and my current adventure is with all my paper documents.
My tool of choice is Paperless-ngx, which so far is an amazing open source solution, and highly recommended if you’re interest in such a thing…
Such a move to Paperless would not be complete without some form of integration into Vera / openLuup, and thankfully it has a nice Rest API (https://paperless-ngx.readthedocs.io/en/latest/api.html) I can use. So far I’ve been able to make use the GET requests, to provide document counts etc., but it’s the mutipart/form data piece where I’m struggling..
The Curl command to upload a document, is as follows
curl -H "Authorization: Basic Y2hyaXM62tgbsgjunotmeY2hyaXNob3N0aW5n" -F "title=Companies House File 10" -F "correspondent=12" -F "document=@/mnt/nas/10.pdf" http://192.168.102.134:8777/api/documents/post_document/But, I’d like to know how to do this with Lua code, more just to help me in my Lua learning curve, and what I thought would be reasonably straight forward thing to do, has turned out to be quite the opposite 🙂 Sending a multipart form data submission via http.request, is not as straight forward as I hoped. If anyone has any time to help, I’d appreciate some guidance on what I’m missing with the following, as based on the feedback, it looks like it not seeing the file (which I think I’ve confirmed by trying to sending both the curl and Lua commands to httbin.org ) …
local http = require("socket.http") local ltn12 = require("ltn12") local mime = require("mime") local lfs = require("lfs") local username = "username" local password = "password" local httpendpoint = 'http://httpbin.org/post' local filepath = "/mnt/nas/10.pdf" local file = io.open(filepath, "rb") local contents = file:read( "*a" ) -- https://stackoverflow.com/questions/3508338/what-is-the-boundary-in-multipart-form-data local boundary = "-----BoundaryePkpFF7tjBAqx29L" local send = "--"..boundary.. "\r\nContent-Disposition: form-data; title='Companies House File'\r\n" .."----"..boundary.. "\r\nContent-Disposition: form-data; document="..filepath.. "\r\nContent-type: application/pdf".."\r\n" ..contents.."\r\n" .."------"..boundary.."--\r\n" -- Execute request (returns response body, response code, response header) local resp = {} local body, code, headers, status = http.request { url = httpendpoint, method = 'POST', headers = { ["Authorization"] = "Basic " .. (mime.b64(username ..":" .. password)), ["Content-Length"] = lfs.attributes(filepath, 'size'), ['Content-Type'] = "multipart/form-data; boundary="..boundary, }, -- source = ltn12.source.file( io.open(filepath,"rb") ), source = ltn12.source.file( io.open(send) ), sink = ltn12.sink.table(resp) } print(body, code, headers, status) print(table.concat(resp)) if headers then for k,v in pairs(headers) do print(k,v) end endHi,
I’m sure this is not a unique question about Vera (& Lua 5.1) and I’ve seen this reference in place,, but I can’t seem to find a fix to make an https download request work..
To give you an example, the code below aims to download 2 different files, the http call works fine, returning a 200 code, but the https one doesn’t, it doesn’t return anything ?
Please could some highlight what I’m missing?
print("--------DOWNLOAD http---------") local http = require("socket.http") local body, code = http.request("http://pbs.twimg.com/media/CCROQ8vUEAEgFke.jpg") print(code) if not body then error(code) end local f = assert(io.open('mnt/nas/webtest1.jpg', 'wb')) -- open in "binary" mode f:write(body) f:close() print("--------DOWNLOAD https---------") local https = require "ssl.https" --luasec local body, code = https.request("https://u.cubeupload.com/jbcooper/16146313918060.jpg") print(code) if not body then error(code) end local f = assert(io.open('mnt/nas/webtest2.jpg', 'wb')) -- open in "binary" mode f:write(body) f:close()Hi
I’m trying out the Fitbit api, not only to extract some activity data for a potential plugin, but also to learn more about OAuth 2.0. More details can be found here..
Fitbit Development: Getting Started Fitbit Development: Getting StartedYou'll fit in here. Using JavaScript, CSS, and SVG, developers now have a fast, easy way to build apps and clock faces for Fitbit OS.
In addition to having a fitbit account, to get going with any integration, you need to register your app, via here..
LoginWith an app registered, I’ve called mine “VERA” , I’ve written chunks of Lua code to support the various steps, (completed manually) my goal now is to try and put them all together into functions to be called in sequence on my Vera and/or OpenLuup.
Where I’m getting stuck is with the authorisation code step, which Fitbit provides appended to the domain name I registered with them when registered my app ..
Here’s an example of a redirect URL that Fitbit will redirect the user too, with their associated authorisation code, which I need in the process later on to obtain the required access token. (More specific details are here - https://dev.fitbit.com/build/reference/web-api/developer-guide/authorization/ )
https://myapp.com/callback?code=d62d6f5bdc13df79d9a5f#_=_And it’s the d62d6f5bdc13df79d9a5f part of that new page/URL which I need..
How best do i capture/process the URL call Fitbit issues?
I assume this is where a luup.register_handler would come in, and I could register my Vera handle (using https ?, as that’s required by Fitbit) as the redirect domain/url e.g.
https://veraip:3480/data_request?id=lr_fitbit&So Fitbit would redirect to..
https://veraip:3480/data_request?id=lr_fitbit&/?code=d62d6f5bdc13df79d9a5f#_=_But that doesn’t seem to work..
Any suggestions on how to do this would be appreciated..
function FitbitRequestHandler(lul_request, lul_parameters, lul_outputformat) for k,v in pairs(lul_parameters) do luup.log ('fitbit_Handler: parameters are: '..tostring(k)..'='..tostring(v)) end if next(lul_parameters) == nil then luup.log("lul_parameters Table is empty") end local html = "</html><head>" .. "</head>" .. "<body>" .. "PRINTING" .. "\n" .. " lul_request: " .. tostring(lul_request) .. "\n" .. " lul_parameters: " .. tostring(lul_parameters) .. "\n" .. " lul_outputformat: " .. tostring(lul_outputformat) .. "\n" .. "</body></html>" return html, "text/html" end luup.register_handler("FitbitRequestHandler", "fitbit")Thanks is advance…
Hi,
Please could people share how they are presenting and handling errors in their Vera / OpenLuup plugins, I’ve tried to look through various Implementation files, but very few seem to have anything, other than perhaps a luup.task (which for some reason doesn’t work for me) ?
Below is the startup and check/validation functions for a plugin I’m working on..
function checkIPPowerSetUp(lul_device) log("Checking if parent device is configured correctly...") ipAddress = luup.devices[lul_device].ip -- check if parent device has an ip address assigned if ipAddress == nil or ipAddress == "" then -- if not stop and present error message luup.task('ERROR: IP Address is missing',2,'IPPower',-1) debug("ERROR: IP Address is missing " ..ipAddress.. " unable to progress") return false else -- if one is provided, present success message luup.task('IP Address for IPPower 9258 present, setup continues',4,'IPPower',-1) debug("IPPower StartUp activated - Device #" .. lul_device .. " now creating children") createChildIPPowerOutlets(lul_device) luup.call_delay("IPPowerPoller", 5) return true end end function IPPowerStartup(lul_device) -- set attributes for parent device luup.attr_set( "name", "IPPower 9258", lul_device) luup.attr_set( "category_num", "3", lul_device) luup.attr_set( "subcategory_num", "1", lul_device) luup.variable_set("urn:nodecentral-net:serviceId:IPPower1", "Icon", 1, lul_device) luup.variable_set("urn:nodecentral-net:serviceId:IPPower1", "PluginVersion", PV, lul_device) checkIPPowerSetUp(lul_device) log("Start up, Parent device created...") end </functions> <startup>IPPowerStartup</startup>What’s the best way to present errors to the user, and make iot clear what they need to do ?
I'm struggling to fix the code attached here
https://github.com/dbochicchio/vera/blob/master/OpenSprinkler/L_VeraOpenSprinkler1.lua#L270,L429
Under certain circumstances (thanks @DesT!) I see that it's causing a luup reload infinite cycle, because child devices are being created, then deleted. Specifically, I see this in the logs:
2020-06-01 19:35:30.482 luup.chdev.sync:: [102] Open Sprinkler Test, syncing children 2020-06-01 19:35:30.482 openLuup.chdev:: deleting [413] Daily Garden 2020-06-01 19:35:30.482 openLuup.chdev:: deleting [414] Water Level 2020-06-01 19:35:30.482 openLuup.chdev:: deleting [411] Daily Cedars 2020-06-01 19:35:30.482 openLuup.chdev:: deleting [412] St-Eustache Rules AM 2020-06-01 19:35:30.482 openLuup.luup:: device 102 'Open Sprinkler Test' requesting reloadThe devices are created (look at line 357/410), but when I call luup.chdev.sync, they got deleted. Any hints? Thanks
Hi
I’m putting the finishing touches to a HDMI Matrix plugin, and while I have it working well - I can’t find anything on the Vera/MIOS wikis that tells me how I can show if a UI button is pressed or not (to reflect a input choice that is selected)
Here’s a screenshot of the child outputs I’ve created for the Matrix switcher. (The plugin creates a parent device and then 4 children to show the Matrix Outputs - parent is not shown - and the idea is that’s you select the input number for the respective output)
BB5B6001-294E-4AE9-A095-D2E639F56B9C.jpeg
Here is an extract of the button element in the associated JSON file used for the child devices..
{ "ControlGroup": "1", "ControlType": "button", "top": "0", "left": "0", "Label": { "lang_tag": "Input1", "text": "Input1" }, "Display": { "Top": 60, "Left": 50, "Width": 75, "Height": 20 }, "Command": { "Service": "urn:nodecentral-net:serviceId:SYMatrix1", "Action": "SYCall", "Parameters": [ { "Name": "action", "Value": "I1" } ] } },The associated command calls the parent implementation file SYCall - (The value “I1” relates to a element of the URL that needs to be called to select that associated input)
<action> <serviceId>urn:nodecentral-net:serviceId:SYMatrix1</serviceId> <name>SYCall</name> <run> SYMatrix.SYCall(lul_device, lul_settings.action, lul_settings) </run> </action>And then the Lua file (L_****) has holds the SYCall function
function SYCall (deviceNo, action, settings) ….. endI’ve just started to have a play with iOS Shortcut and one of my test cases is to see if I can do the following
select a a file (.lua or .txt) on my iPad extract the entire script/contents encode the data obtained append the encode content to the end of the RunLua action. call the URL http://myIP/data_request?id=lu_action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=RunLua&Code=Below is a screenshot of the workflow I have built in Shortcuts, which seems pretty straight forward, and it does generate a encoded URL, however the actual request itself does not complete. I get an ‘OK’ response back eventually, but the code is not actioned, and the logs don’t seem to tell me anything..
FAB8DB5F-9003-4624-BCF3-8F536A660D21.jpeg
The code I’m extracting from the Lua file works fine in the test Lua window, so i’m not sure what’s occurring differently, between the two routes, maybe it’s the encryption being used ?..
Is anyone able to help me test this, and see if they can generate a long / multi line RunLua encoded URL (if not via Shortcut, then by some that can help me with the root cause to then address that in Shortcut
I have a couple of calendar URLs I’m trying to call, and both work when i enter the URL into the browser, however when I try to call them via Lua and http(s).request, only one of them works ? Please could someone help me understand why, and what needs to be done differently to make the other one work ?
local socket = require 'socket' local http = require "socket.http" local ssl = require 'ssl' local https = require 'ssl.https' local calendarUria = "https://ics.fixtur.es/v2/ipswich-town.ics" local calendarUrib = "https://calendar.google.com/calendar/ical/en-gb.uk%23holiday%40group.v.calendar.google.com/public/basic.ics" print("Fixtur.es Football (URL A) returns the following?") local responseBodya, responseCodea, responseHeadersa = https.request(calendarUria) print(responseBodya, responseCodea, responseHeadersa) print("Google UK Holidays (URL B) returns the following?") local responseBodyb, responseCodeb, responseHeadersb = https.request(calendarUrib) print(responseBodyb, responseCodeb, responseHeadersb)If you run the code above, only the second Google (URL `B) returns anything..
Hi
Picking up from another thread this is to focus on the creation of the 4 key elements of a new Plugin for my Aviosys IPPower 9258, which are quite old, and I don’t believe are made anymore, but they seem to have a simple http api interface to cut my teeth on with a plugin.
First the device file, which is to support the creation/set up of the device, and here I explain what it is, tell Vera what the implementation file and .json file is, and also leverage the pre-existing Switch and HA capabilities..
Device File - D_IPPower.xml
<?xml version="1.0"?> <root xmlns="urn:schemas-upnp-org:device-1-0"> <specVersion> <major>1</major> <minor>0</minor> </specVersion> <device> <deviceType>urn:nodecentral-net:device:IPPower:1</deviceType> <staticJson>D_IPPower1.json</staticJson> <manufacturer>Aviosys</manufacturer> <modelDescription>IPPowerSwitch</modelDescription> <modelName>IPPower9258</modelName> <handleChildren>1</handleChildren> <implementationList> <implementationFile>I_IPPower1.xml</implementationFile> </implementationList> <serviceList> <service> <serviceType>urn:schemas-upnp-org:service:SwitchPower:1</serviceType> <serviceId>urn:upnp-org:serviceId:SwitchPower1</serviceId> <SCPDURL>S_SwitchPower1.xml</SCPDURL> </service> <service> <serviceType>urn:schemas-micasaverde-com:service:HaDevice:1</serviceType> <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId> <SCPDURL>S_HaDevice1.xml</SCPDURL> </service> </serviceList> </device> </root>Next it is the Implementation file, which thanks to the feedback on this forum, will focus on a specific Lua file, where all the code will be, the only thing I need to add here are the Actions (what do I want it to be able to do etc.)
Implementation file - I_IPPower.xml
<?xml version="1.0"?> <!-- I_IPPower.xml; Vera/openLuup "IPPower 9258" Plug-in V1.1 Nov 2021 --> <implementation> <settings> <protocol>crlf</protocol> </settings> <files>L_IPPower1.lua</files> <startup>IPPowerStartup</startup> <actionList> <action> <serviceId>urn:upnp-org:serviceId:SwitchPower1</serviceId> <name>SetTarget</name> <job> local url = "http://" .. ipAddress .. "/set.cmd?user=admin+pass=12345678+cmd=setpower+" .. luup.devices[lul_device].id .. "=" .. lul_settings.newTargetValue luup.log("Sending command " .. url) local status, data = luup.inet.wget(url) if (data) then debug("Data received = " .. data) else log("No Data received !! ") end local value = string.match(data, luup.devices[lul_device].id.."=(%d)") if (value) then luup.variable_set("urn:upnp-org:serviceId:SwitchPower1", "Status", value, lul_device) else log("Value returned is empty") end </job> </action> </actionList> </implementation>The other two files D_IPPower.json, and L_IPPower.lua are still being worked on so i will add later …
If anyone notices any improvement
Hi
I’ve seen storage.set and storage.get used in some Lua code posted on the internet, where they need to store/retrieve somethings, e.g.
iv = encdec.base64dec(challenge_Key) storage.set('IV', iv)and then later on within another function I see..
iv = storage.get('IV')It makes sense how this is meant to work, and it seems such a simple/elegant feature - hence I was wondering, is there an equivalent in Vera/OpenLuup? As I’d naturally go to use luup.variable_set and luup.variable_get - but that requires a device to be used etc.
So, I was messing with my Alexa routines and ha bridge, and I want to share something very cool you could do with the ability to get the last Alexa that heard your command. I mentioned it a couple of times on the old place, but Vera's OS is missing the jq package, but openLuup could have it installed.
Basically, just execute -lastalexa with my VeraAlexa plug-in:
local lastAlexa = luup.call_action("urn:bochicchio-com:serviceId:VeraAlexa1", "RunCommand", {Command="-lastalexa"}, 666)Then use it in your scene, to dynamically generate your TTS response with the device you've asked your question seconds before!
local temperature = 25 -- get it from your sensor luup.call_action("urn:bochicchio-com:serviceId:VeraAlexa1", "Say", {Text="Ok, outise temperature is " .. temperature .. " degree", Volume=50, GroupZones= lastAlexa, Repeat = 2}, 666Very cool indeed 🙂
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)I've implemented this:
https://smarthome.community/topic/20/openluup-version-log/18
into my own MQTT Server and that's cool, since I'm now pushing every variable via MQTT, originating from the Vera.
Just a couple of questions for @akbooer
do I need to check that sock is connected, from time to time? is there anything specifically to retry the command?I'm running it in the latest hour, and everything seems OK, but I want to code it for the worst situations. Thanks, always inspiring!
Hi all
Here’s another snippet of code I like to use, to see what’s going on.
If anyone has any variations on this, I’d love to see it.
Sharing the love...
As I have a number of foscam cameras around the house, using their API I created the following bit of code to set to retrieve any settings across all of them, to keep them in sync.
This one gets the device name from each of them. If anyone has any variations or ways to improve the bit of code please let me know..
local url = require("socket.url") local URL = "http://" local username = "admin" local password = "password" local cmd = "getDevName" -- /cgi-bin/CGIProxy.fcgi?cmd=getDevName&usr=admin&pwd= local foscamsIPs = { { NO=1, IP="192.168.102.165:88", LOCATION="conservatory"}, { NO=2, IP="192.168.102.38:80", LOCATION="boiler room"}, { NO=3, IP="192.168.102.223:88", LOCATION="Living room"}, { NO=4, IP="192.168.102.245:88", LOCATION="front room"}, { NO=5, IP="192.168.102.104:88", LOCATION="kitchen"}, } for k,v in pairs(foscamsIPs) do -- Read each value. local update = URL .. v["IP"] .. "/cgi-bin/CGIProxy.fcgi?cmd="..cmd.."&usr="..username.."&pwd="..password -- do this for each reading. local status, result = luup.inet.wget(update) print(v["LOCATION"].. " status is " ..status.. " : result is " ..result) end````I have this block of lua and I am using luup.call_delay to delay the sending of IR codes. it works as intended but I am trying to eliminate the use of luup.sleep for the delay before the tcp:close() as well. i cant figure out how to implement luup.call_delay() for the tcp:close() and keep the luup.call_delay intact for the delay between the IR codes.
Thanks in advance.
function Delay1() --[[HDMI Matrix B1--]] local socket = require("socket") tcp = assert(socket.connect("192.168.255.204", 4998)) tcp:send("sendir,1:3,1,38000,1,69,341,171,22,21,22,21,22,21,22,21,22,21,22,21,22,21,22,21,22,64,22,64,22,64,22,64,22,64,22,64,22,64,22,64,22,21,22,21,22,64,22,21,22,64,22,21,22,21,22,21,22,64,22,64,22,21,22,64,22,21,22,64,22,64,22,64,22,1493,341,85,22,3669" .. "\r\n") luup.sleep(1050) tcp:close() end luup.call_delay("Delay1", 1) function Delay2() --[[HDMI Matrix B2--]] local socket = require("socket") tcp = assert(socket.connect("192.168.255.204", 4998)) tcp:send("sendir,1:3,1,38000,1,69,343,171,21,21,22,21,22,21,22,21,21,21,22,21,22,21,21,21,22,63,22,63,22,63,22,63,22,63,22,63,22,63,22,63,22,63,22,63,22,63,22,21,22,63,22,21,21,21,22,21,22,21,21,22,21,21,22,63,22,21,22,63,22,63,22,63,22,1528,342,85,22,1528" .. "\r\n") luup.sleep(1050) tcp:close() end luup.call_delay("Delay2", 5)In my Virtual HTTP plugin I have patterns that could be specified to dynamically compose HTTP URLs based on current values. So, if you enter
http://myip/color?v=%s
it gets replaced with the current color. It's very handy when having more parameters, or if your parameter is in a different position.
The problem is that from time to time, there are users with legitimate % in their URLs (since it's the escape char in URLs, after all) and this is causing weird behavior or crash.
So, I want to find something smarter to support both.
Any hints here is appreciated.
Luup : Hue Energy (watts) Calculator
-
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.
-
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"
-
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 ?