Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 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
17 changes: 17 additions & 0 deletions homeassistant/components/xiaomi_miio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
MODEL_FAN_P11,
MODEL_FAN_P18,
MODEL_FAN_ZA5,
MODEL_PET_FOUNTAIN_70M2,
MODELS_AIR_MONITOR,
MODELS_FAN,
MODELS_FAN_MIIO,
Expand All @@ -65,6 +66,7 @@
MODELS_HUMIDIFIER_MIOT,
MODELS_HUMIDIFIER_MJJSQ,
MODELS_LIGHT,
MODELS_PET_FOUNTAIN,
MODELS_PURIFIER_MIOT,
MODELS_SWITCH,
MODELS_VACUUM,
Expand All @@ -75,6 +77,7 @@
)
from .coordinator import UPDATE_INTERVAL, GatewayDeviceCoordinator
from .gateway import ConnectXiaomiGateway
from .pet_fountain_miot import XiaomiPetFountain
from .services import async_setup_services
from .typing import XiaomiMiioConfigEntry, XiaomiMiioRuntimeData

Expand Down Expand Up @@ -115,6 +118,15 @@
Platform.VACUUM,
]
AIR_MONITOR_PLATFORMS = [Platform.AIR_QUALITY, Platform.SENSOR]
PET_FOUNTAIN_PLATFORMS = [
Platform.SENSOR,
Platform.BINARY_SENSOR,
Platform.SELECT,
Platform.NUMBER,
Platform.SWITCH,
Platform.TIME,
Platform.BUTTON,
]

