Skip to content

Commit

Permalink
Add Device.get_properties(), cleanup devices using get_prop (#657)
Browse files Browse the repository at this point in the history
* Add Device.get_properties(), cleanup devices using get_prop

This will move common handling for property request splitting,
and verifying the results, into the Device class.

Also, add tests for this functionality.

* Add pytest-mock to test reqs

* convert miotdevice to use the helper, avoid name clash with it

* fix tests by using get_properties_for_mapping for dummies, too
  • Loading branch information
rytilahti committed Apr 18, 2020
1 parent 942ce61 commit 8f16c1b
Show file tree
Hide file tree
Showing 28 changed files with 82 additions and 289 deletions.
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Expand Up @@ -23,7 +23,7 @@ steps:
- script: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-azurepipelines pytest-cov
pip install pytest pytest-azurepipelines pytest-cov pytest-mock
displayName: 'Install dependencies'

- script: |
Expand Down
16 changes: 1 addition & 15 deletions miio/airdehumidifier.py
Expand Up @@ -236,21 +236,7 @@ def status(self) -> AirDehumidifierStatus:

properties = AVAILABLE_PROPERTIES[self.model]

_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_prop", _props[:1]))
_props[:] = _props[1:]

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.error(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)
values = self.get_properties(properties, max_properties=1)

return AirDehumidifierStatus(
defaultdict(lambda: None, zip(properties, values)), self.device_info
Expand Down
18 changes: 1 addition & 17 deletions miio/airfresh.py
Expand Up @@ -228,23 +228,7 @@ def status(self) -> AirFreshStatus:
"led",
]

# A single request is limited to 16 properties. Therefore the
# properties are divided into multiple requests
_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_prop", _props[:15]))
_props[:] = _props[15:]

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.debug(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)
values = self.get_properties(properties, max_properties=15)

return AirFreshStatus(defaultdict(lambda: None, zip(properties, values)))

Expand Down
19 changes: 1 addition & 18 deletions miio/airfresh_t2017.py
Expand Up @@ -280,24 +280,7 @@ def status(self) -> AirFreshStatus:
"""Retrieve properties."""

properties = AVAILABLE_PROPERTIES[self.model]

# A single request is limited to 16 properties. Therefore the
# properties are divided into multiple requests
_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_prop", _props[:15]))
_props[:] = _props[15:]

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.debug(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)
values = self.get_properties(properties, max_properties=15)

return AirFreshStatus(defaultdict(lambda: None, zip(properties, values)))

Expand Down
16 changes: 1 addition & 15 deletions miio/airhumidifier.py
Expand Up @@ -309,21 +309,7 @@ def status(self) -> AirHumidifierStatus:
if self.model in [MODEL_HUMIDIFIER_CA1, MODEL_HUMIDIFIER_CB1]:
_props_per_request = 1

_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_prop", _props[:_props_per_request]))
_props[:] = _props[_props_per_request:]

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.error(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)
values = self.get_properties(properties, max_properties=_props_per_request)

return AirHumidifierStatus(
defaultdict(lambda: None, zip(properties, values)), self.device_info
Expand Down
16 changes: 1 addition & 15 deletions miio/airhumidifier_mjjsq.py
Expand Up @@ -167,21 +167,7 @@ def status(self) -> AirHumidifierStatus:
"""Retrieve properties."""

properties = AVAILABLE_PROPERTIES[self.model]
_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_prop", _props[:1]))
_props[:] = _props[1:]

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.error(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)
values = self.get_properties(properties, max_properties=1)

return AirHumidifierStatus(defaultdict(lambda: None, zip(properties, values)))

Expand Down
18 changes: 1 addition & 17 deletions miio/airpurifier.py
Expand Up @@ -409,23 +409,7 @@ def status(self) -> AirPurifierStatus:
"button_pressed",
]

# A single request is limited to 16 properties. Therefore the
# properties are divided into multiple requests
_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_prop", _props[:15]))
_props[:] = _props[15:]

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.debug(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)
values = self.get_properties(properties, max_properties=15)

return AirPurifierStatus(defaultdict(lambda: None, zip(properties, values)))

Expand Down
2 changes: 1 addition & 1 deletion miio/airpurifier_miot.py
Expand Up @@ -297,7 +297,7 @@ def status(self) -> AirPurifierMiotStatus:
return AirPurifierMiotStatus(
{
prop["did"]: prop["value"] if prop["code"] == 0 else None
for prop in self.get_properties()
for prop in self.get_properties_for_mapping()
}
)

Expand Down
12 changes: 1 addition & 11 deletions miio/ceil.py
Expand Up @@ -112,17 +112,7 @@ class Ceil(Device):
def status(self) -> CeilStatus:
"""Retrieve properties."""
properties = ["power", "bright", "cct", "snm", "dv", "bl", "ac"]
values = self.send("get_prop", properties)

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.debug(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)
values = self.get_properties(properties)

return CeilStatus(defaultdict(lambda: None, zip(properties, values)))

Expand Down
2 changes: 1 addition & 1 deletion miio/chuangmi_camera.py
Expand Up @@ -166,7 +166,7 @@ def status(self) -> CameraStatus:
"mini_level",
]

