Ubiquiti UniFi - Plugin Potential?

If you just want to pick out one variable, you don’t use a loop, you just go after it. I would not want to rely on the output here, since it’s a moving target. Modify your program to just directly print the result of the UnifiController function and post the output here, along with what value you are trying to get to, and I can give you the specific syntax for directly accessible it in the data.

I’m really not sure what the correct terminology is but I’m trying to loop through the .json to get the information of each device outputted into a list/table format.

value.ip, value.hostname, value.oui, value. _last_seen_by_usw, value. first_seen

I understand, but I need you to post the JSON.

I’ll PM you the json, it’s quite large and has all of my internal network devices listed…

OK, so the data returned is an object with a couple of keys, the only one of which we care about is called data. The value at that key is an array containing node information. Each array element is an object with keys for the different data values.

If we decode the JSON like this: local u = json.decode( UnifiController() )

Then u is the top-level object. The array of nodes is in u.data. The first node is u.data[1], and the hostname of that node is u.data[1].hostname.

So you have to use a combination of dereferencing through the object keys and array elements to get to the data.

Here’s a loop that just prints some of the data about the nodes:

for _,entry in ipairs( u.data ) {
    print("ip=" .. entry.ip
		.. ", hostname=" .. String(entry.hostname)
		.. ", oui=" .. String(entry.oui)
		.. ", first_seen=" .. String(entry.first_seen)
		.. ", last_seen=" .. String(entry.last_seen)
		)
end

The ipairs() function returns two data values for every array element (see we are using u.data, the node array, as the argument to ipairs()). The _ for the first return value just tells Lua we don’t care about that value and it can discard it. It’s the index into the array… 1, 2, 3, 4, 5, etc. The second return value is the array element at the index. This is an object that contains the data keys. We can access the data keys here using “dot” notation, like entry.hostname. This will give us the value of the hostname key in the current entry (which changes each trip through the ipairs() loop). If the key contains any special characters (non-alphanumeric), then you can use “square bracket” notation to get to the key value. For example, if for some reason the node data had a key last-wakeup, we could not use entry.last-wakeup, because Lua will think we are asking it to subtract wakeup from entry.last. To fix this, we write instead entry['last-wakeup']. This is just an alternate syntax for the dot notation.

Let’s say we wanted to just extract some of the data from the response into a lighter, more useful structure, where we could quickly test to see if an IP address has a host on it, or if a particular host is on the network:

-- This makes a table as a map/dictionary indexed by ip (access like nodes['192.168.102.134'])
-- This form would be more appropriate for quickly locating a single device, as shown below. You can 
-- change the value used to make the "nodes" entry to change the table's lookup key.
local nodes = {}
for _,entry in ipairs( u.data ) do
	-- To index by IP address, use this line:
	nodes[entry.ip] = { ip=entry.ip, hostname=entry.hostname, oui=entry.oui, first_seen=entry.first_seen, last_seen=entry.last_seen }
	-- Or, to index by hostname, use this line:
	nodes[entry.hostname] = { ip=entry.ip, hostname=entry.hostname, oui=entry.oui, first_seen=entry.first_seen, last_seen=entry.last_seen }
end

What this does is create a new table called nodes that is set up for direct access to a device by either its IP address or hostname (depending on which of the two key lines you used, above–either/or there, not both).

Here’s an example of how you use that:

-- Try it. This assumes you used the IP address as key above. We have to use the square-bracket 
-- syntax because the key contains special characters (the dots).
if nodes['192.168.102.134'] then
        -- Print the hostname of the node with that IP
	print("The node at 192.168.102.134 is ", nodes['192.168.102.134'].hostname)
else
	print("There is no node at 192.168.102.134 on the WiFi network")
end
-- And this version assumes you used the hostname as key above. 
if not nodes['Vera 3'] then
	print("Oh no! The Vera3 is not on the WiFi network!")
end
-- For hostnames that don't have special characters, you can use the dot syntax:
if nodes.SonosZP then
	print("I see the Sonos Play 1 at " .. nodes.SonosZP.ip)
end

Awesome , thanks @rigpapa

QQ - I’ve just tried the loop but I get an error, first it seems a ‘}’ is missing, and then it’s looking for a ‘do’ to be added ? So, I’ve tried to address that by doing the following, but that only ends in a familiar earlier error message…

