Xiaomi Humidifier support

Currently only Xiaomi AirPurifier is supported, but Xiaomi Humidifier is also supported by miio. Looks like adding support for Humidifier is not something needed to be done from scratch.
I tried to customise xiaomi_miio platform, but not much luck for now :slight_smile: Would be great to have it supported.

miio docs: https://github.com/aholstenson/miio/blob/master/docs/devices/humidifier.md

The linked miio is different one from one that is used in homeassistant, however, the support for those is also available (https://github.com/rytilahti/python-miio/blob/master/miio/airhumidifier.py), so it would “just” require someone to write a platform to support this.

Ok, got it working with slight modification of AirPurifier platform.
55
09
Here is the link to two source files:

I think you’d better delete a ‘#’ in line 163 :slight_smile:

I would love to use it. But when I am trying to test it, I am getting CheckSum error:

File “/usr/lib/python3.6/site-packages/construct/core.py”, line 2272, in _parse
raise ChecksumError(“wrong checksum, read %r, computed %r” % (hexlify(hash1), hexlify(hash2)))
construct.core.ChecksumError: wrong checksum, read b’ffffffffffffffffffffffffffffffff’, computed b’86f9dfc783dd2b635f17750e28fc898c’

Am I doing something wrong?

And maybe I should note that I am also using Xiaomi Air Purifier 2 bulitin integration.

Thanks, fixed now.

Sorry, no idea… Looks to me like this error is unrelated to the custom platform code.

I recommend you use python3.5.
If you use HASSBIAN, it works well.

Thanks @bezkod for the initial work. I didn’t know your custom_component and did the same like you as proof of concept:

If the component works fine I will merge the code into fan/xiaomi_miio soon in order to support the Xiaomi Air Humidifier officially in future.

@bezkod Our code differs in one feature: Is the LED of the Air Humidifier controllable? Did you remove set_led_on and set_led_off on purpose? Could someone (@af950833, @itchaboy) test all exposed services of the component? Thanks in advance!

Another request: Could someone provide the output of following mirobo (cli, part of python-miio) calls for the air humidifier:

# ip and token needs to be adapted

bin/mirobo --ip 192.168.1.71 --token 3d5f7ae53b51aa312e464b150b37453b -d info

bin/mirobo --ip 192.168.1.71 --token 3d5f7ae53b51aa312e464b150b37453b get_prop '["power", "mode", "temp_dec", "humidity", "buzzer","led_b", "led"]'

how do I go about adding a custom component? Would I add the following folder to my config folder? custom_components/fan/custom_component.py

(homeassistant) [email protected]:/home/pi $ mirobo --ip 192.168.0.25 --token 689c4056fe28ebb3a2e8c2fe350e51ba -d info
INFO:miio.vacuum_cli:Debug mode active
DEBUG:miio.vacuum_cli:Read stored sequence ids: {‘seq’: 2, ‘manual_seq’: 0}
DEBUG:miio.vacuum_cli:Connecting to 192.168.0.25 with token 689c4056fe28ebb3a2e8c2fe350e51ba
DEBUG:miio.protocol:Unable to decrypt, returning raw bytes: b’‘
DEBUG:miio.device:Got a response: Container:
data = Container:
length = 0
value = (total 0)
offset2 = 32
offset1 = 32
data = (total 0)
header = Container:
length = 16
value = Container:
length = 32
unknown = 0
device_id = 047c3fd9 (total 8)
ts = 1970-01-07 15:54:21
offset2 = 16
offset1 = 0
data = !1\x00 \x00\x00\x00\x00\x04|?\xd9\x00\x08\xc8\xad (total 16)
checksum = h\[email protected]\xfe(\xeb\xb3\xa2\xe8\xc2\xfe5\x0eQ\xba (total 16)
DEBUG:miio.device:Discovered b’047c3fd9’ with ts: 1970-01-07 15:54:21, token: b’689c4056fe28ebb3a2e8c2fe350e51ba’
DEBUG:miio.device:192.168.0.25:54321 >>: {‘method’: ‘miIO.info’, ‘params’: [], ‘id’: 3}
DEBUG:miio.device:192.168.0.25:54321 (ts: 1970-01-07 15:54:21, id: 3) << {‘id’: 3, ‘result’: {‘fw_ver’: ‘1.2.9_5033’, ‘token’: ‘689c4056fe28ebb3a2e8c2fe350e51ba’, ‘otu_stat’: [101, 74, 5343, 0, 5327, 407], ‘mmfree’: 228248, ‘netif’: {‘gw’: ‘192.168.0.1’, ‘localIp’: ‘192.168.0.25’, ‘mask’: ‘255.255.255.0’}, ‘ott_stat’: [0, 0, 0, 0], ‘model’: ‘zhimi.humidifier.v1’, ‘cfg_time’: 0, ‘life’: 575661, ‘ap’: {‘rssi’: -35, ‘ssid’: ‘Tommy’, ‘bssid’: ‘AC:9E:17:A1:DF:F8’}, ‘wifi_fw_ver’: ‘SD878x-14.76.36.p84-702.1.0-WM’, ‘hw_ver’: ‘MW300’, ‘ot’: ‘otu’, ‘mac’: ‘78:11:DC:8E:00:E7’}}
zhimi.humidifier.v1 v1.2.9_5033 (78:11:DC:8E:00:E7) @ 192.168.0.25 - token: 689c4056fe28ebb3a2e8c2fe350e51ba
DEBUG:miio.vacuum_cli:Full response: {‘ap’: {‘bssid’: ‘AC:9E:17:A1:DF:F8’, ‘rssi’: -35, ‘ssid’: ‘Tommy’},
‘cfg_time’: 0,
‘fw_ver’: ‘1.2.9_5033’,
‘hw_ver’: ‘MW300’,
‘life’: 575661,
‘mac’: ‘78:11:DC:8E:00:E7’,
‘mmfree’: 228248,
‘model’: ‘zhimi.humidifier.v1’,
‘netif’: {‘gw’: ‘192.168.0.1’,
‘localIp’: ‘192.168.0.25’,
‘mask’: ‘255.255.255.0’},
‘ot’: ‘otu’,
‘ott_stat’: [0, 0, 0, 0],
‘otu_stat’: [101, 74, 5343, 0, 5327, 407],
‘token’: ‘689c4056fe28ebb3a2e8c2fe350e51ba’,
‘wifi_fw_ver’: ‘SD878x-14.76.36.p84-702.1.0-WM’}
DEBUG:miio.vacuum_cli:Writing {‘seq’: 3, ‘manual_seq’: 0} to /tmp/python-mirobo.seq
(homeassistant) [email protected]:/home/pi $
(homeassistant) [email protected]:/home/pi $ mirobo --ip 192.168.0.25 --token 689c4056fe28ebb3a2e8c2fe350e51ba raw_command get_prop '[“power”, “mode”, “temp_dec”, “humidity”, “buzzer”,“led_b”, “led”]'
Sending cmd get_prop with params [‘power’, ‘mode’, ‘temp_dec’, ‘humidity’, ‘buzzer’, ‘led_b’, ‘led’]
[‘off’, ‘high’, 294, 33, ‘on’, 0, None]
(homeassistant) [email protected]:/home/pi $

“”"
Support for Xiaomi Mi Air Humidifier

“”"
import asyncio
from functools import partial
import logging
import os

import voluptuous as vol

from homeassistant.helpers.entity import ToggleEntity
from homeassistant.components.fan import (FanEntity, PLATFORM_SCHEMA,
SUPPORT_SET_SPEED, DOMAIN)
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_TOKEN,
ATTR_ENTITY_ID, )
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(name)

DEFAULT_NAME = 'Xiaomi Air Humidifier’
PLATFORM = ‘xiaomi_airhumidifier’

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_TOKEN): vol.All(cv.string, vol.Length(min=32, max=32)),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})

