The wifi connection to the devices I had were very flaky … so I put the project on the side for now.
I really wanted them for outside … where I need more range … but I already have Wifi coverage.
Richard, would it make sense to share the code you may have written for others to try to develop the plugin or is it something that you would like to keep for yourself and eventually release sometime in the future?
It’s not in a very workable state right now … It was just some prototype code to verify I could talk to it. But I spent all the time trying to make the connection/configuration easy and reliable.
I came across an interesting post on the Ubiquiti forum, which has a shell script looking at occupancy and the triggering an action via a http call to Vera.
https://community.ubnt.com/t5/UniFi-Wireless/Shell-Script-for-occupancy/td-p/1507765
The resulting discussion may be of use/interest to help with a Unifi presence detection plugin/app
Hi @RTS
Did you make any more progress with your Ubiquiti plugin ?
[quote=“parkerc, post:25, topic:192116”]Hi @RTS
Did you make any more progress with your Ubiquiti plugin ?[/quote]
Or would you be prepared to share the code? I’m thinking of modifying the Ping Sensor Plugin - basically replace the section of code that runs the ping
local returnCode = os.execute("ping -c 1 " .. address)
With something that uses the Unifi API to ask if an IP Address is present on the WLAN. Rationale for IP rather than MAC is minimising the amount of change required to the Ping Sensor code.
I’d be happy to hardcode all of the config for my Unifi controller.
I had a lot of problems with the Ubiquiti devices … discovery and configuration was not very reliable … I did not want a continuous support project … so I dropped it.
I have created a working “Unifi Ping” prototype, based on the existing Ping Sensor that uses the Unifi controller and will look up MAC, IP or Hostname (or pretty much anything that shows up in the controller status).
My only concern is the execution time to grab the contents from the Unifi server. Gimme a little time to test it works as expected and I’ll share.
General question (RTS?): is there a recommended location on Vera (via SSH) to store user created shell scripts? I’m using /tmp at the moment. Ideally someone can convert the .sh script to lua and embed it in the plugin, but that’s beyond my capability at the moment.
Ok… latest update.
My proof of concept hacked version has been working well for 2 days now monitoring 2 iphones and 2 androids. It’s been perfect, much more reliable than ping was.
Today I ‘repackaged’ the plugin, ditching UI5 legacy and replacing anything ‘ping’ with ‘unifi’ and created some snazzy UAP icons for status indication as well as UI messages when it’s doing a unifi query.
Now I’m going to start on some enhance/extend, fix the UI and move any hardcoded script stuff into UI variables. At that point, I’ll post it for people to try.
First release of plugin can be found here:
http://forum.micasaverde.com/index.php/topic,50187.msg326848.html#msg326848
It’s amazing the stuff I find myself coming back to in my ever continuing Vera / Lua journey:-)
I never did find a right way to integrate my UniFi home network set up with Vera, so I thought I’d open this thread up again and focus on this Unifi Presence Lua script/code (i’m never quite sure what to call it) to connect to my UniFi Controller and retrieve a .json and then search for the specified device information.
I’ve been looking to update this code to generate a customer report, and have tried a few variations. The main part of the code is as follows, which returns a large .json file
function UnifiController()
unifi_username = "user" -- Username for example admin
unifi_password = "pass" -- Password for example 1234546
unifi_controller_ip = "192.168.1.207" -- Controller IP
unifi_controller_port = "8443" -- Controller port, default is 8443
cookie = "/tmp/unifi_cookie_lua" -- Temp cookie file
tempfilename = "/tmp/UnifiController.tmp" -- Temp JSON output file
local f = io.popen("stat -c %Y " .. tempfilename)
local last_modified = f:read()
if (os.difftime (os.time(), last_modified) > 30)
then
-- URL for logging in, query and logging out
url_login = 'curl --cookie ' .. cookie .. ' --cookie-jar ' .. cookie .. ' --insecure -H \'Content-Type: application/json\' -X POST -d \'{"password":"' .. unifi_password .. '","username":"' .. unifi_username .. '"}\' https://' .. unifi_controller_ip .. ':' .. unifi_controller_port .. '/api/login'
url_open = 'curl --cookie ' .. cookie .. ' --cookie-jar ' .. cookie .. ' --insecure -s -o '..tempfilename..' --data "json={}" https://' .. unifi_controller_ip .. ':' .. unifi_controller_port .. '/api/s/default/stat/sta'
url_logout = 'curl --cookie ' .. cookie .. ' --cookie-jar ' .. cookie .. ' --insecure https://' .. unifi_controller_ip .. ':' .. unifi_controller_port .. '/logout'
-- Execute url
-- Execute url
read_login = os.execute(url_login)
read_open = os.execute(url_open)
read_logout = os.execute(url_logout)
end
file = io.open(tempfilename, "r")
while true do
line = file:read("*line")
if not line then
break
end
return line
end
file:close()
end
print(UnifiController())
I have validated the returned json online and it’s confirmed as good, and now I want to work with it, but I’m not sure of the best way to do it.
I either time-out or get an error on line 355 - example below.
local json = require "dkjson"
local unifidata = json.decode(UnifiController())
for k, v in pairs(unifidata) do
print(k,v)
end
Returns
Runtime error: Line 355: bad argument #1 to 'strfind' (string expected, got nil)
Here is the .json design (when I run it through a json designer app)
The UnifiController()
function has only two code paths to exit:
- The
return
statement inside thewhile
loop near the bottom. -
break
ing out of thewhile
loop because no line was read.
#1 will only return one line of the temporary file, not the entire file. That’s because the return
stops further execution of the function (and therefore the loop), so this function can only ever return one line if there is any data at all to be read. So it’s quite possible that if the JSON response returns multiple lines, you are trying to parse an incomplete response and that is crashing DKJSON.
#2 will only be taken if the temporary file is completely empty (e.g. the first attempt to read results in nothing). There is no return statement at all at the end of the function, so the function will return nothing (you will receive that as nil
) and this will also result in DKJSON complaining.
It should also be noted that even if the Unifi system is returning a single-line JSON response, it may contain data that gives DKJSON heartburn. I would first go examine the contents of the temporary file. If it is empty, you’ve hit #2. If it is not empty, see if it has multiple lines; if so, you’ve hit #1. Otherwise, it’s possible that DKJSON is unhappy with the returned data (you might paste the data into jsonlint.com and examine it for special characters, particularly any special/international character that is not properly UTF-8 encoded).
Note: Also, because #1 exits the function early, the file handle is never closed and therefore in a plugin environment you would have a file handle leak that would eventually crash the system. My recommendations to fix your function:
- Replace the
read("*line")
withread("*all")
. This will read the entire file in one pass. Note that as a general warning to doing it this way, very large responses will cause spikes in memory utilization that could destabilize LuaUPnP, but in this particular application, I doubt you’d ever see a response long enough. - Remove the
return line
line from inside the while loop. - Put
return line
at the end of your function (after thefile:close()
and right before theend
statement). - Declare all the variables (including
line
andfile
) that you are using in the functionlocal
. You’re making a ton of globals without that.
Thanks @rigpapa
There’s a lot to digest there.
Every time I run the core code, with either a print or return statement I get a .json formatted response, and when I’ve placed that into an online json validator, it always reports good.
An earlier version of my code, that did not have the ‘break’ line, would just lock up and Vera would reload luup, so I had to add it so it could break out…
I don’t think it’s a massive .json file, (3000 lines on the jsonlint) and as you can see from the json designer image i posted - it holds a reasonable amount of values for each endpoint registered on the UniFi controller. I could not see anything out of the ordinary in the content produced.
As I can get a ‘json formatted file/response every time - is there something in Lua I could do to validate it ?
Also the error I always seem to get is on line 355, I looked at that via the json validator, but nothing looked wrong, although I have no idea how the script/code on Vera calculates the line number ? Do you ?
The cure is worse than the disease, in a way. If you read “*a” (or “*all”) it will never return nil
: You don’t even need the while
…
local line
local file = io.open(tempfilename, "r")
if file then
line = file:read("*a")
file:close()
end
return line
The line number reported is in the code, not the JSON. You’d need to post the JSON for us to really be able to move forward.
Great, thanks so much @rigpapa - those updates helped, as using that updated core code…
local json = require "dkjson"
local unifidata = json.decode(UnifiController())
for k, v in pairs(unifidata) do
print(k,v)
end
… I now have two tables returned.
Here’s what LuaTest reported.
LuaTest 1.7
Lua file: /etc/cmh-ludl/luatest.lua
Results
No errors
Runtime: 309.7 ms
Code returned: nil
Print output
meta table: 0x12c49b8
data table: 0xf05ec8
Well, that’s looking up. At least it didn’t throw an error. The output isn’t useful, though, in looking at the data (can’t see what’s in the table), but if you know what’s in the table, mush on and extract what you need from it.
Yep, that is indeed what I’m trying to work out - how do I interrogate the resulting ‘data’ table…
I’ve spent some time trying to learn Lua and json decoding - with mixed success… it’s all seems to come down to how you break it up. For example.
Doing this seems to get me the keys - number of entries
for k, v in pairs(unifidata.data) do
print(k, v)
end
Returns…
LuaTest 1.7
Lua file: /etc/cmh-ludl/luatest.lua
Results
No errors
Runtime: 1439.6 ms
Code returned: nil
Print output
1 table: 0x158e9a8
2 table: 0x16189e0
3 table: 0x1410898
4 table: 0x14e6f68
5 table: 0x149bb40
Etc....
And then this returns all the variables.
local unifidata = json.decode(UnifiController())
for k, v in pairs(unifidata.data) do
for _, v in pairs(v) do
print(v)
end
end
But extracting a specific set of values is often where I struggle - any pointers ?
I’m finally making some progress with this, let’s see where it takes me
local unifidata = json.decode(UnifiController())
for key, value in pairs(unifidata.data) do
for name, variable in pairs(value) do
print(name, variable)
end
end
Returns this.
LuaTest 1.7
Lua file: /etc/cmh-ludl/luatest.lua
Results
No errors
Runtime: 14803.9 ms
Code returned: nil
Print output
_is_guest_by_usw false
_last_seen_by_usw 1606773424
sw_depth 1
is_wired true
ip 192.168.1.134
user_id 5f199b057de17904c996bc193
sw_mac 78:8a:11:fe:1c:a8
assoc_time 1606612980
wired-tx_bytes 4708199785
hostname NAS247123
wired-tx_bytes-r 4233
wired-rx_packets 14381240
latest_assoc_time 1606737061
wired-rx_bytes-r 142
first_seen 1595513605
network_id 571b92fb384e11e6ac19093e
network LAN
_id 5f199b057de17904c996bc192
wired-tx_packets 6544197
wired-rx_bytes 20449918065
is_guest false
oui Qnap
etc..
Ok, I’ve gone as far as I can, I’m able to identify the values I want but my loops are wrong, as it shows me the same value for all the lines present - rather than just one line for each.
local json = require "dkjson"
local unifidata = json.decode(UnifiController())
for key, value in pairs(unifidata.data) do
for name, variable in pairs(value) do
print(value.ip, value.hostname, value.oui)
end
end
Returns…
LuaTest 1.7
Lua file: /etc/cmh-ludl/luatest.lua
Results
No errors
Runtime: 31906.8 ms
Code returned: nil
Print output
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.134 NAS41234AB Qnap
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.237 nil NestLabs
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
192.168.102.33 nil Bskyb
Etc...
You are looping with: for name, variable in pairs(value) do
…but printing print(value.ip, value.hostname, value.oui)
So your inner loop is working but it prints value from the outer loop, that’s why it’s not changing.