Skip to content

Commit d98677c

Browse files
authored
Merge pull request #1538 from enkiusz/mfi-mpower
labgrid/driver/power: Backend for Ubiquity mFi mPower
2 parents b67bfc1 + 6980c6f commit d98677c

File tree

2 files changed

+103
-0
lines changed

2 files changed

+103
-0
lines changed

doc/configuration.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,10 @@ Currently available are:
198198
``gude8316``
199199
Controls *Gude Expert Power Control 8316 PDUs* via a simple HTTP API.
200200

201+
``mfi_mpower``
202+
Controls the *Ubiquity mFi mPower* Power Strip with Ethernet and Wi-Fi connectivity via HTTP.
203+
Tested on a mFi mPower Pro EU device.
204+
201205
``netio``
202206
Controls *NETIO 4-Port PDUs* via a simple HTTP API.
203207

labgrid/driver/power/mfi_mpower.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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

Comments
 (0)