Skip to content

Commit

Permalink
Merge branch 'v3-support' of git://github.com/matsimon/unifi-api into…
Browse files Browse the repository at this point in the history
… matsimon-v3-support

* 'v3-support' of git://github.com/matsimon/unifi-api:
  Update sample scripts for v3 support and their documentation
  Implement v3 support with minimal impact on current use

Conflicts:
	unifi/controller.py
  • Loading branch information
calmh committed Sep 26, 2013
2 parents f582cb0 + 7bb08f8 commit 07619bf
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 13 deletions.
38 changes: 35 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ The following small utilities are bundled with the API:
### unifi-ls-clients

Lists the currently active clients on the networks. Takes parameters for
controller, username, password.
controller, username, password, controller version and site ID (UniFi >= 3.x)

```
jb@unifi:~ % unifi-ls-clients -c localhost -u admin -p p4ssw0rd
jb@unifi:~ % unifi-ls-clients -c localhost -u admin -p p4ssw0rd -v v3 -s default
NAME MAC AP CHAN RSSI RX TX
client-kitchen 00:24:36:9a:0d:ab Study 100 51 300 216
jborg-mbp 28:cf:da:d6:46:20 Study 100 45 300 300
Expand All @@ -41,7 +41,7 @@ According to that, an SNR of 15 dB seems like a good cutoff, and that's also
the default value in the script. You can set a higher value for testing:

```
jb@unifi:~ % unifi-low-snr-reconnect -c localhost -u admin -p p4ssw0rd --minsnr 30
jb@unifi:~ % unifi-low-snr-reconnect -c localhost -u admin -p p4ssw0rd -v v3 -s default --minsnr 30
2012-11-15 11:23:01 INFO unifi-low-snr-reconnect: Disconnecting jb-ipad/1c:ab:a7:af:05:65@Study (SNR 22 dB < 30 dB)
2012-11-15 11:23:01 INFO unifi-low-snr-reconnect: Disconnecting Annas-Iphone/74:e2:f5:97:da:7e@Living Room (SNR 29 dB < 30 dB)
```
Expand All @@ -61,6 +61,38 @@ for ap in c.get_aps():
See also the scripts `unifi-ls-clients` and `unifi-low-rssi-reconnect` for more
examples of how to use the API.

UniFi v3 Compatibility and Migration
------------------------------------
With the release of v3, UniFi gained multisite support which requires some
changes on how to interract with the API . Currently we assume v2 to be the
default, thus: Updating the API WON'T BREAK existing code using this API.

Though, for continued v2 usage we **recommend** you start explicitely
instanciating your controller in v2 mode for the day the default assumption
starts to be v3 or newer:

```python
c = Controller('192.168.1.99', 'admin', 'p4ssw0rd', 'v2')
```

With UniFi v3, connecting to the first (`default`) site, is as easy as
instanciating a controller in v3 mode:

```python
c = Controller('192.168.1.99', 'admin', 'p4ssw0rd', 'v3')
```

Connecting to a site other than `default` requires indication of both version
and the site ID:

```python
c = Controller('192.168.1.99', 'admin', 'p4ssw0rd', 'v3', 'myothersite')
```

You can find about the site ID by selecting the site in the UniFi web interface,
i.e. "My other site". Then you can find ia its URL (`https://localhost:8443/manage/s/foobar`)
that the site ID is `myothersite`.

API
---

Expand Down
4 changes: 3 additions & 1 deletion unifi-log-roaming
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ parser = argparse.ArgumentParser()
parser.add_argument('-c', '--controller', default='unifi', help='the controller address (default "unifi")')
parser.add_argument('-u', '--username', default='admin', help='the controller usernane (default "admin")')
parser.add_argument('-p', '--password', default='', help='the controller password')
parser.add_argument('-v', '--version', default='v2', help='the controller base version (default "v2")')
parser.add_argument('-s', '--siteid', default='default', help='the site ID, UniFi >=3.x only (default "default")')
args = parser.parse_args()

c = Controller(args.controller, args.username, args.password)
c = Controller(args.controller, args.username, args.password, args.version, args.siteid)

aps = c.get_aps()
ap_names = dict([(ap['mac'], ap['name']) for ap in aps])
Expand Down
4 changes: 3 additions & 1 deletion unifi-low-snr-reconnect
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ parser = argparse.ArgumentParser()
parser.add_argument('-c', '--controller', default='unifi', help='the controller address (default "unifi")')
parser.add_argument('-u', '--username', default='admin', help='the controller usernane (default "admin")')
parser.add_argument('-p', '--password', default='', help='the controller password')
parser.add_argument('-v', '--version', default='v2', help='the controller base version (default "v2")')
parser.add_argument('-s', '--siteid', default='default', help='the site ID, UniFi >=3.x only (default "default")')
parser.add_argument('-m', '--minsnr', default=15, type=int, help='(dB) minimum required client SNR (default 15)')
parser.add_argument('-i', '--checkintv', default=5, type=int, help='(s) check interval (default 5)')
parser.add_argument('-o', '--holdintv', default=300, type=int, help='(s) holddown interval (default 300)')
parser.add_argument('-d', '--debug', help='enable debug output', action='store_true')
args = parser.parse_args()

