-
-
Notifications
You must be signed in to change notification settings - Fork 565
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
Support for the Xiaomi Mi WiFi Repeater 2 added #278
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
from unittest import TestCase | ||
from miio import WifiRepeater | ||
from miio.wifirepeater import WifiRepeaterConfiguration, WifiRepeaterStatus | ||
import pytest | ||
|
||
|
||
class DummyWifiRepeater(WifiRepeater): | ||
def __init__(self, *args, **kwargs): | ||
self.state = { | ||
'sta': {'count': 2, 'access_policy': 0}, | ||
'mat': [ | ||
{'mac': 'aa:aa:aa:aa:aa:aa', 'ip': '192.168.1.133', | ||
'last_time': 54371873}, | ||
{'mac': 'bb:bb:bb:bb:bb:bb', 'ip': '192.168.1.156', | ||
'last_time': 54371496} | ||
], | ||
'access_list': {'mac': ''} | ||
} | ||
self.config = {'ssid': 'SSID', 'pwd': 'PWD', 'hidden': 0} | ||
self.device_info = { | ||
'life': 543452, 'cfg_time': 543452, | ||
'token': 'ffffffffffffffffffffffffffffffff', | ||
'fw_ver': '2.2.14', 'hw_ver': 'R02', | ||
'uid': 1583412143, 'api_level': 2, | ||
'mcu_fw_ver': '1000', 'wifi_fw_ver': '1.0.0', | ||
'mac': 'FF:FF:FF:FF:FF:FF', | ||
'model': 'xiaomi.repeater.v2', | ||
'ap': {'rssi': -63, 'ssid': 'SSID', | ||
'bssid': 'EE:EE:EE:EE:EE:EE', | ||
'rx': 136695922, 'tx': 1779521233}, | ||
'sta': {'count': 2, 'ssid': 'REPEATER-SSID', | ||
'hidden': 0, | ||
'assoclist': 'cc:cc:cc:cc:cc:cc;bb:bb:bb:bb:bb:bb;'}, | ||
'netif': {'localIp': '192.168.1.170', | ||
'mask': '255.255.255.0', | ||
'gw': '192.168.1.1'}, | ||
'desc': {'wifi_explorer': 1, | ||
'sn': '14923 / 20191356', 'color': 101, | ||
'channel': 'release'} | ||
} | ||
|
||
self.return_values = { | ||
'miIO.get_repeater_sta_info': self._get_state, | ||
'miIO.get_repeater_ap_info': self._get_configuration, | ||
'miIO.switch_wifi_explorer': self._set_wifi_explorer, | ||
'miIO.switch_wifi_ssid': self._set_configuration, | ||
'miIO.info': self._get_info, | ||
} | ||
self.start_state = self.state.copy() | ||
self.start_config = self.config.copy() | ||
self.start_device_info = self.device_info.copy() | ||
|
||
def send(self, command: str, parameters=None, retry_count=3): | ||
"""Overridden send() to return values from `self.return_values`.""" | ||
return self.return_values[command](parameters) | ||
|
||
def _reset_state(self): | ||
"""Revert back to the original state.""" | ||
self.state = self.start_state.copy() | ||
self.config = self.start_config.copy() | ||
self.device_info = self.start_device_info.copy() | ||
|
||
def _get_state(self, param): | ||
return self.state | ||
|
||
def _get_configuration(self, param): | ||
return self.config | ||
|
||
def _get_info(self, param): | ||
return self.device_info | ||
|
||
def _set_wifi_explorer(self, data): | ||
self.device_info['desc']['wifi_explorer'] = data[0]['wifi_explorer'] | ||
|
||
def _set_configuration(self, data): | ||
self.config = { | ||
'ssid': data[0]['ssid'], | ||
'pwd': data[0]['pwd'], | ||
'hidden': data[0]['hidden'] | ||
} | ||
|
||
self.device_info['desc']['wifi_explorer'] = data[0]['wifi_explorer'] | ||
return True | ||
|
||
|
||
@pytest.fixture(scope="class") | ||
def wifirepeater(request): | ||
request.cls.device = DummyWifiRepeater() | ||
# TODO add ability to test on a real device | ||
|
||
|
||
@pytest.mark.usefixtures("wifirepeater") | ||
class TestWifiRepeater(TestCase): | ||
def state(self): | ||
return self.device.status() | ||
|
||
def configuration(self): | ||
return self.device.configuration() | ||
|
||
def info(self): | ||
return self.device.info() | ||
|
||
def test_status(self): | ||
self.device._reset_state() | ||
|
||
assert repr(self.state()) == repr(WifiRepeaterStatus(self.device.start_state)) | ||
|
||
assert self.state().access_policy == self.device.start_state['sta']['access_policy'] | ||
assert self.state().associated_stations == self.device.start_state['mat'] | ||
|
||
def test_set_wifi_explorer(self): | ||
self.device.set_wifi_explorer(True) | ||
assert self.info().raw['desc']['wifi_explorer'] == 1 | ||
|
||
self.device.set_wifi_explorer(False) | ||
assert self.info().raw['desc']['wifi_explorer'] == 0 | ||
|
||
def test_configuration(self): | ||
self.device._reset_state() | ||
|
||
assert repr(self.configuration()) == repr(WifiRepeaterConfiguration(self.device.start_config)) | ||
|
||
assert self.configuration().ssid == self.device.start_config['ssid'] | ||
assert self.configuration().password == self.device.start_config['pwd'] | ||
assert self.configuration().ssid_hidden is (self.device.start_config['hidden'] == 1) | ||
|
||
def test_set_configuration(self): | ||
def configuration(): | ||
return self.device.configuration() | ||
|
||
dummy_configuration = { | ||
'ssid': 'SSID2', | ||
'password': 'PASSWORD2', | ||
'hidden': True, | ||
'wifi_explorer': False | ||
} | ||
|
||
self.device.set_configuration( | ||
dummy_configuration['ssid'], | ||
dummy_configuration['password'], | ||
dummy_configuration['hidden'], | ||
dummy_configuration['wifi_explorer']) | ||
assert configuration().ssid == dummy_configuration['ssid'] | ||
assert configuration().password == dummy_configuration['password'] | ||
assert configuration().ssid_hidden is dummy_configuration['hidden'] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import logging | ||
from .device import Device | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class WifiRepeaterStatus: | ||
def __init__(self, data): | ||
""" | ||
Response of a xiaomi.repeater.v2: | ||
|
||
{ | ||
'sta': {'count': 2, 'access_policy': 0}, | ||
'mat': [ | ||
{'mac': 'aa:aa:aa:aa:aa:aa', 'ip': '192.168.1.133', 'last_time': 54371873}, | ||
{'mac': 'bb:bb:bb:bb:bb:bb', 'ip': '192.168.1.156', 'last_time': 54371496} | ||
], | ||
'access_list': {'mac': ''} | ||
} | ||
""" | ||
self.data = data | ||
|
||
@property | ||
def access_policy(self) -> int: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it known what this integer represents? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, no. My assumption is the connectivity between the clients. I cannot be controlled at the moment. The method is unknown / unused of the Mi Home app. |
||
"""Access policy of the associated stations.""" | ||
return self.data['sta']['access_policy'] | ||
|
||
@property | ||
def associated_stations(self) -> dict: | ||
"""List of associated stations.""" | ||
return self.data['mat'] | ||
|
||
def __repr__(self) -> str: | ||
s = "<WifiRepeaterStatus access_policy=%s, " \ | ||
"associated_stations=%s>" % \ | ||
(self.access_policy, | ||
len(self.associated_stations)) | ||
return s | ||
|
||
|
||
class WifiRepeaterConfiguration: | ||
def __init__(self, data): | ||
""" | ||
Response of a xiaomi.repeater.v2: | ||
|
||
{'ssid': 'SSID', 'pwd': 'PWD', 'hidden': 0} | ||
""" | ||
self.data = data | ||
|
||
@property | ||
def ssid(self) -> str: | ||
return self.data['ssid'] | ||
|
||
@property | ||
def password(self) -> str: | ||
return self.data['pwd'] | ||
|
||
@property | ||
def ssid_hidden(self) -> bool: | ||
return self.data['hidden'] == 1 | ||
|
||
def __repr__(self) -> str: | ||
s = "<WifiRepeaterConfiguration ssid=%s, " \ | ||
"password=%s, " \ | ||
"ssid_hidden=%s>" % \ | ||
(self.ssid, | ||
self.password, | ||
self.ssid_hidden) | ||
return s | ||
|
||
|
||
class WifiRepeater(Device): | ||
"""Device class for Xiaomi Mi WiFi Repeater 2.""" | ||
def status(self) -> WifiRepeaterStatus: | ||
"""Return the associated stations.""" | ||
return WifiRepeaterStatus(self.send("miIO.get_repeater_sta_info", [])) | ||
|
||
def configuration(self) -> WifiRepeaterConfiguration: | ||
"""Return the configuration of the accesspoint.""" | ||
return WifiRepeaterConfiguration( | ||
self.send("miIO.get_repeater_ap_info", [])) | ||
|
||
def set_wifi_explorer(self, wifi_explorer: bool): | ||
"""Turn the WiFi explorer on/off.""" | ||
return self.send("miIO.switch_wifi_explorer", [{ | ||
'wifi_explorer': int(wifi_explorer) | ||
}]) | ||
|
||
def set_configuration(self, ssid: str, password: str, hidden: bool = False, | ||
wifi_explorer: bool = True): | ||
"""Update the configuration of the accesspoint.""" | ||
return self.send("miIO.switch_wifi_ssid", [{ | ||
'ssid': ssid, | ||
'pwd': password, | ||
'hidden': int(hidden), | ||
'wifi_explorer': int(wifi_explorer) | ||
}]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line too long (102 > 100 characters)