MODEL_TO_CLASS_MAP = {
MODEL_FAN_1C: Fan1C,
Expand Down Expand Up @@ -156,6 +168,8 @@ def get_platforms(config_entry):
if flow_type == CONF_DEVICE:
if model in MODELS_SWITCH:
return SWITCH_PLATFORMS
if model in MODELS_PET_FOUNTAIN:
return PET_FOUNTAIN_PLATFORMS
if model in MODELS_HUMIDIFIER:
return HUMIDIFIER_PLATFORMS
if model in MODELS_FAN:
Expand Down Expand Up @@ -320,6 +334,7 @@ async def async_create_miio_device_and_coordinator(

if (
model not in MODELS_HUMIDIFIER
and model not in MODELS_PET_FOUNTAIN
and model not in MODELS_FAN
and model not in MODELS_VACUUM
and not model.startswith(ROBOROCK_GENERIC)
Expand All @@ -334,6 +349,8 @@ async def async_create_miio_device_and_coordinator(
if model in MODELS_HUMIDIFIER_MIOT:
device = AirHumidifierMiot(host, token, lazy_discover=lazy_discover)
migrate = True
elif model == MODEL_PET_FOUNTAIN_70M2:
device = XiaomiPetFountain(host, token, lazy_discover=lazy_discover)
elif model in MODELS_HUMIDIFIER_MJJSQ:
device = AirHumidifierMjjsq(
host, token, lazy_discover=lazy_discover, model=model
Expand Down
53 changes: 52 additions & 1 deletion homeassistant/components/xiaomi_miio/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
MODEL_AIRFRESH_A1,
MODEL_AIRFRESH_T2017,
MODEL_FAN_ZA5,
MODEL_PET_FOUNTAIN_70M2,
MODELS_HUMIDIFIER_MIIO,
MODELS_HUMIDIFIER_MIOT,
MODELS_HUMIDIFIER_MJJSQ,
Expand All @@ -44,6 +45,10 @@
ATTR_MOP_ATTACHED = "is_water_box_carriage_attached"
ATTR_WATER_BOX_ATTACHED = "is_water_box_attached"
ATTR_WATER_SHORTAGE = "is_water_shortage"
ATTR_LOW_BATTERY = "low_battery"
ATTR_USB_POWER = "usb_power"
ATTR_PUMP_BLOCKED = "pump_blocked"
ATTR_FAULT = "has_fault"


@dataclass(frozen=True)
Expand Down Expand Up @@ -132,6 +137,39 @@ class XiaomiMiioBinarySensorDescription(BinarySensorEntityDescription):
HUMIDIFIER_MIIO_BINARY_SENSORS = (ATTR_WATER_TANK_DETACHED,)
HUMIDIFIER_MIOT_BINARY_SENSORS = (ATTR_WATER_TANK_DETACHED,)
HUMIDIFIER_MJJSQ_BINARY_SENSORS = (ATTR_NO_WATER, ATTR_WATER_TANK_DETACHED)
PET_FOUNTAIN_BINARY_SENSORS = (
XiaomiMiioBinarySensorDescription(
key="water_shortage",
translation_key="water_shortage",
icon="mdi:water-alert",
device_class=BinarySensorDeviceClass.PROBLEM,
),
XiaomiMiioBinarySensorDescription(
key=ATTR_PUMP_BLOCKED,
translation_key=ATTR_PUMP_BLOCKED,
icon="mdi:pump-off",
device_class=BinarySensorDeviceClass.PROBLEM,
),
XiaomiMiioBinarySensorDescription(
key=ATTR_LOW_BATTERY,
translation_key=ATTR_LOW_BATTERY,
device_class=BinarySensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
),
XiaomiMiioBinarySensorDescription(
key=ATTR_USB_POWER,
translation_key=ATTR_USB_POWER,
device_class=BinarySensorDeviceClass.PLUG,
entity_category=EntityCategory.DIAGNOSTIC,
),
XiaomiMiioBinarySensorDescription(
key=ATTR_FAULT,
translation_key=ATTR_FAULT,
icon="mdi:alert-circle",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_registry_enabled_default=False,
),
)


def _setup_vacuum_sensors(
Expand Down Expand Up @@ -181,7 +219,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Xiaomi sensor from a config entry."""
entities = []
entities: list[BinarySensorEntity] = []

if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE:
model = config_entry.data[CONF_MODEL]
Expand All @@ -196,6 +234,19 @@ async def async_setup_entry(
sensors = HUMIDIFIER_MIOT_BINARY_SENSORS
elif model in MODELS_HUMIDIFIER_MJJSQ:
sensors = HUMIDIFIER_MJJSQ_BINARY_SENSORS
elif model == MODEL_PET_FOUNTAIN_70M2:
entities.extend(
XiaomiGenericBinarySensor(
config_entry.runtime_data.device,
config_entry,
f"{description.key}_{config_entry.unique_id}",
config_entry.runtime_data.device_coordinator,
description,
)
for description in PET_FOUNTAIN_BINARY_SENSORS
)
async_add_entities(entities)
return
elif model in MODELS_VACUUM:
_setup_vacuum_sensors(hass, config_entry, async_add_entities)
return
Expand Down
25 changes: 17 additions & 8 deletions homeassistant/components/xiaomi_miio/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@
from miio import Device as MiioDevice
from miio.integrations.vacuum.roborock.vacuum import Consumable

from homeassistant.components.button import (
ButtonDeviceClass,
ButtonEntity,
ButtonEntityDescription,
)
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.const import CONF_MODEL, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import MODEL_AIRFRESH_A1, MODEL_AIRFRESH_T2017, MODELS_VACUUM
from .const import (
MODEL_AIRFRESH_A1,
MODEL_AIRFRESH_T2017,
MODEL_PET_FOUNTAIN_70M2,
MODELS_VACUUM,
)
from .entity import XiaomiCoordinatedMiioEntity
from .typing import XiaomiMiioConfigEntry

Expand All @@ -32,6 +33,7 @@
ATTR_RESET_VACUUM_SIDE_BRUSH = "reset_vacuum_side_brush"
ATTR_RESET_VACUUM_FILTER = "reset_vacuum_filter"
ATTR_RESET_VACUUM_SENSOR_DIRTY = "reset_vacuum_sensor_dirty"
ATTR_RESET_FILTER_LIFE = "reset_filter_life"


@dataclass(frozen=True)
Expand Down Expand Up @@ -61,6 +63,14 @@ class XiaomiMiioButtonDescription(ButtonEntityDescription):
method_press_error_message="Resetting the upper filter lifetime failed.",
entity_category=EntityCategory.CONFIG,
),
XiaomiMiioButtonDescription(
key=ATTR_RESET_FILTER_LIFE,
translation_key=ATTR_RESET_FILTER_LIFE,
icon="mdi:air-filter",
method_press="reset_filter_life",
method_press_error_message="Resetting the filter lifetime failed.",
entity_category=EntityCategory.CONFIG,
),
# Vacuums
XiaomiMiioButtonDescription(
key=ATTR_RESET_VACUUM_MAIN_BRUSH,
Expand Down Expand Up @@ -113,6 +123,7 @@ class XiaomiMiioButtonDescription(ButtonEntityDescription):
ATTR_RESET_DUST_FILTER,
ATTR_RESET_UPPER_FILTER,
),
MODEL_PET_FOUNTAIN_70M2: (ATTR_RESET_FILTER_LIFE,),
**dict.fromkeys(MODELS_VACUUM, BUTTONS_FOR_VACUUM),
}

Expand Down Expand Up @@ -158,8 +169,6 @@ class XiaomiGenericCoordinatedButton(

entity_description: XiaomiMiioButtonDescription

_attr_device_class = ButtonDeviceClass.RESTART

def __init__(
Comment on lines 165 to 172

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of _attr_device_class = ButtonDeviceClass.RESTART appears unintended and unrelated to the pet fountain changes. This removal should be reverted to maintain the device class designation for buttons, which affects how they're displayed and handled in the UI.

Copilot uses AI. Check for mistakes.
self,
device: MiioDevice,
Expand Down
7 changes: 6 additions & 1 deletion homeassistant/components/xiaomi_miio/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
CONF_MANUAL,
DEFAULT_CLOUD_COUNTRY,
DOMAIN,
MODEL_PET_FOUNTAIN_70M2,
MODELS_ALL,
MODELS_ALL_DEVICES,
MODELS_GATEWAY,
Expand All @@ -43,6 +44,10 @@

_LOGGER = logging.getLogger(__name__)

MODEL_TO_DEFAULT_NAME = {
MODEL_PET_FOUNTAIN_70M2: "Xiaomi Smart Pet Fountain 2",
}

DEVICE_SETTINGS = {
vol.Required(CONF_TOKEN): vol.All(str, vol.Length(min=32, max=32)),
}
Expand Down Expand Up @@ -407,7 +412,7 @@ async def async_step_connect(
return self.async_update_reload_and_abort(existing_entry, data=data)

if self.name is None:
self.name = self.model
self.name = MODEL_TO_DEFAULT_NAME.get(self.model, self.model)

flow_type = None
for gateway_model in MODELS_GATEWAY:
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/xiaomi_miio/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class SetupException(Exception):
MODEL_AIRHUMIDIFIER_JSQ = "deerma.humidifier.jsq"
MODEL_AIRHUMIDIFIER_JSQ1 = "deerma.humidifier.jsq1"
MODEL_AIRHUMIDIFIER_MJJSQ = "deerma.humidifier.mjjsq"
MODEL_PET_FOUNTAIN_70M2 = "xiaomi.pet_waterer.70m2"

MODEL_AIRFRESH_A1 = "dmaker.airfresh.a1"
MODEL_AIRFRESH_VA2 = "zhimi.airfresh.va2"
Expand Down Expand Up @@ -161,6 +162,7 @@ class SetupException(Exception):
MODEL_AIRHUMIDIFIER_JSQ1,
MODEL_AIRHUMIDIFIER_MJJSQ,
]
MODELS_PET_FOUNTAIN = [MODEL_PET_FOUNTAIN_70M2]

# AirQuality Models
MODEL_AIRQUALITYMONITOR_V1 = "zhimi.airmonitor.v1"
Expand Down Expand Up @@ -263,6 +265,7 @@ class SetupException(Exception):
+ MODELS_AIR_MONITOR
+ MODELS_FAN
+ MODELS_HUMIDIFIER
+ MODELS_PET_FOUNTAIN
+ MODELS_LIGHT
)
MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY
Expand Down
48 changes: 48 additions & 0 deletions homeassistant/components/xiaomi_miio/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
DOMAIN as PLATFORM_DOMAIN,
NumberEntity,
NumberEntityDescription,
NumberMode,
)
from homeassistant.const import (
CONF_DEVICE,
Expand All @@ -22,6 +23,7 @@
UnitOfTime,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
Expand Down Expand Up @@ -93,6 +95,7 @@
MODEL_FAN_ZA3,
MODEL_FAN_ZA4,
MODEL_FAN_ZA5,
MODEL_PET_FOUNTAIN_70M2,
MODELS_PURIFIER_MIIO,
MODELS_PURIFIER_MIOT,
)
Expand All @@ -108,6 +111,7 @@
ATTR_MOTOR_SPEED = "motor_speed"
ATTR_OSCILLATION_ANGLE = "angle"
ATTR_VOLUME = "volume"
ATTR_WATER_INTERVAL = "water_interval"


@dataclass(frozen=True, kw_only=True)
Expand Down Expand Up @@ -235,6 +239,21 @@ class FavoriteLevelValues:
),
}

PET_FOUNTAIN_NUMBER_TYPES = (
XiaomiMiioNumberDescription(
key=ATTR_WATER_INTERVAL,
translation_key=ATTR_WATER_INTERVAL,
icon="mdi:timer-cog-outline",
mode=NumberMode.BOX,
native_unit_of_measurement=UnitOfTime.MINUTES,
native_min_value=10,
native_max_value=120,
native_step=5,
method="async_set_water_interval",
entity_category=EntityCategory.CONFIG,
),
)

MODEL_TO_FEATURES_MAP = {
MODEL_AIRFRESH_A1: FEATURE_FLAGS_AIRFRESH_A1,
MODEL_AIRFRESH_VA2: FEATURE_FLAGS_AIRFRESH,
Expand Down Expand Up @@ -304,6 +323,8 @@ async def async_setup_entry(
features = FEATURE_FLAGS_AIRPURIFIER_MIIO
elif model in MODELS_PURIFIER_MIOT:
features = FEATURE_FLAGS_AIRPURIFIER_MIOT
elif model == MODEL_PET_FOUNTAIN_70M2:
features = 0
else:
return

Expand Down Expand Up @@ -348,6 +369,17 @@ async def async_setup_entry(
)
)

if model == MODEL_PET_FOUNTAIN_70M2:
entities.extend(
XiaomiNumberEntity(
device,
config_entry,
f"{description.key}_{config_entry.unique_id}",
coordinator,
description,
)
for description in PET_FOUNTAIN_NUMBER_TYPES
)
async_add_entities(entities)


Expand Down Expand Up @@ -472,3 +504,19 @@ async def async_set_favorite_rpm(self, rpm: int) -> bool:
self._device.set_favorite_rpm, # type: ignore[attr-defined]
rpm,
)

async def async_set_water_interval(self, minutes: int) -> bool:
"""Set the water interval for interval mode."""
if not 10 <= minutes <= 120:
raise ServiceValidationError(
"Water interval must be between 10 and 120 minutes"
)
if minutes % 5 != 0:
raise ServiceValidationError(
"Water interval must be set in 5 minute increments"
)
return await self._try_command(
"Setting the water interval of the miio device failed.",
self._device.set_water_interval, # type: ignore[attr-defined]
minutes,
)
Loading
Loading