Skip to content

Commit

Permalink
Support for the Xiaomi Mi WiFi Repeater 2 added (#278) (Closes: #275)
Browse files Browse the repository at this point in the history
  • Loading branch information
syssi authored Mar 26, 2018
1 parent 42e6035 commit e307486
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 0 deletions.
1 change: 1 addition & 0 deletions miio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from miio.chuangmi_ir import ChuangmiIr
from miio.fan import Fan
from miio.wifispeaker import WifiSpeaker
from miio.wifirepeater import WifiRepeater
from miio.airqualitymonitor import AirQualityMonitor
from miio.airconditioningcompanion import AirConditioningCompanion
from miio.yeelight import Yeelight
Expand Down
145 changes: 145 additions & 0 deletions miio/tests/test_wifirepeater.py
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_roaming(self):
self.device.set_wifi_roaming(True)
assert self.info().raw['desc']['wifi_explorer'] == 1

self.device.set_wifi_roaming(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']
97 changes: 97 additions & 0 deletions miio/wifirepeater.py
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:
"""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_roaming(self, wifi_roaming: bool):
"""Turn the WiFi roaming on/off."""
return self.send("miIO.switch_wifi_explorer", [{
'wifi_explorer': int(wifi_roaming)
}])

def set_configuration(self, ssid: str, password: str, hidden: bool = False,
wifi_roaming: bool = False):
"""Update the configuration of the accesspoint."""
return self.send("miIO.switch_wifi_ssid", [{
'ssid': ssid,
'pwd': password,
'hidden': int(hidden),
'wifi_explorer': int(wifi_roaming)
}])

0 comments on commit e307486

Please sign in to comment.