local json = require "dkjson"
	local f = io.open(tempfilename, "r")
	local unifidata = json.decode(f)
		for _,entry in ipairs( unifidata.data ) do
                    print("ip=" .. entry.ip
		.. ", hostname=" .. entry.hostname
		.. ", oui=" .. entry.oui
		.. ", first_seen=" .. entry.first_seen
		.. ", last_seen=" .. entry.last_seen) 

Result.

LuaTest 1.7

Lua file: /etc/cmh-ludl/luatest.lua

Results
Runtime error: Line 355: bad argument #1 to 'strfind' (string expected, got userdata)

Print output
(none)

Ok, reworked the code (as I had so many versions running, and I get a different error now.

local json = require "dkjson"
local u = json.decode( UnifiController() )
	for _,entry in ipairs( u.data ) do
    print("ip=" .. entry.ip
		.. ", hostname=" .. entry.hostname
		.. ", oui=" .. entry.oui
		.. ", first_seen=" .. entry.first_seen
		.. ", last_seen=" .. entry.last_seen
		)
	end

Returns

LuaTest 1.7

Lua file: /etc/cmh-ludl/luatest.lua

Results
Runtime error: Line 41: attempt to concatenate field 'hostname' (a nil value)

Now I know some of the host names are blank/nil, but how do you break out of that loop mid print when a nil value is found ?

			if entry.hostname == nil then 
				break 
			end

Or perhaps just show ‘nil’ or ‘n/a’ instead ?

And I also tried your suggestion about creating the nodes table, but that too initially returned some errors , and after trying to connect those, with this…

local json = require "dkjson"
local u = json.decode( UnifiController() )
local nodes = {}
for _,entry in ipairs( u.data ) do
	nodes[entry.ip] = { ip=entry.ip, hostname=entry.hostname, oui=entry.oui, first_seen=entry.first_seen, last_seen=entry.last_seen }
	nodes[entry.hostname] = { ip=entry.ip, hostname=entry.hostname, oui=entry.oui, first_seen=entry.first_seen, last_seen=entry.last_seen }
	end

Here’s that error

LuaTest 1.7

Lua file: /etc/cmh-ludl/luatest.lua

Results
Runtime error: Line 39: table index is nil

I’m obviously doing something wrong with both the example you gave me, any ideas ?

It it helps,

As,suggested, doing this,

local json = require "dkjson"
local u = json.decode( UnifiController() )
		print(u.data[1].hostname) 

I will get just the one value returned,

But it I want to get one line item returned for each device, it seems to return that same value for all the lines related to that device,

local json = require "dkjson"
local u = json.decode( UnifiController() )
	for _,entry in ipairs( u.data ) do
		print(u.data[1].hostname)
	end
LuaTest 1.7

Lua file: /etc/cmh-ludl/luatest.lua

Results
No errors
Runtime: 1312.5 ms
Code returned: nil

Print output
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
NAS4DD1B4     
```

Hi @rigpapa

I think i’ve got it now, this seems to do what I was after to create a list/report view

local json = require "dkjson"
local u = json.decode( UnifiController() )
	for k,entry in ipairs( u.data ) do
		print(u.data[k].ip, u.data[k].hostname, u.data[k].oui)
	end

Many thanks again for all your advice/direction.

Be aware, u.data[k] and entry are exactly the same thing inside your loop. That’s how ipairs() works.

I fixed the other issues in the code. Sorry, I obviously can’t run it because I don’t have your specific set-up. Since hostname (and possibly other fields) can be omitted (making them nil when you reference them), you need to guard. A simple guard for string concatenation is something of this form: print("hostname is "..String(entry.hostname)). The String() function will make nil into the word “nil” but it won’t crash.

Thanks so much again @rigpapa,

The second loop I had in was obviously throwing me off earlier, so it’s been great to have your eyes go over it.

I’ve not adjusted it to use ‘entry’ yet, if that’s the same as ‘u.data[k]’ , but the following was where I had got to, to deal with the nill values - plus I finally got a sort table to work too !! :crazy_face: (proud smiley face)

local tt = {}
local json = require "dkjson"
local u = json.decode( UnifiController() )
	for k,entry in ipairs( u.data ) do
			if u.data[k].hostname == nil then u.data[k].hostname = "No Hostname" end
				if u.data[k].ip == nil then u.data[k].ip = "No IP" end	
				if u.data[k]._last_seen_by_usw == nil then u.data[k]._last_seen_by_usw = 0 end
				local last_seen = os.date("%Y/%m/%d - %H:%M:%S", u.data[k]._last_seen_by_usw)
				table.insert(tt, { 
					last = u.data[k]._last_seen_by_usw,
					ip = u.data[k].ip,
					hostname = u.data[k].hostname, 
					oui = u.data[k].oui,
					lastHR = last_seen})
	end
	
table.sort(tt, function(a,b) return a.last > b.last end) -- sort highest to lowest

for _,d in ipairs(tt) do
	print(d.lastHR, d.ip, d.hostname, d.oui, d.last )
end	

What would be the best way to limit the final report to a specific set of devices, e.g based on a ip addresses, or hostname or oui ? Would you just use an ‘if’ statement after the ‘do’ or create a look up table ? Could you share how you would do it ?

Here are some more of the apis you can call.

	local deviceb = 'api/s/default/stat/device-basic'
	local device = 'api/s/default/stat/device'
	local health = 'api/s/default/stat/health'
	local stat = 'api/s/default/stat/sta'
	local user = 'api/s/default/stat/user'
	local mac = 'api/s/default/stat/device/74:bc:b9:d7:a3:75'
	local site = 'api/s/default/stat/report/daily'
	local event = 'api/s/default/stat/event'
	local netconf = 'api/s/default/list/networkconf'
	local wlanconf = 'api/s/default/list/wlanconf'
	local site = 'api/s/default/stat/site' -- not working
	local status = 'status'
	local alarm = 'api/s/default/list/alarm'

Based on what I have found, there is great base of information provided by the UniFi api to evolve the current ‘sensor’ plug-in or to create a new one,

The event api for example, reports on the movement of a devices between access points, so in addition to presence detection when nearing your house, you could also see movement within the house too…

@parkerc It looks like we both started looking into the problem around the same time. I used @BlueSmurf’s plugin and changed it to remove the shell script. I have also used @rigpapa’s VirutalSensor plugin as an inspiration to add virtual motion sensors. I tried to be thoughtful about attributions in the code, but if anybody has any reservations, please let me know. This is my first publication of anything, so please accept my preemptive apologies.

https://github.com/imro2/unifi-presence

Note: currently it uses the same file names as @BlueSmurf plugin, so if you are using it, don’t download this one.

1 Like

Thanks for the nod, I appreciate it. Let me know if I can help.

Hi all,

I installed Vera Presence Detection plugin from below link

I created new account for Managing Devices by UniFi Controller
under SETTINGS/Admins

Then in Plugin Advanced/Variables I entered:
UnifiURL
UnifiUser (newly created)
UnifiPassword

Then I created Virtual Sensor and entered MAC Address reloaded engine and refreshed a web browser but I am only getting image

I am using UniFi CONTROLLER v6.0.43 cloud key

Can anybody advice ?

Well, the icon isn’t green so it looks like you are not logged in. Do you have ssh access to your Vera? If so, can you log in and get some logs?

cd /tmp/log/cmh 
tail -f LuaUPnP.log | grep UnifiSensor

I’m running into the same issue with the new UnifiPresence plugin. The logs are showing me:

50 02/15/21 19:09:58.134 luup_log:211: UnifiPresence: Connection failed: HTTP code: “invalid protocol (any)”

I did a bit of poking at it, and found that I changed the protocol setting in the https.request call to sslv23 it would work.

For any issues with UnifiPresence plugin please post here Unifi Presence plugin - General Plugin Discussion - Ezlo Community.

I did not mean to hijack parkerc’s thread.