Unifi controller api get connected device mac address

Discussion in 'C-Bus Automation Controllers' started by Pie Boy, Mar 27, 2020.

  1. Pie Boy

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    This code communicates from SHAC to the unif controller api and returns connected devices by mac address. im using this to set my away scene once i leave the house or disconnect from the wifi..... but im checking more than one device - so im hoping that should give me the desired effect

    im running this code in a resident script every 60 sec, change username, password and controller_ip and port to suit

    Code:
    htps = require("ssl.https")
     require('json')
    
    htps.TIMEOUT = 5
    
    usr_credenitals = json.encode({
      username = 'admin',
      password = 'admin123456'
    })
    
    controller_ip = '192.168.1.10'
    controller_port = '8443'
    
    login ='https://'.. controller_ip..':'..controller_port..'/api/login'
    sta = 'https://' .. controller_ip..':'..controller_port..'/api/s/default/stat/sta'
    
    Post_response = {}
    
    local res, err, response_headers, status = htps.request
      {
        url = login,
        method = "POST",
        protocol = 'tlsv12',
        headers =
        {
        ["Accept"] = "*/*",
        ["Content-Type"] = "application/x-www-form-urlencoded",
        ["Content-Length"] = usr_credenitals:len()
        },
    
      source = ltn12.source.string(usr_credenitals),    
      sink = ltn12.sink.table(Post_response)
      }
    
    if Post_response then
     -- log(Post_response)
      --log(res, code, response_headers
    
      mmm_cookies = response_headers['set-cookie']
      if mmm_cookies then
      session = mmm_cookies:match('(unifises=[^;]+)')
      token = mmm_cookies:match('(csrf_token=[^;]+)')
    
      cookie = session .. '; ' .. token
      end
    
      Get_response ={}
    
      res, err = htps.request({
         url = sta,
         method = "GET",
         protocol = 'tlsv12',
         headers = {
        cookie = cookie
        },
        sink = ltn12.sink.table(Get_response),
      })
    
    
      if Get_response then
        Get_response = table.concat(Get_response)
        dec = json.decode(Get_response)
    
        -- get all mac adresses currently connected to site
        for i = 1, #dec.data do
          log(dec.data[i].mac)
          -- return boolean if mac adress is connected
     -- replace string at end with known mac adress of device you want to moniter
         is_mac_1 = dec.data[i].mac =='90:e1:7b:6f:48:2e'
         is_mac_2 = dec.data[i].mac =='90:e1:79:6f:22:2e'
        end
    
      -- check if mac 1&2 are both false then set scene
    if not is_mac_1 and not is_mac_2  then
            --- set scene  here
    end
    
      else
        log('could not get stat ' .. tostring(err))
      end
      else
      log('login failed ' .. tostring(err))
    end
    
    
     
    Last edited: Mar 27, 2020
    Pie Boy, Mar 27, 2020
    #1
  2. Pie Boy

    paultulloch

    Joined:
    Jan 19, 2019
    Messages:
    9
    Likes Received:
    0
    Location:
    Perth
    Hi Pie Boy,

    I'm having fum working with this! Great example of what is possible with integrating separate systems. I have been trying to get this to work, and I am not sure if this is still working for the more recent software updates to the Unifi Controller software? Unifi documentation advises that for UDM the login endpoint is now "/api/auth/login" and "All API endpoints need to be prefixed with /proxy/network".
    I am unable to resolve the following error "attempt to concatenate global 'token' (a nil value)", and I'm guessing that this is because the api call is failing.
    Thanks
    Paul
     
    paultulloch, Jan 31, 2021
    #2
  3. Pie Boy

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    Yeah, the latest unifi update has removed controller access by port 8443, and is now available at port 443 with https, might require some modifications to work, I’ll have a look soon and report back.
     
    Pie Boy, Jan 31, 2021
    #3
  4. Pie Boy

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    Try This,

    Code:
    htps = require("ssl.https")
           require('json')
    
    htps.TIMEOUT = 5
    
    usr_credenitals = json.encode({
        username = 'XXXXXXXXXXXXXX',
      password = 'XXXXXXXX'
    })
    
    login = 'https://192.168.1.10:443/api/auth/login'
    
    sta = 'https://192.168.1.10:443/proxy/network/api/s/d5gh60j5/stat/sta'
                                 
    get_sites = 'https://192.168.1.10:443/proxy/network/api/self/sites'
    
    Post_response = {}
     
    local res, err, response_headers, status = htps.request
      {
        url = login,
        method = "POST",
        protocol = 'tlsv12',
        headers =
        {
        ["Accept"] = "*/*",
        ["Content-Type"] = "application/json",
        ["Content-Length"] = usr_credenitals:len()
        },
     
      source = ltn12.source.string(usr_credenitals),      
      sink = ltn12.sink.table(Post_response)
      }
    
    if Post_response then
      --log(res, code, response_headers)
     mmm_cookies = response_headers['set-cookie']
      if mmm_cookies then
      token = mmm_cookies:match('(TOKEN=[^;]+)'..';')
      end
     
      Get_response ={}
     
      res, err = htps.request({
         url = sta,
         method = "GET",
         protocol = 'tlsv12',
         headers = {
         cookie = token,
        ['X-CSRF-Token'] = response_headers['x-csrf-token']
        },
        sink = ltn12.sink.table(Get_response),
      })
     
      if Get_response then
    
        Get_response = table.concat(Get_response)
        dec = json.decode(Get_response)
       
        -- get all mac adresses currently connected to site
        for i = 1, #dec.data do
          log(dec.data[i].mac)
          -- return boolean if mac adress is connected
          is_mac = dec.data[i].mac =='42:45:05:39:0f:d9'
         
         if is_mac then
            log(is_mac)
          end
        end
       
      else
        log('could not get stat ' .. tostring(err))
      end
      else
      log('login failed ' .. tostring(err))
    end
     
    Pie Boy, Feb 2, 2021
    #4
  5. Pie Boy

    paultulloch

    Joined:
    Jan 19, 2019
    Messages:
    9
    Likes Received:
    0
    Location:
    Perth
    Thanks, I am able to connect through now and get the list of connected mac addresses. The only gotcha was that you need to update the site ('default' if you are lazy like me).
     
    paultulloch, Feb 3, 2021
    #5
  6. Pie Boy

    paultulloch

    Joined:
    Jan 19, 2019
    Messages:
    9
    Likes Received:
    0
    Location:
    Perth
    Hi Pieboy,
    Have you tried to use any of the other APIs? - for instance disable a wifi? (e.g. .../api/s/rest/wlanconf/...) or a mac address? I have tried using the first part of your script, and then using a couple of the other APIs, but struggling to get it to work.
    Thanks
    Paul
     
    paultulloch, Feb 13, 2021
    #6
  7. Pie Boy

    paultulloch

    Joined:
    Jan 19, 2019
    Messages:
    9
    Likes Received:
    0
    Location:
    Perth
    This is as far as I have got, unable to get the second api to block a mac address

    Code:
    --Get data from Unifi
    htps = require("ssl.https")
           require('json')
    
    htps.TIMEOUT = 5
    
    usr_credenitals = json.encode({
        username = 'user',
      password = 'password'
    })
    --Enter IP address and port 443
    login = 'https://192.168.2.1:443/api/auth/login'
    --Update Site if not default
    sta = 'https://192.168.2.1:443/proxy/network/api/s/default/stat/sta'
                               
    get_sites = 'https://192.168.2.1:443/proxy/network/api/self/sites'
    
    --block mac is a post.  {mac: "e8:3a:12:22:0a:49", cmd: "block-sta"}
    block_mac = 'https://192.168.2.1:443/proxy/network/api/s/default/cmd/stamgr'
    --First api call to log in, get cookies
    Post_response = {}
    
    local res, err, response_headers, status = htps.request
      {
        url = login,
        method = "POST",
        protocol = 'tlsv12',
        headers =
        {
        ["Accept"] = "*/*",
        ["Content-Type"] = "application/json",
        ["Content-Length"] = usr_credenitals:len()
        },
     
      source = ltn12.source.string(usr_credenitals),    
      sink = ltn12.sink.table(Post_response)
      }
    
    if Post_response then
     --log(res, code, response_headers)
     mmm_cookies = response_headers['set-cookie']
      if mmm_cookies then
      token = mmm_cookies:match('(TOKEN=[^;]+)'..';')
      end
    --Second api call.  This is a get
      Get_response = {}
     
      res, err = htps.request({
         url = block_mac,
         method = "POST",
         protocol = 'tlsv12',
         headers = {
         cookie = token,
        ['X-CSRF-Token'] = response_headers['x-csrf-token'],
            cmd = 'block-sta',
          mac = 'e8:3a:12:22:0a:49'
              },
        sink = ltn12.sink.table(Get_response),
            })
     
       
      else
      log('login failed ' .. tostring(err))
    end
     
    paultulloch, Feb 19, 2021
    #7
  8. Pie Boy

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    a couple of ideas
    For Post command you need to specify the payload/ data message in the ltn12.source var like below,
    also your message might need to be encoded as json, this is one thing i couldn't find any info on control commands and format -- i assume it would be json?? i would reverse engineer the request to be 100% sure on the format...(fiddler or similar to capture the request if you make it from a web page you will see the traffic etc) , also the request format might need to be "PUT" i remember reading somewhere that post can creates a new object every time instead of modifying the existing, but im only 50% Sure about that....


    Code:
    --Second api call.  This is a post for block mac address endpoint
      Get_response = {}
    
     --- this syntax is incorrect cmd = 'block-sta', mac = 'e8:3a:12:22:0a:49'
    
    -- it may need to be encoded json with correct format something like the below although im unsure of the correct format
    cmd = json.encode({
        block-sta = 'e8:3a:12:22:0a:49',
    })
    
      res, err = htps.request({
        url = block_mac,
        method = "POST",
        protocol = 'tlsv12',
        headers = {
        cookie = token,
       ['X-CSRF-Token'] = response_headers['x-csrf-token'],
     
             },
     
      source = ltn12.source.string(cmd),
       sink = ltn12.sink.table(Get_response),
           })
    
     
      else
      log('login failed ' .. tostring(err))
    end
     
    Last edited: Feb 19, 2021
    Pie Boy, Feb 19, 2021
    #8
  9. Pie Boy

    paultulloch

    Joined:
    Jan 19, 2019
    Messages:
    9
    Likes Received:
    0
    Location:
    Perth
    I think this delivers the correct payload

    Code:
      cmd = json.encode({
        cmd = 'block-sta',
          mac = 'e8:3a:12:22:0a:49',
    })
    as it generates the string:

    {"mac":"e8:3a:12:22:0a:49","cmd":"block-sta"}

    which appears the same as the unifi controller:

    upload_2021-2-20_9-40-58.png

    But not working yet
     

    Attached Files:

    Last edited: Feb 20, 2021
    paultulloch, Feb 20, 2021
    #9
  10. Pie Boy

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    Yeah cool,
    Command format is correct, as you have it above,
    You will probably also need content-length and content type headers ad in after

    Cookie = token,
    [‘Content-Length’] = #cmd,
    [‘Content-Type’] = “application/json”,
     
    Last edited: Feb 20, 2021
    Pie Boy, Feb 20, 2021
    #10
  11. Pie Boy

    paultulloch

    Joined:
    Jan 19, 2019
    Messages:
    9
    Likes Received:
    0
    Location:
    Perth
    And we have a winner. Thank you for your help. The idea here is to run an event script to block a mac address from a 'button press', but this could also be added to a schedule . I want to also look at the wifi config api - sure this can be done by logging in to the controller, but this will be more exposed, and could be added to a dinner scene . .

    Slight trap is this took five minutes to show up as blocked on the controller


    Code:
    --Get data from Unifi
    htps = require("ssl.https")
           require('json')
    
    htps.TIMEOUT = 5
    
    usr_credenitals = json.encode({
        username = 'user',
      password = 'Password1'
    })
    --Enter IP address and port 443
    login = 'https://192.168.2.1:443/api/auth/login'
    --Update Site if not default
    sta = 'https://192.168.2.1:443/proxy/network/api/s/default/stat/sta'
                                
    get_sites = 'https://192.168.2.1:443/proxy/network/api/self/sites'
    --wlan is a get/put  {"enabled": false}
    block_wifi = 'https://192.168.2.1:443/proxy/network/api/s/rest/wlanconf/5decb05a694a9c04b1c862c8'
    --block mac is a post.  {mac: "e8:3a:12:22:0a:49", cmd: "block-sta"}
    block_mac = 'https://192.168.2.1:443/proxy/network/api/s/default/cmd/stamgr'
    --First api call to log in, get cookies
    Post_response = {}
    
    local res, err, response_headers, status = htps.request
      {
        url = login,
        method = "POST",
        protocol = 'tlsv12',
        headers =
        {
        ["Accept"] = "*/*",
        ["Content-Type"] = "application/json",
        ["Content-Length"] = usr_credenitals:len()
        },
     
      source = ltn12.source.string(usr_credenitals),     
      sink = ltn12.sink.table(Post_response)
      }
    
    if Post_response then
     --log(res, code, response_headers)
     mmm_cookies = response_headers['set-cookie']
      if mmm_cookies then
      token = mmm_cookies:match('(TOKEN=[^;]+)'..';')
      end
      --Second api call.  This is post api to block specific mac address
      -- use 'block-sta' to block and 'unblock-sta' to unblock
      Get_response = {}
      cmd1 = json.encode({
        cmd = 'block-sta',
          mac = 'e8:3a:12:22:0a:49',
    })
    
     local res, err = htps.request({
         url = block_mac,
         method = "POST",
         protocol = 'tlsv12',
         headers = {
         cookie = token,
        ["Content-Length"] =cmd1:len(),
        ["Content-Type"] = "application/json",
        ['X-CSRF-Token'] = response_headers['x-csrf-token'],
                      },
      source = ltn12.source.string(cmd1),
          sink = ltn12.sink.table(Get_response),
            })
        else
      log('login failed ' .. tostring(err))
    end
     
    paultulloch, Feb 20, 2021
    #11
  12. Pie Boy

    paultulloch

    Joined:
    Jan 19, 2019
    Messages:
    9
    Likes Received:
    0
    Location:
    Perth
    And to block a wifi network. The wifi is a long string that you can see in your browser address bar on the wifi config screen

    Code:
    --Get data from Unifi
    htps = require("ssl.https")
           require('json')
    
    htps.TIMEOUT = 5
    
    usr_credenitals = json.encode({
        username = 'user',
      password = 'Password1'
    })
    --Enter IP address and port 443
    login = 'https://192.168.2.1:443/api/auth/login'
    --Update Site if not default
    sta = 'https://192.168.2.1:443/proxy/network/api/s/default/stat/sta'
                                
    get_sites = 'https://192.168.2.1:443/proxy/network/api/self/sites'
    --wlan is a get/put  {"enabled": false}
    block_wifi = 'https://192.168.2.1:443/proxy/network/api/s/default/rest/wlanconf/enteryourwifihere'
    --block mac is a post.  {mac: "e8:3a:12:22:0a:49", cmd: "block-sta"}
    block_mac = 'https://192.168.2.1:443/proxy/network/api/s/default/cmd/stamgr'
    --First api call to log in, get cookies
    Post_response = {}
    
    local res, err, response_headers, status = htps.request
      {
        url = login,
        method = "POST",
        protocol = 'tlsv12',
        headers =
        {
        ["Accept"] = "*/*",
        ["Content-Type"] = "application/json",
        ["Content-Length"] = usr_credenitals:len()
        },
     
      source = ltn12.source.string(usr_credenitals),     
      sink = ltn12.sink.table(Post_response)
      }
    
    if Post_response then
     --log(res, code, response_headers)
     mmm_cookies = response_headers['set-cookie']
      if mmm_cookies then
      token = mmm_cookies:match('(TOKEN=[^;]+)'..';')
      end
    --Second api call.  This is a get
      Get_response = {}
      cmd1 = json.encode({
          enabled = 'false',
          
    })
    
     local res, err = htps.request({
         url = block_wifi,
         method = "PUT",
         protocol = 'tlsv12',
         headers = {
         cookie = token,
        ["Content-Length"] =cmd1:len(),
        ["Content-Type"] = "application/json",
        ['X-CSRF-Token'] = response_headers['x-csrf-token'],
                      },
      source = ltn12.source.string(cmd1),
          sink = ltn12.sink.table(Get_response),
            })
     
        else
      log('login failed ' .. tostring(err))
    end
     
    paultulloch, Feb 20, 2021
    #12
Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.