REQUIREMENTS = [‘python-miio==0.3.3’]

ATTR_TEMPERATURE = 'temperature’
ATTR_HUMIDITY = 'humidity’
ATTR_MODE = ‘mode’

SUCCESS = [‘ok’]

pylint: disable=unused-argument

@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
""“Set up the air humidifier from config.”""
from miio import AirHumidifier, DeviceException
if PLATFORM not in hass.data:
hass.data[PLATFORM] = {}

host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
token = config.get(CONF_TOKEN)

_LOGGER.info("Initializing with host %s (token %s...)", host, token[:5])

try:
    air_humidifier = AirHumidifier(host, token)

    xiaomi_air_humidifier = XiaomiAirHumidifier(name, air_humidifier)
    hass.data[PLATFORM][host] = xiaomi_air_humidifier
except DeviceException:
    raise PlatformNotReady

async_add_devices([xiaomi_air_humidifier], update_before_add=True)

class XiaomiAirHumidifier(FanEntity):
""“Representation of a Xiaomi Air Humidifier.”""

def __init__(self, name, air_humidifier):
    """Initialize the air humidifier."""
    self._name = name

    self._air_humidifier = air_humidifier
    self._state = None
    self._state_attrs = {
        ATTR_TEMPERATURE: None,
        ATTR_HUMIDITY: None,
        ATTR_MODE: None,
    }

@property
def supported_features(self):
    """Flag supported features."""
    return SUPPORT_SET_SPEED

@property
def should_poll(self):
    """Poll the fan."""
    return True

@property
def name(self):
    """Return the name of the device if any."""
    return self._name

@property
def available(self):
    """Return true when state is known."""
    return self._state is not None

@property
def device_state_attributes(self):
    """Return the state attributes of the device."""
    return self._state_attrs

@property
def is_on(self):
    """Return true if fan is on."""
    return self._state

@asyncio.coroutine
def _try_command(self, mask_error, func, *args, **kwargs):
    """Call a air humidifier command handling error messages."""
    from miio import DeviceException
    try:
        result = yield from self.hass.async_add_job(
            partial(func, *args, **kwargs))

        _LOGGER.debug("Response received from air humidifier: %s", result)

        return result == SUCCESS
    except DeviceException as exc:
        _LOGGER.error(mask_error, exc)
        return False