values = self.send("get_prop", properties)
values = self.get_properties(properties)

return CameraStatus(dict(zip(properties, values)))

Expand Down
12 changes: 1 addition & 11 deletions miio/chuangmi_plug.py
Expand Up @@ -134,17 +134,7 @@ def __init__(
def status(self) -> ChuangmiPlugStatus:
"""Retrieve properties."""
properties = AVAILABLE_PROPERTIES[self.model].copy()
values = self.send("get_prop", properties)

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.debug(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)
values = self.get_properties(properties)

if self.model == MODEL_CHUANGMI_PLUG_V3:
load_power = self.send("get_power") # Response: [300]
Expand Down
33 changes: 33 additions & 0 deletions miio/device.py
Expand Up @@ -181,3 +181,36 @@ def configure_wifi(self, ssid, password, uid=0, extra_params=None):
params = {"ssid": ssid, "passwd": password, "uid": uid, **extra_params}

return self._protocol.send("miIO.config_router", params)[0]

def get_properties(self, properties, *, max_properties=None):
"""Request properties in slices based on given max_properties.
This is necessary as some devices have limitation on how many
properties can be queried at once.
If `max_properties` is None, all properties are requested at once.
:param list properties: List of properties to query from the device.
:param int max_properties: Number of properties that can be requested at once.
:return List of property values.
"""
_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_prop", _props[:max_properties]))
if max_properties is None:
break

_props[:] = _props[max_properties:]

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.debug(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)

return values
36 changes: 2 additions & 34 deletions miio/fan.py
Expand Up @@ -418,21 +418,7 @@ def status(self) -> FanStatus:
if self.model in [MODEL_FAN_SA1, MODEL_FAN_ZA1, MODEL_FAN_ZA3, MODEL_FAN_ZA4]:
_props_per_request = 1

_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_prop", _props[:_props_per_request]))
_props[:] = _props[_props_per_request:]

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.error(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)
values = self.get_properties(properties, max_properties=_props_per_request)

return FanStatus(dict(zip(properties, values)))

Expand Down Expand Up @@ -662,25 +648,7 @@ def __init__(
def status(self) -> FanStatusP5:
"""Retrieve properties."""
properties = AVAILABLE_PROPERTIES[self.model]

# A single request is limited to 16 properties. Therefore the
# properties are divided into multiple requests
_props_per_request = 15
_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_prop", _props[:_props_per_request]))
_props[:] = _props[_props_per_request:]

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.error(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)
values = self.get_properties(properties, max_properties=15)

return FanStatusP5(dict(zip(properties, values)))

Expand Down
16 changes: 1 addition & 15 deletions miio/heater.py
Expand Up @@ -196,21 +196,7 @@ def status(self) -> HeaterStatus:
if self.model in [MODEL_HEATER_MA1, MODEL_HEATER_ZA1]:
_props_per_request = 1

_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_prop", _props[:_props_per_request]))
_props[:] = _props[_props_per_request:]

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.error(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)
values = self.get_properties(properties, max_properties=_props_per_request)

return HeaterStatus(dict(zip(properties, values)))

Expand Down
22 changes: 2 additions & 20 deletions miio/miot_device.py
Expand Up @@ -20,31 +20,13 @@ def __init__(
self.mapping = mapping
super().__init__(ip, token, start_id, debug, lazy_discover)

def get_properties(self) -> list:
def get_properties_for_mapping(self) -> list:
"""Retrieve raw properties based on mapping."""

# We send property key in "did" because it's sent back via response and we can identify the property.
properties = [{"did": k, **v} for k, v in self.mapping.items()]

# A single request is limited to 16 properties. Therefore the
# properties are divided into multiple requests
_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_properties", _props[:15]))
_props[:] = _props[15:]

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.debug(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)

return values
return self.get_properties(properties, max_properties=15)

def set_property(self, property_key: str, value):
"""Sets property value."""
Expand Down
17 changes: 2 additions & 15 deletions miio/philips_bulb.py
Expand Up @@ -13,10 +13,7 @@
MODEL_PHILIPS_LIGHT_BULB = "philips.light.bulb"
MODEL_PHILIPS_LIGHT_HBULB = "philips.light.hbulb"

AVAILABLE_PROPERTIES_COMMON = [
"power",
"dv",
]
AVAILABLE_PROPERTIES_COMMON = ["power", "dv"]

AVAILABLE_PROPERTIES = {
MODEL_PHILIPS_LIGHT_HBULB: AVAILABLE_PROPERTIES_COMMON + ["bri"],
Expand Down Expand Up @@ -121,17 +118,7 @@ def status(self) -> PhilipsBulbStatus:
"""Retrieve properties."""

properties = AVAILABLE_PROPERTIES[self.model]
values = self.send("get_prop", properties)

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.debug(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count,
values_count,
)
values = self.get_properties(properties)

return PhilipsBulbStatus(defaultdict(lambda: None, zip(properties, values)))

Expand Down

0 comments on commit 8f16c1b

Please sign in to comment.