Ubiquiti UniFi - Plugin Potential?

Sorry @rigpapa I’m just not getting this. :frowning:

Dkjson encode shows me a ‘data’ table and then when I drill down (with k,v) I see another table - which I’ve called ‘value

Now, I have to drill down into that ‘value’ table again to get the variables ?

How do I focus the loop in situations like this ?

The very interior print statement should say print(name,variable), because those are the changing values on the interior loop.

Sorry @rigpapa, I wasn’t explaining myself very well. I’ve tried many variations of the print statement, to capture the right values, but I’m trying to get just the one instance, per device as I’d like to print out a table view, with content like

Ip hostname, oui,
192.168.1.23 NA45678AB. QNAP
192.168.1.44. nil. Nestlabs

Placing this into inside loop, returns what I had in an earlier post and just lists everything

print(name,variable)

Result

LuaTest 1.7

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

Results
No errors
Runtime: 15532.3 ms
Code returned: nil

Print output
_is_guest_by_usw     false     
_last_seen_by_usw     1606854881     
sw_depth     1     
is_wired     true     
ip     192.168.102.134     
user_id     5f199b057de17904c996bc19     
sw_mac     78:4a:24:fe:1c:a7     
assoc_time     1606612980     
wired-tx_bytes     5066283427     
hostname     NA45678AB     
wired-tx_bytes-r     4479     
wired-rx_packets     14600066     
latest_assoc_time     1606854633     
wired-rx_bytes-r     99     
first_seen     1595513605     
network_id     571b92fb384e11e6ac19093e     

Where within the two loops do I put the command, to just get one instance of the requested variable ?

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 ?