@asyncio.coroutine
def async_turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None:
    """Turn the fan on."""
    if speed:
        # If operation mode was set the device must not be turned on.
        yield from self.async_set_speed(speed)
        return

    yield from self._try_command(
        "Turning the air humidifier on failed.", self._air_humidifier.on)

@asyncio.coroutine
def async_turn_off(self: ToggleEntity, **kwargs) -> None:
    """Turn the fan off."""
    yield from self._try_command(
        "Turning the air humidifier off failed.", self._air_humidifier.off)

@asyncio.coroutine
def async_update(self):
    """Fetch state from the device."""
    from miio import DeviceException

    try:
        state = yield from self.hass.async_add_job(
            self._air_humidifier.status)
        _LOGGER.debug("Got new state: %s", state)

        self._state = state.is_on
        self._state_attrs = {
            ATTR_TEMPERATURE: state.temperature,
            ATTR_HUMIDITY: state.humidity,
            ATTR_MODE: state.mode.value
        }

    except DeviceException as ex:
        _LOGGER.error("Got exception while fetching the state: %s", ex)

@property
def speed_list(self: ToggleEntity) -> list:
    """Get the list of available speeds."""
    from miio.airhumidifier import OperationMode
    return [mode.name for mode in OperationMode]

@property
def speed(self):
    """Return the current speed."""
    if self._state:
        from miio.airhumidifier import OperationMode

        return OperationMode(self._state_attrs[ATTR_MODE]).name

    return None

@asyncio.coroutine
def async_set_speed(self: ToggleEntity, speed: str) -> None:
    """Set the speed of the fan."""
    _LOGGER.debug("Setting the operation mode to: " + speed)
    from miio.airhumidifier import OperationMode

    yield from self._try_command(
        "Setting operation mode of the air humidifier failed.",
        self._air_humidifier.set_mode, OperationMode[speed])

from HA 0.61.0 the servicess are not working, so I removed all service in the code and using upper code as custom_components.
Also I wanna let you know that there is no LED Brigtness in Air Purifier Pro. I am also using it after removing the service"LED Brightness" in HA 0.61.1

It looks like there is not attribute “led_b” or “led” for the Air Humidifier. Could you execute this two commands for testing:

mirobo --ip 192.168.0.25 --token 689c4056fe28ebb3a2e8c2fe350e51ba raw_command get_prop '["power", "mode", "temp_dec", "humidity", "buzzer","led"]'
mirobo --ip 192.168.0.25 --token 689c4056fe28ebb3a2e8c2fe350e51ba raw_command get_prop '["power", "mode", "temp_dec", "humidity", "buzzer","led_b"]'

One of the outputs will contain a “null” value.

Just copy the file

https://raw.githubusercontent.com/syssi/xiaomi_airhumidifier/master/custom_components/fan/xiaomi_airhumidifier.py

to custom_components/fan/xiaomi_airhumidifier.py and add the configuration to your

# configurations.yaml

fan:
  - platform: xiaomi_airhumidifier
    name: Xiaomi Air Humidifier
    host: 192.168.130.71
    token: b7c4a758c251955d2c24b1d9e41ce47d

[email protected]:~ $ sudo su -s /bin/bash homeassistant
[email protected]:/home/pi $ source /srv/homeassistant/bin/activate
(homeassistant) [email protected]:/home/pi $ mirobo --ip 192.168.0.25 --token 689c4056fe28ebb3a2e8c2fe350e51ba raw_command get_prop ‘[“power”, “mode”, “temp_dec”, “humidity”, “buzzer”,“led”]’
Sending cmd get_prop with params [‘power’, ‘mode’, ‘temp_dec’, ‘humidity’, ‘buzzer’, ‘led’]
[‘off’, ‘high’, 268, 37, ‘on’, None]
(homeassistant) [email protected]:/home/pi $ mirobo --ip 192.168.0.25 --token 689c4056fe28ebb3a2e8c2fe350e51ba raw_command get_prop ‘[“power”, “mode”, “temp_dec”, “humidity”, “buzzer”,“led_b”]’
Sending cmd get_prop with params [‘power’, ‘mode’, ‘temp_dec’, ‘humidity’, ‘buzzer’, ‘led_b’]
[‘off’, ‘high’, 268, 37, ‘on’, 0]
(homeassistant) [email protected]:/home/pi $

In the Xiaomi App’s Air Humidifier, there is no LED on/off but there is the LED brightness.
In the LED brightness, 2 is off, 1 is Dim light and 0 is Bright light.
I used it as a sevice in HA 0.60.1 and it worked well.

Perfect! I updated the component.

I always thank you :slight_smile:

Your screenshots are showing some missing features:

  1. Target humidity
  2. Sounds (Volume?)
  3. Parental controls

We can extend the component if you provide some further testing. :slight_smile: