Skip to content
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

feat: Add full support for Xiaomi Purifier Elite (zhimi.airp.meb1) #1995

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 60 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,23 +1,71 @@
# Byte-compiled files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
*.egg
*.egg-info/
dist/
build/
eggs/
sdist/
wheels/

__pycache__
.idea/
.cache/
.mypy_cache/
.tox/
.venv/
# Installer logs
pip-log.txt
pip-delete-this-directory.txt

.coverage
# Virtual environments
.env/
.venv/
env/
venv/
ENV/
env.bak/
venv.bak/

# generated apidocs
docs/_build/
docs/api/
# Pytest
.cache/
.tox/

# IDE configurations
.idea/
.vscode/
.vscode/settings.json

# pycharm shenanigans
*.orig
*_BACKUP_*
*_BASE_*
*_LOCAL_*
*_REMOTE_*

# Coverage reports
.coverage
*.cover
*.coverage.*

# Testing files
test-results/
.nox/

# MyPy
.mypy_cache/

# Sphinx documentation
docs/_build/
docs/api/

# Local files
*.log
*.swp
.DS_Store
*.tmp
*.temp
*.bak

# Git metadata
*.rej
*.un~
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ integration, this library supports also the following devices:
* Xiaomi Smart Pet Water Dispenser (mmgg.pet_waterer.s1, s4, wi11)
* Xiaomi Mi Smart Humidifer S (jsqs, jsq5)
* Xiaomi Mi Robot Vacuum Mop 2 (Pro+, Ultra)
* Xiaomi Air Purifier Elite (zhimi.airp.meb1)

*Feel free to create a pull request to add support for new devices as
well as additional features for already supported ones.*
Expand Down
149 changes: 145 additions & 4 deletions miio/integrations/zhimi/airpurifier/airpurifier_miot.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@
# Screen
"led_brightness": {"siid": 13, "piid": 2},
# Device Display Unit
"device-display-unit": {"siid": 14, "piid": 1},
"device_display_unit": {"siid": 14, "piid": 1},
}

# https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-za1:2
Expand Down Expand Up @@ -258,11 +258,49 @@
"filter_rfid_tag": {"siid": 14, "piid": 1},
"filter_rfid_product_id": {"siid": 14, "piid": 3},
# Device Display Unit
"device-display-unit": {"siid": 16, "piid": 1},
"device_display_unit": {"siid": 16, "piid": 1},
# Other
"gestures": {"siid": 15, "piid": 13},
}

# https://home.miot-spec.com/spec/zhimi.airp.meb1
_MAPPING_MEB1 = {
# Air Purifier (siid=2)
"power": {"siid": 2, "piid": 1},
"fault": {"siid": 2, "piid": 2},
"mode": {"siid": 2, "piid": 4},
"fan_level": {"siid": 2, "piid": 5},
"plasma": {"siid": 2, "piid": 6},
"uv": {"siid": 2, "piid": 7},
# Environment (siid=3)
"pm2_5_density": {"siid": 3, "piid": 4},
"pm10_density": {"siid": 3, "piid": 8},
"aqi": {"siid": 3, "piid": 9},
"humidity": {"siid": 3, "piid": 1},
"temperature": {"siid": 3, "piid": 7},
# Filter (siid=4)
"filter_life_remaining": {"siid": 4, "piid": 1},
"filter_hours_used": {"siid": 4, "piid": 3},
# Alarm (siid=6)
"buzzer": {"siid": 6, "piid": 1},
"buzzer_volume": {"siid": 6, "piid": 2},
# Physical Control Locked (siid=8)
"child_lock": {"siid": 8, "piid": 1},
# Custom Service (siid=9)
"motor_speed": {"siid": 9, "piid": 1},
"reboot_cause": {"siid": 9, "piid": 8},
"country_code": {"siid": 9, "piid": 11},
# AQI (siid=11)
"aqi_realtime_update_duration": {"siid": 11, "piid": 4},
# RFID (siid=12)
"filter_rfid_tag": {"siid": 12, "piid": 1},
"filter_rfid_product_id": {"siid": 12, "piid": 3},
# Screen (siid=13)
"led_brightness": {"siid": 13, "piid": 2},
# Device Display Unit (siid=15)
"temperature_display_unit": {"siid": 15, "piid": 1},
}


