|
| 1 | +""" |
| 2 | + Controls the *Ubiquity mFi mPower* Power Strip with Ethernet and Wi-Fi connectivity via HTTP. |
| 3 | + Reference: https://dl.ubnt.com/guides/mfi/mFi_mPower_PRO_US_QSG.pdf |
| 4 | +
|
| 5 | + Example configuration to use port #3 on a device with URL 'http://172.17.180.53/' |
| 6 | + with the default credentials ('ubnt' for both username and password): |
| 7 | +
|
| 8 | + NetworkPowerPort: |
| 9 | + model: mfi_mpower |
| 10 | + host: 'http://172.17.180.53/' |
| 11 | + index: 3 |
| 12 | +
|
| 13 | + Custom credentials can be provided in the URL itself: |
| 14 | +
|
| 15 | + NetworkPowerPort: |
| 16 | + model: mfi_mpower |
| 17 | + host: 'http://username:password@172.17.180.53/' |
| 18 | + index: 3 |
| 19 | +""" |
| 20 | + |
| 21 | +from typing import Tuple |
| 22 | +from urllib.parse import urlparse, urljoin |
| 23 | + |
| 24 | +import requests |
| 25 | + |
| 26 | +from ..exception import ExecutionError |
| 27 | + |
| 28 | + |
| 29 | +def login(s: requests.Session, base_url: str, credentials: dict) -> None: |
| 30 | + |
| 31 | + # We need to first fetch the base url to satisfy the Cookie Monster |
| 32 | + s.get(base_url) |
| 33 | + |
| 34 | + s.post(urljoin(base_url, '/login.cgi'), |
| 35 | + data=dict(username=credentials['username'], password=credentials['password'])) |
| 36 | + |
| 37 | + |
| 38 | +# Obtain credentials and repack base_url if needed |
| 39 | +def get_credentials(base_url: str) -> Tuple[str, dict]: |
| 40 | + base_url = urlparse(base_url) |
| 41 | + |
| 42 | + if base_url.username is None or base_url.password is None: |
| 43 | + credentials = dict(username='ubnt', password='ubnt') |
| 44 | + else: |
| 45 | + credentials = dict(username=base_url.username, password=base_url.password) |
| 46 | + base_url._replace(netloc=base_url.netloc.replace(f'{base_url.username}:{base_url.password}@', '')) |
| 47 | + |
| 48 | + base_url = base_url.geturl() |
| 49 | + return (base_url, credentials) |
| 50 | + |
| 51 | + |
| 52 | +def power_set(host, port, index, value): |
| 53 | + index = int(index) |
| 54 | + value = 1 if value else 0 |
| 55 | + |
| 56 | + (base_url, credentials) = get_credentials(host) |
| 57 | + |
| 58 | + s = requests.Session() |
| 59 | + |
| 60 | + login(s, base_url, credentials) |
| 61 | + r = s.put(urljoin(base_url, f'/sensors/{index}/'), data=dict(output=value)) |
| 62 | + if r.status_code == 200 and r.headers['Content-Type'] == 'application/json': |
| 63 | + j = r.json() |
| 64 | + if j['status'] != 'success': |
| 65 | + raise ExecutionError(f"unexpected API status code: '{j['status']}', response JSON: {j}") |
| 66 | + else: |
| 67 | + raise ExecutionError(f"unexpected http response: code {r.status_code}, content type '{r.headers['Content-Type']}' and content: '{r.text}'") |
| 68 | + |
| 69 | + |
| 70 | +def power_get(host, port, index): |
| 71 | + index = int(index) |
| 72 | + |
| 73 | + s = requests.Session() |
| 74 | + |
| 75 | + (base_url, credentials) = get_credentials(host) |
| 76 | + |
| 77 | + login(s, base_url, credentials) |
| 78 | + |
| 79 | + r = s.get(urljoin(base_url, '/mfi/sensors.cgi')) |
| 80 | + if r.status_code == 200 and r.headers['Content-Type'] == 'application/json': |
| 81 | + j = r.json() |
| 82 | + if j['status'] != 'success': |
| 83 | + raise ExecutionError(f"unexpected API status code: '{j['status']}', response JSON: {j}") |
| 84 | + |
| 85 | + port = next(filter(lambda s: s['port'] == index, j['sensors']), None) |
| 86 | + if port is None: |
| 87 | + raise ExecutionError(f"port index '{index}' not found, available indices: '{[s['port'] for s in j['sensors']]}'") |
| 88 | + |
| 89 | + if port['output'] == 0: |
| 90 | + return False |
| 91 | + elif port['output'] == 1: |
| 92 | + return True |
| 93 | + else: |
| 94 | + raise ExecutionError("unexpected port output value: '{port['output']}'") |
| 95 | + else: |
| 96 | + raise ExecutionError(f"unexpected http response: code {r.status_code}, content type '{r.headers['Content-Type']}' and content: '{r.text}'") |
| 97 | + |
| 98 | + return False |
| 99 | + |
0 commit comments