Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to connect to a UDM Pro with this library #12

Open
richardellwood opened this issue Nov 9, 2020 · 82 comments
Open

Unable to connect to a UDM Pro with this library #12

richardellwood opened this issue Nov 9, 2020 · 82 comments

Comments

@richardellwood
Copy link

Attempting to connect to my UDM Pro with the program

from unificontrol import UnifiClient
import ssl

cert = ssl.get_server_certificate(("10.1.0.1", 443))

router = UnifiClient(host = "10.1.0.1", port = "443", username = "admin", password = "[password]", cert = cert)

I get the following error

Traceback (most recent call last):
  File "test.py", line 8, in <module>
    clients = router.list_sites()
  File "/home/sysop/.local/lib/python3.5/site-packages/unificontrol/metaprogram.py", line 123, in wrapper
    return instance(client, *a, **kw)
  File "/home/sysop/.local/lib/python3.5/site-packages/unificontrol/metaprogram.py", line 101, in __call__
    return client._execute(url, self._method, rest_dict, need_login=self._need_login)
  File "/home/sysop/.local/lib/python3.5/site-packages/unificontrol/unifi.py", line 110, in _execute
    response = resp.json()
  File "/home/sysop/.local/lib/python3.5/site-packages/requests/models.py", line 898, in json
    return complexjson.loads(self.text, **kwargs)
  File "/usr/lib/python3.5/json/__init__.py", line 319, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.5/json/decoder.py", line 339, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python3.5/json/decoder.py", line 357, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 2 column 1 (char 1)

Any advice is appreciated.
Richard

@nickovs
Copy link
Owner

nickovs commented Nov 9, 2020

