-
-
Notifications
You must be signed in to change notification settings - Fork 558
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add support for smart pet water dispenser mmgg.pet_waterer.s1 (#1174)
* add support for smart pet water dispenser mmgg.pet_waterer.s1 * state checks added * more human-friendly properties * toggleable methods converted to boolean switch + formating + type-hint fix * set_power method renamed to is_on * comments + type-hints fix * reset_all_filters method added + manual sort for `status` output + comments + some status properties renamed to show return values e.g. days/minutes * pet_water_dispenser moved to integrations/ + time properties now returns `timedelta` objects + code clean-up + status() return object comment + README update * resolve imports after move * #1174 (review) * clean-up * fix types, fix reset_all_filters(), comments
- Loading branch information
Showing
7 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# flake8: noqa | ||
from .device import PetWaterDispenser |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import logging | ||
from typing import Any, Dict, List | ||
|
||
import click | ||
|
||
from miio.click_common import EnumType, command, format_output | ||
from miio.miot_device import MiotDevice | ||
|
||
from .status import OperatingMode, PetWaterDispenserStatus | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
MODEL_MMGG_PET_WATERER_S1 = "mmgg.pet_waterer.s1" | ||
MODEL_MMGG_PET_WATERER_S4 = "mmgg.pet_waterer.s4" | ||
|
||
SUPPORTED_MODELS: List[str] = [MODEL_MMGG_PET_WATERER_S1, MODEL_MMGG_PET_WATERER_S4] | ||
|
||
_MAPPING: Dict[str, Dict[str, int]] = { | ||
# https://home.miot-spec.com/spec/mmgg.pet_waterer.s1 | ||
# https://home.miot-spec.com/spec/mmgg.pet_waterer.s4 | ||
"cotton_left_time": {"siid": 5, "piid": 1}, | ||
"reset_cotton_life": {"siid": 5, "aiid": 1}, | ||
"reset_clean_time": {"siid": 6, "aiid": 1}, | ||
"fault": {"siid": 2, "piid": 1}, | ||
"filter_left_time": {"siid": 3, "piid": 1}, | ||
"indicator_light": {"siid": 4, "piid": 1}, | ||
"lid_up_flag": {"siid": 7, "piid": 4}, # missing on mmgg.pet_waterer.s4 | ||
"location": {"siid": 9, "piid": 2}, | ||
"mode": {"siid": 2, "piid": 3}, | ||
"no_water_flag": {"siid": 7, "piid": 1}, | ||
"no_water_time": {"siid": 7, "piid": 2}, | ||
"on": {"siid": 2, "piid": 2}, | ||
"pump_block_flag": {"siid": 7, "piid": 3}, | ||
"remain_clean_time": {"siid": 6, "piid": 1}, | ||
"reset_filter_life": {"siid": 3, "aiid": 1}, | ||
"reset_device": {"siid": 8, "aiid": 1}, | ||
"timezone": {"siid": 9, "piid": 1}, | ||
} | ||
|
||
|
||
class PetWaterDispenser(MiotDevice): | ||
"""Main class representing the Pet Waterer / Pet Drinking Fountain / Smart Pet Water | ||
Dispenser.""" | ||
|
||
mapping = _MAPPING | ||
_supported_models = SUPPORTED_MODELS | ||
|
||
@command( | ||
default_output=format_output( | ||
"", | ||
"On: {result.is_on}\n" | ||
"Mode: {result.mode}\n" | ||
"LED on: {result.is_led_on}\n" | ||
"Lid up: {result.is_lid_up}\n" | ||
"No water: {result.is_no_water}\n" | ||
"Time without water: {result.no_water_minutes}\n" | ||
"Pump blocked: {result.is_pump_blocked}\n" | ||
"Error detected: {result.is_error_detected}\n" | ||
"Days before cleaning left: {result.before_cleaning_days}\n" | ||
"Cotton filter live left: {result.cotton_left_days}\n" | ||
"Sponge filter live left: {result.sponge_filter_left_days}\n" | ||
"Location: {result.location}\n" | ||
"Timezone: {result.timezone}\n", | ||
) | ||
) | ||
def status(self) -> PetWaterDispenserStatus: | ||
"""Retrieve properties.""" | ||
data = { | ||
prop["did"]: prop["value"] if prop["code"] == 0 else None | ||
for prop in self.get_properties_for_mapping() | ||
} | ||
|
||
_LOGGER.debug(data) | ||
|
||
return PetWaterDispenserStatus(data) | ||
|
||
@command(default_output=format_output("Turning device on")) | ||
def on(self) -> List[Dict[str, Any]]: | ||
"""Turn device on.""" | ||
return self.set_property("on", True) | ||
|
||
@command(default_output=format_output("Turning device off")) | ||
def off(self) -> List[Dict[str, Any]]: | ||
"""Turn device off.""" | ||
return self.set_property("on", False) | ||
|
||
@command( | ||
click.argument("led", type=bool), | ||
default_output=format_output( | ||
lambda led: "Turning LED on" if led else "Turning LED off" | ||
), | ||
) | ||
def set_led(self, led: bool) -> List[Dict[str, Any]]: | ||
"""Toggle indicator light on/off.""" | ||
if led: | ||
return self.set_property("indicator_light", True) | ||
return self.set_property("indicator_light", False) | ||
|
||
@command( | ||
click.argument("mode", type=EnumType(OperatingMode)), | ||
default_output=format_output('Changing mode to "{mode.name}"'), | ||
) | ||
def set_mode(self, mode: OperatingMode) -> List[Dict[str, Any]]: | ||
"""Switch operation mode.""" | ||
return self.set_property("mode", mode.value) | ||
|
||
@command(default_output=format_output("Resetting sponge filter")) | ||
def reset_sponge_filter(self) -> Dict[str, Any]: | ||
"""Reset sponge filter.""" | ||
return self.call_action("reset_filter_life") | ||
|
||
@command(default_output=format_output("Resetting cotton filter")) | ||
def reset_cotton_filter(self) -> Dict[str, Any]: | ||
"""Reset cotton filter.""" | ||
return self.call_action("reset_cotton_life") | ||
|
||
@command(default_output=format_output("Resetting all filters")) | ||
def reset_all_filters(self) -> List[Dict[str, Any]]: | ||
"""Reset all filters [cotton, sponge].""" | ||
return [self.reset_cotton_filter(), self.reset_sponge_filter()] | ||
|
||
@command(default_output=format_output("Resetting cleaning time")) | ||
def reset_cleaning_time(self) -> Dict[str, Any]: | ||
"""Reset cleaning time counter.""" | ||
return self.call_action("reset_clean_time") | ||
|
||
@command(default_output=format_output("Resetting device")) | ||
def reset(self) -> Dict[str, Any]: | ||
"""Reset device.""" | ||
return self.call_action("reset_device") | ||
|
||
@command( | ||
click.argument("timezone", type=click.IntRange(-12, 12)), | ||
default_output=format_output('Changing timezone to "{timezone}"'), | ||
) | ||
def set_timezone(self, timezone: int) -> List[Dict[str, Any]]: | ||
"""Change timezone.""" | ||
return self.set_property("timezone", timezone) | ||
|
||
@command( | ||
click.argument("location", type=str), | ||
default_output=format_output('Changing location to "{location}"'), | ||
) | ||
def set_location(self, location: str) -> List[Dict[str, Any]]: | ||
"""Change location.""" | ||
return self.set_property("location", location) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import enum | ||
from datetime import timedelta | ||
from typing import Any, Dict | ||
|
||
from miio.miot_device import DeviceStatus | ||
|
||
|
||
class OperatingMode(enum.Enum): | ||
Normal = 1 | ||
Smart = 2 | ||
|
||
|
||
class PetWaterDispenserStatus(DeviceStatus): | ||
"""Container for status reports from Pet Water Dispenser.""" | ||
|
||
def __init__(self, data: Dict[str, Any]) -> None: | ||
"""Response of Pet Water Dispenser (mmgg.pet_waterer.s1) | ||
[ | ||
{'code': 0, 'did': 'cotton_left_time', 'piid': 1, 'siid': 5, 'value': 10}, | ||
{'code': 0, 'did': 'fault', 'piid': 1, 'siid': 2, 'value': 0}, | ||
{'code': 0, 'did': 'filter_left_time', 'piid': 1, 'siid': 3, 'value': 10}, | ||
{'code': 0, 'did': 'indicator_light', 'piid': 1, 'siid': 4, 'value': True}, | ||
{'code': 0, 'did': 'lid_up_flag', 'piid': 4, 'siid': 7, 'value': False}, | ||
{'code': 0, 'did': 'location', 'piid': 2, 'siid': 9, 'value': 'ru'}, | ||
{'code': 0, 'did': 'mode', 'piid': 3, 'siid': 2, 'value': 1}, | ||
{'code': 0, 'did': 'no_water_flag', 'piid': 1, 'siid': 7, 'value': True}, | ||
{'code': 0, 'did': 'no_water_time', 'piid': 2, 'siid': 7, 'value': 0}, | ||
{'code': 0, 'did': 'on', 'piid': 2, 'siid': 2, 'value': True}, | ||
{'code': 0, 'did': 'pump_block_flag', 'piid': 3, 'siid': 7, 'value': False}, | ||
{'code': 0, 'did': 'remain_clean_time', 'piid': 1, 'siid': 6, 'value': 4}, | ||
{'code': 0, 'did': 'timezone', 'piid': 1, 'siid': 9, 'value': 3} | ||
] | ||
""" | ||
self.data = data | ||
|
||
@property | ||
def sponge_filter_left_days(self) -> timedelta: | ||
"""Filter life time remaining in days.""" | ||
return timedelta(days=self.data["filter_left_time"]) | ||
|
||
@property | ||
def is_on(self) -> bool: | ||
"""True if device is on.""" | ||
return self.data["on"] | ||
|
||
@property | ||
def mode(self) -> OperatingMode: | ||
"""OperatingMode.""" | ||
return OperatingMode(self.data["mode"]) | ||
|
||
@property | ||
def is_led_on(self) -> bool: | ||
"""True if enabled.""" | ||
return self.data["indicator_light"] | ||
|
||
@property | ||
def cotton_left_days(self) -> timedelta: | ||
"""Cotton filter life time remaining in days.""" | ||
return timedelta(days=self.data["cotton_left_time"]) | ||
|
||
@property | ||
def before_cleaning_days(self) -> timedelta: | ||
"""Days before cleaning.""" | ||
return timedelta(days=self.data["remain_clean_time"]) | ||
|
||
@property | ||
def is_no_water(self) -> bool: | ||
"""True if there is no water left.""" | ||
if self.data["no_water_flag"]: | ||
return False | ||
return True | ||
|
||
@property | ||
def no_water_minutes(self) -> timedelta: | ||
"""Minutes without water.""" | ||
return timedelta(minutes=self.data["no_water_time"]) | ||
|
||
@property | ||
def is_pump_blocked(self) -> bool: | ||
"""True if pump is blocked.""" | ||
return self.data["pump_block_flag"] | ||
|
||
@property | ||
def is_lid_up(self) -> bool: | ||
"""True if lid is up.""" | ||
return self.data["lid_up_flag"] | ||
|
||
@property | ||
def timezone(self) -> int: | ||
"""Timezone from -12 to +12.""" | ||
return self.data["timezone"] | ||
|
||
@property | ||
def location(self) -> str: | ||
"""Device location string.""" | ||
return self.data["location"] | ||
|
||
@property | ||
def is_error_detected(self) -> bool: | ||
"""True if fault detected.""" | ||
return self.data["fault"] > 0 |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from datetime import timedelta | ||
|
||
from ..status import OperatingMode, PetWaterDispenserStatus | ||
|
||
data = { | ||
"cotton_left_time": 10, | ||
"fault": 0, | ||
"filter_left_time": 10, | ||
"indicator_light": True, | ||
"lid_up_flag": False, | ||
"location": "ru", | ||
"mode": 1, | ||
"no_water_flag": True, | ||
"no_water_time": 0, | ||
"on": True, | ||
"pump_block_flag": False, | ||
"remain_clean_time": 2, | ||
"timezone": 3, | ||
} | ||
|
||
|
||
def test_status(): | ||
status = PetWaterDispenserStatus(data) | ||
|
||
assert status.is_on is True | ||
assert status.sponge_filter_left_days == timedelta(days=10) | ||
assert status.mode == OperatingMode(1) | ||
assert status.is_led_on is True | ||
assert status.cotton_left_days == timedelta(days=10) | ||
assert status.before_cleaning_days == timedelta(days=2) | ||
assert status.is_no_water is False | ||
assert status.no_water_minutes == timedelta(minutes=0) | ||
assert status.is_pump_blocked is False | ||
assert status.is_lid_up is False | ||
assert status.timezone == 3 | ||
assert status.location == "ru" | ||
assert status.is_error_detected is False |