_MAPPINGS = {
"zhimi.airpurifier.ma4": _MAPPING, # airpurifier 3
Expand All @@ -281,6 +319,7 @@
"zhimi.airpurifier.rma2": _MAPPING_RMA2, # airpurifier 4 lite
"zhimi.airp.rmb1": _MAPPING_RMB1, # airpurifier 4 lite
"zhimi.airpurifier.za1": _MAPPING_ZA1, # smartmi air purifier
"zhimi.airp.meb1": _MAPPING_MEB1, # air purifier elite
}

# Models requiring reversed led brightness value
Expand All @@ -290,6 +329,7 @@
"zhimi.airp.mb5a",
"zhimi.airp.vb4",
"zhimi.airp.rmb1",
"zhimi.airp.meb1",
]


Expand All @@ -307,6 +347,14 @@
Off = 2


class FaultCode(enum.Enum):
NO_FAULT = 0
SENSOR_PM_ERROR = 1
SENSOR_HUM_ERROR = 2
NO_FILTER = 4
UNKNOWN_ERROR = -1


class AirPurifierMiotStatus(DeviceStatus):
"""Container for status reports from the air purifier.

Expand Down Expand Up @@ -429,11 +477,66 @@
return round(temperate, 1) if temperate is not None else None

@property
@sensor("PM10 Density", unit="μg/m³")
def pm10_density(self) -> Optional[float]:
"""Current temperature, if available."""
"""Current PM10 density, if available."""
pm10_density = self.data.get("pm10_density")
return round(pm10_density, 1) if pm10_density is not None else None

@property
@sensor("PM2.5 Density", unit="μg/m³")
def pm25_density(self) -> Optional[float]:
"""Return the PM2.5 density."""
return self.data.get("pm2_5_density")

Check warning on line 490 in miio/integrations/zhimi/airpurifier/airpurifier_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/zhimi/airpurifier/airpurifier_miot.py#L490

Added line #L490 was not covered by tests

@property
def is_plasma_on(self) -> bool:
"""Return True if plasma is on."""
return bool(self.data.get("plasma"))

Check warning on line 495 in miio/integrations/zhimi/airpurifier/airpurifier_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/zhimi/airpurifier/airpurifier_miot.py#L495

Added line #L495 was not covered by tests

@property
@setting("Plasma", setter_name="set_plasma")
def plasma(self) -> str:
"""Plasma state."""
return "on" if self.is_plasma_on else "off"

Check warning on line 501 in miio/integrations/zhimi/airpurifier/airpurifier_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/zhimi/airpurifier/airpurifier_miot.py#L501

Added line #L501 was not covered by tests

@property
def is_uv_on(self) -> bool:
"""Return True if UV is on."""
return bool(self.data.get("uv"))

Check warning on line 506 in miio/integrations/zhimi/airpurifier/airpurifier_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/zhimi/airpurifier/airpurifier_miot.py#L506

Added line #L506 was not covered by tests

@property
@setting("UV", setter_name="set_uv")
def uv(self) -> str:
"""UV state."""
return "on" if self.is_uv_on else "off"

Check warning on line 512 in miio/integrations/zhimi/airpurifier/airpurifier_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/zhimi/airpurifier/airpurifier_miot.py#L512

Added line #L512 was not covered by tests

@property
@sensor("Fault Code", unit="")
def fault(self) -> Optional[FaultCode]:
"""Return fault code if any."""
fault_code = self.data.get("fault")
try:
return FaultCode(fault_code) if fault_code is not None else None
except ValueError:
_LOGGER.warning("Unknown fault code: %s", fault_code)
return None

Check warning on line 523 in miio/integrations/zhimi/airpurifier/airpurifier_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/zhimi/airpurifier/airpurifier_miot.py#L518-L523

Added lines #L518 - L523 were not covered by tests

@property
def temperature_display_unit(self) -> Optional[int]:
"""Return temperature display unit."""
return self.data.get("temperature_display_unit")

Check warning on line 528 in miio/integrations/zhimi/airpurifier/airpurifier_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/zhimi/airpurifier/airpurifier_miot.py#L528

Added line #L528 was not covered by tests

@property
def country_code(self) -> Optional[int]:
"""Return country code."""
return self.data.get("country_code")

Check warning on line 533 in miio/integrations/zhimi/airpurifier/airpurifier_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/zhimi/airpurifier/airpurifier_miot.py#L533

Added line #L533 was not covered by tests

@property
def reboot_cause(self) -> Optional[int]:
"""Return reboot cause."""
return self.data.get("reboot_cause")

Check warning on line 538 in miio/integrations/zhimi/airpurifier/airpurifier_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/zhimi/airpurifier/airpurifier_miot.py#L538

Added line #L538 was not covered by tests

@property
def fan_level(self) -> Optional[int]:
"""Current fan level."""
Expand Down Expand Up @@ -530,13 +633,19 @@
default_output=format_output(
"",
"Power: {result.power}\n"
"Fault Code: {result.fault}\n"
"Fault Description: {result.fault_description}\n"
"Plasma: {result.plasma}\n"
"UV: {result.uv}\n"
"Anion: {result.anion}\n"
"AQI: {result.aqi} μg/m³\n"
"TVOC: {result.tvoc}\n"
"Average AQI: {result.average_aqi} μg/m³\n"
"Humidity: {result.humidity} %\n"
"Temperature: {result.temperature} °C\n"
"Temperature Unit: {result.temperature_display_unit}\n"
"PM10 Density: {result.pm10_density} μg/m³\n"
"PM2.5 Density: {result.pm25_density} μg/m³\n"
"Fan Level: {result.fan_level}\n"
"Mode: {result.mode}\n"
"LED: {result.led}\n"
Expand All @@ -555,7 +664,9 @@
"Motor speed: {result.motor_speed} rpm\n"
"Filter RFID product id: {result.filter_rfid_product_id}\n"
"Filter RFID tag: {result.filter_rfid_tag}\n"
"Filter type: {result.filter_type}\n",
"Filter type: {result.filter_type}\n"
"Country Code: {result.country_code}\n"
"Reboot Cause: {result.reboot_cause}\n",
)
)
def status(self) -> AirPurifierMiotStatus:
Expand Down Expand Up @@ -764,3 +875,33 @@
raise ValueError("Invalid brightness level: %s" % level)

return self.set_property("led_brightness_level", level)

@command(
click.argument("plasma", type=bool),
default_output=format_output(
lambda plasma: "Turning on plasma" if plasma else "Turning off plasma"
),
)
def set_plasma(self, plasma: bool):
"""Set plasma on/off."""
if "plasma" not in self._get_mapping():
raise UnsupportedFeatureException(

Check warning on line 888 in miio/integrations/zhimi/airpurifier/airpurifier_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/zhimi/airpurifier/airpurifier_miot.py#L888

Added line #L888 was not covered by tests
"Unsupported plasma for model '%s'" % self.model
)

return self.set_property("plasma", plasma)

Check warning on line 892 in miio/integrations/zhimi/airpurifier/airpurifier_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/zhimi/airpurifier/airpurifier_miot.py#L892

Added line #L892 was not covered by tests

@command(
click.argument("uv", type=bool),
default_output=format_output(
lambda uv: "Turning on UV" if uv else "Turning off UV"
),
)
def set_uv(self, uv: bool):
"""Set UV on/off."""
if "uv" not in self._get_mapping():
raise UnsupportedFeatureException(

Check warning on line 903 in miio/integrations/zhimi/airpurifier/airpurifier_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/zhimi/airpurifier/airpurifier_miot.py#L903

Added line #L903 was not covered by tests
"Unsupported UV for model '%s'" % self.model
)

return self.set_property("uv", uv)

Check warning on line 907 in miio/integrations/zhimi/airpurifier/airpurifier_miot.py

View check run for this annotation

Codecov / codecov/patch

miio/integrations/zhimi/airpurifier/airpurifier_miot.py#L907

Added line #L907 was not covered by tests
Loading