c = Controller(args.controller, args.username, args.password)
c = Controller(args.controller, args.username, args.password, args.version, args.siteid)

all_aps = c.get_aps()
ap_names = dict([(ap['mac'], ap['name']) for ap in all_aps])
Expand Down
4 changes: 3 additions & 1 deletion unifi-ls-clients
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ parser = argparse.ArgumentParser()
parser.add_argument('-c', '--controller', default='unifi', help='the controller address (default "unifi")')
parser.add_argument('-u', '--username', default='admin', help='the controller usernane (default("admin")')
parser.add_argument('-p', '--password', default='', help='the controller password')
parser.add_argument('-v', '--version', default='v2', help='the controller base version (default "v2")')
parser.add_argument('-s', '--siteid', default='default', help='the site ID, UniFi >=3.x only (default "default")')
args = parser.parse_args()

c = Controller(args.controller, args.username, args.password)
c = Controller(args.controller, args.username, args.password, args.version, args.siteid)

aps = c.get_aps()
ap_names = dict([(ap['mac'], ap['name']) for ap in aps])
Expand Down
37 changes: 30 additions & 7 deletions unifi/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class Controller:
nonexistant client) will go unreported.
>>> from unifi.controller import Controller
>>> c = Controller('192.168.1.99', 'admin', 'p4ssw0rd'
>>> c = Controller('192.168.1.99', 'admin', 'p4ssw0rd')
>>> for ap in c.get_aps():
... print 'AP named %s with MAC %s' % (ap['name'], ap['mac'])
...
Expand All @@ -36,20 +36,25 @@ class Controller:
"""

def __init__(self, host, username, password):
def __init__(self, host, username, password, version='v2', site_id='default'):
"""Create a Controller object.
Arguments:
host -- the address of the controller host; IP or name
username -- the username to log in with
password -- the password to log in with
version -- the base version of the controller API [v2|v3]
site_id -- the site ID to connect to (UniFi >= 3.x)
"""

self.host = host
self.username = username
self.password = password
self.site_id = site_id
self.url = 'https://' + host + ':8443/'
self.api_url = self.url + self._construct_api_path(version)

log.debug('Controller for %s', self.url)

cj = cookielib.CookieJar()
Expand All @@ -70,6 +75,24 @@ def _read(self, url, params=None):
res = self.opener.open(url, params)
return self._jsondec(res.read())

def _construct_api_path(self, version):
"""Returns valid base API path based on version given
The base API path for the URL is different depending on UniFi server version.
Default returns correct path for latest known stable working versions.
"""

V2_PATH = 'api/'
V3_PATH = 'api/s/' + self.site_id + '/'

if(version == 'v2'):
return V2_PATH
if(version == 'v3'):
return V3_PATH
else:
return V2_PATH

def _login(self):
log.debug('login() as %s', self.username)
params = urllib.urlencode({'login': 'login',
Expand All @@ -81,23 +104,23 @@ def get_aps(self):

js = json.dumps({'_depth': 2, 'test': None})
params = urllib.urlencode({'json': js})
return self._read(self.url + 'api/stat/device', params)
return self._read(self.api_url + 'stat/device', params)

def get_clients(self):
"""Return a list of all active clients, with significant information about each."""

return self._read(self.url + 'api/stat/sta')
return self._read(self.api_url + 'stat/sta')

def get_wlan_conf(self):
"""Return a list of configured WLANs with their configuration parameters."""

return self._read(self.url + 'api/list/wlanconf')
return self._read(self.api_url + 'list/wlanconf')

def _mac_cmd(self, target_mac, command, mgr='stamgr'):
log.debug('_mac_cmd(%s, %s)', target_mac, command)
params = urllib.urlencode({'json':
{'mac': target_mac, 'cmd': command}})
self._read(self.url + 'api/cmd/' + mgr, params)
self._read(self.api_url + 'cmd/' + mgr, params)

def block_client(self, mac):
"""Add a client to the block list.
Expand Down Expand Up @@ -165,7 +188,7 @@ def create_backup(self):

js = json.dumps({'cmd': 'backup'})
params = urllib.urlencode({'json': js})
answer = self._read(self.url + 'api/cmd/system', params)
answer = self._read(self.api_url + 'cmd/system', params)

return answer[0].get('url')

Expand Down

0 comments on commit 07619bf

Please sign in to comment.