Historically by default the Unifi controller is on port 8443, not port 443. If you are running a CloudKey or similar then there is a UI on 443, but it redirects you to port 8443 for the Unifi Network interface and port 7443 for the Unifi Protect interface. Try the using port 8443 and see if that fixes it (or just don't specify the port argument, since it defaults to the right port).

That said, even if that fixes it the error message is unhelpful; I'll put in some more informative error handling when I have some time.

@richardellwood
Copy link
Author

Thank you, Nickovs. 8443 isn't working for me either. I'm going to try digging into your code and see if I can figure out what's going on. If I find a solution, I'll certainly contribute.

@nickovs
Copy link
Owner

nickovs commented Nov 10, 2020

Based on the stack backtrace, it looks like the crash is happening because whatever the server is sending back can not be decoded as a JSON payload even though the HTTP request returned an OK result. This is happening at line 110 in unifi.py. This suggests to me that the endpoint that you are hitting is not the Unifi controller endpoint but something else (hence my suggestion that you check the port).

If you are up for trying to debug this yourself then you probably want to start by printing r.text just before the code tries to extract the reply body as JSON, to see what is actually getting returned.

@avleen
Copy link

avleen commented Jan 14, 2021

I've been running into this issue too, on the latest 1.8.5 firmware.

When I do the print, the output is:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"><link href="/2.css" rel="stylesheet"></head>
<body>
<div id="root"></div>
<script type="text/javascript" src="/vendor.0306759b.chunk.js"></script><script type="text/javascript" src="/main.d8e6c715.js"></script></body>
</html>

I had to edit metaprogram.py and on L77 change:

return "https://{host}:{port}/api/s/{site}/{endpoint}{path}".format(

to:

return "https://{host}:{port}/proxy/network/api/s/{site}/{endpoint}{path}".format(

@avleen
Copy link

avleen commented Jan 14, 2021

Likewise, in unifi.py, L142 needs to change to:

"auth/login"

and L161 needs to change to:

"auth/logout"

@avleen
Copy link

avleen commented Jan 14, 2021

Also in unifi.py, L114, there is on response['data'] object any more. I'm not sure what it used to look like before, but now response is just a hash full of lots of info. I assume that might be what we're looking for?
Sorry about the flood of comments, I don't know how you'd want to update the library to support the new firmware with all these changes and still be backwards compatible so I'm dumping this knowledge here :)

@avleen
Copy link

avleen commented Jan 14, 2021

Ok, hopefully last update, this is a diff of what worked in the end, for both login and various other things.
Happy to send a pull request if you want it as is, but it will break people on older firmware for sure:
https://github.com/avleen/unificontrol/commit/0c9fcfaa19fe9671e12ac68de054f1e4b47d8e31

@nickovs
Copy link
Owner

nickovs commented Jan 14, 2021

@avleen Thanks for all of these details. Can you confirm that you are also using a UDM Pro? What version of the server code are you running? I don't currently have access to a UDM Pro but if we can find a way to tell the difference and you are up for some testing then maybe we can build a version of this library that works with the UDM, the CloudKey and the stand-alone Java version.

@avleen
Copy link

avleen commented Jan 15, 2021 via email

@thundergreen
Copy link

Hello i am running in the same issue . i installed this library with pip install git+git://github.com/avleen/unificontrol.git@0c9fcfaa19fe9671e12ac68de054f1e4b47d8e31
but still getting same error :(

@avleen
Copy link

avleen commented Jan 17, 2021

Hello i am running in the same issue . i installed this library with pip install git+git://github.com/avleen/unificontrol.git@0c9fcfaa19fe9671e12ac68de054f1e4b47d8e31
but still getting same error :(

What device and firmware version do you have @thundergreen ?

@5bomen
Copy link

5bomen commented Jan 18, 2021

I am running a UDM Pro with firmware 1.8.5 - and willing to test. Though my python skills are very limited. The UDM Pro hosts multiple functions. Apart from the Network Controller there is also Protect, Access and Talk. Which is why the Network API endpoints are at /proxy/network/api/s and the authentication endpoints at /api/auth.

@SinisterStairs
Copy link

SinisterStairs commented Jan 25, 2021

FYI, I'm also getting the same error with a UDM Base. The Network Controller is 6.0.43, UDM firmware is 1.8.5.

I"m not sure if this helps you detect UDM*, but uname -a (from the UniFi OS shell) gives me:
Linux ubnt 4.1.37-v1.8.5.2964-30c04be #1 SMP Tue Dec 29 05:40:16 MST 2020 aarch64 GNU/Linux

Quite a few defaults seem to be different: I believe hostname is always unifi (and default domain is .localdomain), default port is 443, default administrator is root.

The root web application (https://unifi) lets you choose between the Network Controller (URI /network), or managing the UDM itself (/settings).

The latest API, via curl, is here: https://dl.ui.com/unifi/6.0.45-564eef1dda/unifi_sh_api

@nickovs
Copy link
Owner

nickovs commented Jan 25, 2021

It would be helpful if someone (or even several people) with UDMs, CloudKeys and locally installed Java controllers could run:

import requests
print(requests.get("https://<unifi IP address>:8443/status", verify=False).text)
print(requests.get("https://<unifi IP address>:443/status", verify=False).text)

and send me the output.

To save clogging this discussion up, maybe paste the output into a Gist and just post the link here. I'm guessing that this might be enough to let me auto-detect what sort of controller the client is trying to talk to.

@avleen
Copy link

avleen commented Jan 25, 2021 via email

@nickovs
Copy link
Owner

nickovs commented Jan 25, 2021

Can you try https://<unifi IP address>:443/proxy/network/status?

@avleen
Copy link

avleen commented Jan 25, 2021 via email

@nickovs
Copy link
Owner

nickovs commented Jan 25, 2021

Thanks. When you say "doesn't work", do you get a 401 result? If so, do you get something akin to:

{"meta":{"rc":"error","msg":"api.err.LoginRequired"},"data":[]}

in the body that is returned. Thanks.

@SinisterStairs
Copy link

SinisterStairs commented Jan 26, 2021

Yes, unauthenticated, the UDM's /proxy/network/status endpoint returns a 401. Response body is simply Unauthorized.

As verification of the response body, the response header contains:

Content-Length: 12
Content-Type:  text/plain; charset=utf-8

...so, not JSON with useful information, just plain text.

@nickovs
Copy link
Owner

nickovs commented Jan 26, 2021

@SinisterStairs Thanks for that, although it's not what I had hoped. What do you get if you try https://<unifi IP address>:443/api/auth/login or https://<unifi IP address>:443/api/auth/login?username=xxxx&password=xxxx ? Do you get a similarly unhelpful response body or do you get JSON with error details?

@SinisterStairs
Copy link

SinisterStairs commented Jan 26, 2021

Interestingly enough, I typo'd the auth login URL (I used /auth/login instead of /api/auth/login, and got a 200 using basic authentication (response text/html). I get hung up trying to pass in the username/password as query strings to /auth/login.

Using /api/auth/login gives me a JSON 401 when using basic authentication, and hangs trying to pass authentication via query strings. EDIT: This was user error, with how I constructed my curl command.

UPDATE: Need sleep, getting sloppy. I lost of track when I used GET or POST, and when I used basic authentication or query strings. By "hung up" I mean no immediate response to curl, and I hit ctrl-c before timing out.

@nickovs
Copy link
Owner

nickovs commented Jan 31, 2021

I have pushed a branch which hopefully will allow support for the Dream Machine as well as the "classic" controller. It's available in the udm-support branch. There is a new server_type argument to the client constructor; if you pass unificontrol.UNIFI_SERVER_UDM then hopefully it should work with the UDM; it defaults to support the classic controller so as not to break existing code.

It would be a big help if some of you could give this a try and tell me if it works. I don't have a UDM so it's basically based on the code @avleen posted. If I manage to lay my hands on a UDM I'll also try to write some reliable auto-detection code but for the moment you'll need to tell it which you're using.

@SinisterStairs
Copy link

SinisterStairs commented Jan 31, 2021

Thanks so much. I'm getting a unificontrol.exception.UnifiTransportError: 404: Not Found due to invalid credentials.

I tried my UDM's local account, my Unifi Cloud account, and the root SSH password.
https://gist.github.com/SinisterStairs/ab2ebbc208b5bcf34f15a3872811467a

EDIT: Will play around some more in a bit; just wanted to provide some quick feedback.
EDIT2: With curl I get a 405 using /api/auth/login and 200 using /auth/login. Changing unifi.py to use /auth/login didn't make a difference, however. WIth curl, I'm passing in username and password through basic authentication, not as query string parameters. EDIT3: This was errors with my curl command.

@nickovs
Copy link
Owner

nickovs commented Jan 31, 2021

@SinisterStairs Thanks for trying it out. In the mean time I will see if I can find access to a UDM myself.

@SinisterStairs
Copy link

SinisterStairs commented Jan 31, 2021

I tried to create a read-only user for you on my NC that could call the API w/o editing; but I don't think that's possible. If you're unable to find access to a UDM, maybe we could do a screen share sometime (e.g. Zoom). I'm on US Eastern time.

EDIT: I also wanted to thank you for your persistence, supporting a device you don't even own!

@nickovs
Copy link
Owner

nickovs commented Jan 31, 2021

@SinisterStairs Can you try running the following code before you import unificontrol or attempt to connect to your controller and post what log output you get?

import logging
logging.basicConfig(level=logging.DEBUG)

@SinisterStairs
Copy link

SinisterStairs commented Feb 1, 2021

I edited both the gist's code and comment with the latest.
https://gist.github.com/SinisterStairs/939d6a33521aed0e4e6ab36239e584f7

EDIT: To update my previous comments: Authenticating with /api/auth/login is working via curl; previous errors I reported were due to how I constructed the curl command or invoked the endpoint. My functioning curl command is in the comments within the gist above.

@nickovs
Copy link
Owner

nickovs commented Feb 1, 2021

Thanks for the debug output. Can you try adding a call to client.login() before your call to list_clients()? Since the client uses a pooled HTTP/1.1 connection it's possible that the Unifi proxy somehow marks connection as having failed after the first 401 error thus doesn't accept the login retry.

@SinisterStairs
Copy link

I updated the gist with the code including client.login(), and added the output as a new comment.
https://gist.github.com/SinisterStairs/939d6a33521aed0e4e6ab36239e584f7

@nickovs
Copy link
Owner

nickovs commented Feb 1, 2021

Thanks. It looks like there are several issues here:

  1. There seems to be some redirection going on (the code tried to request /api/auth/login but the debug message suggests that it got a 200 reply from /auth/login).
  2. It seems that the reply from a successful login on the UDM is in a different format to the reply from all the other API calls
  3. It looks like the proxy might return 404 on all subsequent requests if the first request on an HTTP/1.1 connection returns a 401 error.

The login and logout methods have always been a little anomalous compared to all of the other calls so I think that I will just need to have special case code to deal with these quirks. I will see what I can do.

@friedmanwd
Copy link

friedmanwd commented Mar 17, 2021

UniFi-CloudKey-Gen2, with 6.0.45 (Build: atag_6.0.45_14358), always gives "simplejson.errors.JSONDecodeError: Expecting value: line 2 column 1 (char 1)". Installed using pip3 and installed latest from github.

@clyra
Copy link

clyra commented Mar 17, 2021

@nickovs @thundergreen . The version of my sensor can be found here: https://github.com/clyra/unifics. The sensor uses this udm-branch (git+https://github.com/nickovs/unificontrol.git@udm-support#egg=unificontrol).

@friedmanwd I guess the udm-branch is not yet merged in the master branch so you should install the library using the above url.

@friedmanwd
Copy link

friedmanwd commented Mar 17, 2021 via email

@friedmanwd
Copy link

friedmanwd commented Mar 18, 2021 via email

@nickovs
Copy link
Owner

nickovs commented Mar 18, 2021

@friedmanwd I'm glad you got this working in the end. Yes, there needs to be some updates to this branch to make it more friendly; that's part of why I've not yet merged it onto the master branch and released it to PyPI. Hopefully I'll get a chance to do that in the next week or so.

@stevehoek
Copy link

stevehoek commented Mar 19, 2021

I recently heard some chatter about a "new API" in UniFi-OS 1.9.0 and higher.
https://www.reddit.com/r/Ubiquiti/comments/m8f8h6/udm_190_api/

Has anyone tired @nickovs 's UDM branch on a device running 1.9.x?

@draco2003
Copy link

I'm on UDMP 1.90 and controller version 6.1.61
Login works along with client list, but can't get block_client to work.
Not sure if it's a version issue or PEBKAC issue since it's the first version I'm using this library against.

@phen0
Copy link

phen0 commented Apr 8, 2021

I'm also on UDM Pro but 1.92 firmware. I noticed that client list and other functions are working like a charm but when it comes to callable commands for instance stamgr block-sta I get an 404. I read that it might be required to add to headers:

"Set-Cookie: TOKEN=xxx" and "X-CSRF-TOKEN: cafeca-cafeca-cafeca-cafecafe"

You could have a look here: https://community.ui.com/questions/UDM-Pro-Unifi-API-Question/14e5fa72-4364-4958-b81f-fdded9e71a94#answer/5261af5c-bd81-46e2-9016-d42cdf73bc14

Any chance to fix? I already had a look at the source code but I'm not sure where to store these headers and to add them again to subsequent API requests.

@nickovs
Copy link
Owner

nickovs commented Apr 8, 2021

The problem is that with the upgraded UI in the more recent controller Ubiquiti moved a great swath of the command and status endpoints. While the new arrangement does seem to be somewhat better organised than the original API, I've still not reversed out all the new endpoints and checked the new command formats.

Last weekend I upgraded my main controller to the latest version, since the new controller is now being pushed to all their products, not just the UDM. I will try to find some time to work on this soon but unfortunately this is purely a side project for me, so progress is not always as fast as other people would like. If others have input then I'm more than happy to accept pull requests!

@phen0
Copy link

phen0 commented Apr 8, 2021

Totally understood. I double checked the version. Firmware for my UDMP is already 1.9.3. Well if you don't think it's related to the headers mentioned above how could I reverse engineer the new endpoints? I already gave a try with wireshark but no luck so far.
cheers

@phen0
Copy link

phen0 commented May 3, 2021

Hey if someone might be interested. I was successful. My script can block / unblock clients on a UDM Pro controller based on Mac addresses. You can also scan the list of clients for keywords to filter retrieve the Mac address dynamically :))

@phen0
Copy link

phen0 commented May 3, 2021 via email

@gcaley
Copy link

gcaley commented May 8, 2021

Hey Linas, thanks for your feedback. Finally I managed to adjust my python script to retrieve a list of clients, filter by Mac address dynamically and to block specific clients. Cheers, Manuel

On 29. Apr 2021, at 13:47, Linas Zvirblis @.***> wrote: I tried updating it for the new API, and it does somewhat work. For retrieving data at least. See #23 <#23> However in the end I decided to go for an alternative approach and start from scratch. You can see it at TrackMan/uniman https://github.com/TrackMan/uniman, but be warned that it's just a prototype at this point. Although you may find something you can use. — You are receiving this because you commented. Reply to this email directly, view it on GitHub <#12 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AC5AEKFHROJBVDTV7TEPJQTTLFBNHANCNFSM4TP6Y2HQ.

Please do post your script! :)

@phen0
Copy link

phen0 commented May 8, 2021

I tried to post the script here in the comment...but the formatting is driving me nuts :)
The code tag doesn't work...how can I post a code snippet?

@phen0
Copy link

phen0 commented May 9, 2021

Finally I got it....

#!/usr/local/bin/python3.8
# -*- coding: utf-8 -*-
import sys
import base64
import json
from requests import Request, Session, packages
from requests.packages.urllib3.exceptions import InsecureRequestWarning

username = 'user'
password = 'pass'
unifi_url = 'https://ubnt.example.com'
site = 'default'
post = 'POST'
get = 'GET'
action = None
address = None
csrf_token = None
cookie_token = None
debug = False

tracked_headers = ['X-CSRF-TOKEN', 'CONTENT-TYPE', 'CONTENT-LENGTH', 'SET-COOKIE']
session = Session()

def callURL(verb, path, payload, parse_response=True, return_error=True):
    global csrf_token, debug, unifi_url, session

    url = unifi_url + path
    response = None
    try:
        packages.urllib3.disable_warnings(InsecureRequestWarning)
        if verb == post:
            req = Request(verb, url, json=payload)
            if not csrf_token is None:
                print("Update CSRF Token")
                session.headers.update({'X-CSRF-Token': csrf_token})
                print("...done.")
            else:
                print("CSRF Token is None.")
            response = session.send(session.prepare_request(req), verify=False)
            headers = {'Content-Type': 'application/json'}
            #req_cookies = session.request.cookies
            resp_cookies = response.cookies
            resp_headers = response.headers
            req_headers = response.request.headers
            print("---")
            #print("COOKIES --> %s" % req_cookies)
            for r in req_headers:
                if r.upper() in tracked_headers and debug:
                    print("REQ HEADER  %s=%s" % (r, req_headers[r]))
            if debug:
                print("===")
                print("COOKIES <-- %s" % resp_cookies)
            for h in resp_headers:
                if h.upper() in tracked_headers:
                    if debug:
                        print("RESP HEADER --> %s: %s" % (h, resp_headers[h]))
                    if h.upper() == 'X-CSRF-TOKEN':
                        csrf_token = resp_headers[h]
                        if debug:
                            print("Token %s=%s saved." % (h, csrf_token))
                    elif h.upper() == 'SET-COOKIE':
                        cookie_token = resp_headers[h]
                        if debug:
                            print("Cookie %s=%s saved." % (h, cookie_token))
            if debug:
                print("COOKIE / CSRF TOKEN: %s/%s" % (cookie_token, csrf_token))
            print("Response status & reason: " + str(response.status_code) + " " + str(response.reason))
        if response.status_code != 200 and response.status_code != 204 and response.status_code !=201 and return_error:
            raise Exception("Error when requesting remote url %s [%s]:%s" % (path,  response.status_code, response.text))

        if parse_response:
            return response.text
        return None
    except:
        print("Unexpected error: ",sys.exc_info()[0])
        sys.exit(2)

def login():
    global username, password

    print("LOGIN")
    print("=====")
    payload = {'password': password, 'username': username}
    #print("PAYLOAD TO SEND=%s" % payload)
    #payload['strict'] = 'True'
    response = callURL('POST', '/api/auth/login', payload)
    #print(response.content)
    #print("-----------------------------------------")
    #print("JSON", response.json())
    print("\n")

def logout():
    global session

    print("LOGOUT")
    print("======")
    response = callURL('POST', '/logout', '')
    session.cookies.clear()

def listClients():
    global action, site

    print("LIST CLIENTS")
    print("============")
    response = callURL('POST', '/proxy/network/api/s/%s/stat/sta' % site, '')
    output = json.loads(response)
    mac = None
    blocked = None
    name = None
    for client in output['data']:
      if isOnBlacklist(client):
          if "name" in client:
              name = client['name']
              print("Name: %s" % client['name'])
          if "ip" in client:
              print("IP: %s" % client['ip'])
          if "fixed_ip" in client:
              print("Fixed IP: %s" % client['fixed_ip'])
          if "mac" in client:
              mac = client['mac']
              print("MAC: %s" % mac)
          if "hostname" in client:
              print("Host mame: %s" % client['hostname'])
          if "device_name" in client:
              print("Device name: %s" % client['device_name'])
          if "blocked" in client:
              print("Checking name/mac %s/%s" % (client['name'], mac))
              blocked = client['blocked']
              if action == 'block' and not blocked:
                  print("Blocking client with mac %s" % mac)
                  blockClient(mac)
              else:
                  print("Client with mac %s blocking status is %s" % (mac, blocked))
 
          print("---")
    print("\n")

def blockClient(mac):
    global site

    path = '/proxy/network/api/s/%s/cmd/stamgr' % site
    payload = {'cmd': 'block-sta', 'mac': mac}
    response = callURL('POST', path, payload)
    print("BLOCK RESPONSE")
    print(response)

def unblockClient(mac):
    global site

    path = '/proxy/network/api/s/%s/cmd/stamgr' % site
    payload = {'cmd': 'unblock-sta', 'mac': mac}
    response = callURL('POST', path, payload)
    print("UNBLOCK RESPONSE")
    print(response)

def isOnBlacklist(client):
    match = False
    if 'hostname' in client and ("MANOLO" in client['hostname'].upper() and "IPHONE" in client['hostname'].upper()):
        match = True
    if 'name' in client and ("MANOLO" in client['name'].upper() and "IPHONE" in client['name'].upper()):
        match = True
    return match

def noisOnBlacklist(client):
    match = False
    if 'hostname' in client and "NICK" in client['hostname'].upper():
        match = True
    if 'name' in client and "NICK" in client['name'].upper():
        match = True
    if 'hostname' in client and "LUKE" in client['hostname'].upper():
        match = True
    if 'name' in client and "LUKE" in client['name'].upper():
        match = True
    return match 

def usage():
    print("Usage: ./udmCheck.py [BLOCK|UNBLOCK] [MAC ADDRESS]")
    print("e.g.   ./udmCheck.py block")
    print("e.g.   ./udmCheck.py unblock aa:bb:cc:dd:ee:ff")

#
# MAIN PROGRAMME
#
# MACS TO BLOCK:
BLOCK_LIST = ['aa:aa:aa:aa:aa:aa', 'aa:aa:aa:aa:bb:bb' ]

argv = sys.argv[1:]
if len(argv) < 1 or len(argv) > 2:
    usage()
    sys.exit(1)
else:
    login()
    if len(argv) ==  1 and argv[0].upper() == 'BLOCK':
       action = 'block'
       listClients()
    elif len(argv) ==  2 and argv[0].upper() == 'UNBLOCK':
       action = 'unblock'
       address = argv[1].lower()
       unblockClient(address)
    elif len(argv) ==  1 and argv[0].upper() == 'BLOCKLIST':
       for addr in BLOCK_LIST:
           blockClient(addr)
    elif len(argv) ==  1 and argv[0].upper() == 'UNBLOCKLIST':
       for addr in BLOCK_LIST:
           unblockClient(addr)
logout()
sys.exit(0)

@w1tw0lf
Copy link

w1tw0lf commented Jul 30, 2021

Having a similar issue, but with cloudkey that is running the same os as the UDM. Is there away to specify the server type ? Seems almost like it is being picked up as classic and not as udm.

@phen0
Copy link

phen0 commented Aug 2, 2021

hey @w1tw0lf,

did you try my script here. It should work for UDM Pro. I'm using actually.

@w1tw0lf
Copy link

w1tw0lf commented Aug 2, 2021

hey @phen0

That code works, but it isn't giving the ap's details as what I was after. Thanks for the suggestion.

@w1tw0lf
Copy link

w1tw0lf commented Aug 2, 2021

Trying to get it to work with this, https://community.home-assistant.io/t/monitoring-your-unifi-ap/259703, where it works with the original code, @phen0

@phen0
Copy link

phen0 commented Aug 2, 2021

@w1tw0lf
...hm you only need to send a GET to /proxy/network/api/s/[SITE]/stat/device
I got plenty of data for every single device. CPU, harddisk, etc.
Is this what you're looking for?

@w1tw0lf
Copy link

w1tw0lf commented Aug 2, 2021

@phen0

Looking at getting info out of ap's.

This code is what I have copied form the https://community.home-assistant.io/t/monitoring-your-unifi-ap/259703

from unificontrol import UnifiClient
from datetime import timedelta
import json
import re

fill in your unifi controller credentials

host = ''
username = ''
password = ''
site = ''
port = ''
mac = '' ## the mac address of your AP device

################# endpoints ####################

client = UnifiClient(host=host,username=username,password=password,site=site,port=port)
stat = client.stat_sysinfo()
devs = client.list_devices(mac)
clients = client.list_clients()
guests = client.list_guests()

###########################################

numclients = len(clients)
numguests = len(guests)
score = devs[0]['satisfaction']
update = stat[0]['update_available']
cpu = devs[0]['system-stats']['cpu']
ram = devs[0]['system-stats']['mem']

activity = round(devs[0]['uplink']['rx_bytes-r']/125000 + devs[0]['uplink']['tx_bytes-r']/125000,1)
seconds = devs[0]['uptime']
days = seconds // 86400
hours = (seconds - (days * 86400)) // 3600
minutes = (seconds - (days * 86400) - (hours * 3600)) // 60
uptime = str(days)+'d '+str(hours)+'h '+str(minutes)+'m'

Wifi Clients and score

Remember to adjust according to the number of your ssids

wifi0clients = sum(1 for _ in re.finditer(r'\b%s\b' % re.escape('wifi0'), str(clients)))
wifi1clients = sum(1 for _ in re.finditer(r'\b%s\b' % re.escape('wifi1'), str(clients)))
wifi0score = devs[0]['radio_table_stats'][0]['satisfaction']
wifi1score = devs[0]['radio_table_stats'][1]['satisfaction']

Wifi Clients Names###

You can further specify the clients name in total or per ssid with the following

allclients = ",".join([str([x][0]['name']) for x in clients])

wifi1clientnames = ','.join([clients[i]['name']for i in [i for i,x in enumerate(clients) if 'wifi1' in str(x)]])

wifi0clientnames = ','.join([clients[i]['name']for i in [i for i,x in enumerate(clients) if 'wifi0' in str(x)]])

It's been reported that this doesn't work for some users, so give it a try first.

Remember to add the keys allclients, wifi1clientnames, wifi0clientnames in your final json and on your sensor's config in HA.

final = json.dumps({"Clients":numclients,"Guests":numguests,"Clients_wifi0":wifi0clients ,"Clients_wifi1":wifi1clients ,"Score":score,"CPU":str(cpu),"RAM":str(ram),"Uptime":uptime,"Score_wifi0":wifi0score ,"Score_wifi1":wifi1score ,
"Activity":str(activity)+' Mbps',"Update":update})

print (final)

@phen0
Copy link

phen0 commented Aug 2, 2021

hey @w1tw0lf it shouldn't be that complicated. With my code just add a function like:

def listDevices():
    global action, site

    print("LIST DEVICES")
    print("============")
    response = callURL('unifi', 'json', 'POST', '/proxy/network/api/s/%s/stat/device' % site, '')
    output = json.loads(response)
    mac = None
    blocked = None
    name = None
    for device in output['data']:
      print("NAME -> " + device['name'])
      print("CPU  -> {:2.1%}%".format(float(device['system-stats']['cpu'])/100) )
      mem_used = device['sys_stats']['mem_used'] / 1024 / 1024
      print("MEM  -> %dM" % mem_used)
      uptime = int(device['system-stats']['uptime']) / 60 / 60
      print("UP   -> %dh" % uptime)
      print("---")

btw Nickovs code didn't work with UDM Pro hence I developed my own little python script. It's not magic but working.
cheers

@w1tw0lf
Copy link

w1tw0lf commented Aug 2, 2021

thank you @phen0

Will play around a bit.

PS: complete noob to this and still learning.

@phen0
Copy link

phen0 commented Aug 2, 2021

...okay thought you're experienced with python. If you need help please gimme a shout.
/cheers

@w1tw0lf
Copy link

w1tw0lf commented Aug 2, 2021

@phen0 thanks for the offer. If you can assist will be appreciated. Bashing my head around this for a week now.

@w1tw0lf
Copy link

w1tw0lf commented Aug 2, 2021

@phen0 managed to get it to work, have a look here, https://github.com/w1tw0lf/Unifi-AP-Device-info

need to fix a 2 issues still with the info pulled.

@phen0
Copy link

phen0 commented Aug 3, 2021

@w1tw0lf I've raised an issue in your project. Might be the better place for a discussion.

@w1tw0lf
Copy link

w1tw0lf commented Aug 3, 2021

thank you @phen0 much appreciated.

@graf0
Copy link

graf0 commented Aug 31, 2022

here it's done: https://github.com/finish06/pyunifi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests