I think you’d better delete a ‘#’ in line 163
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) homeassistant@Tommy:/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\x9c@V\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) homeassistant@Tommy:/home/pi $
(homeassistant) homeassistant@Tommy:/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) homeassistant@Tommy:/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
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
pi@Tommy:~ $ sudo su -s /bin/bash homeassistant
homeassistant@Tommy:/home/pi $ source /srv/homeassistant/bin/activate
(homeassistant) homeassistant@Tommy:/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) homeassistant@Tommy:/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) homeassistant@Tommy:/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
Your screenshots are showing some missing features:
- Target humidity
- Sounds (Volume?)
- Parental controls
We can extend the component if you provide some further testing.
I miscommented for LED level.
2 is off, 1 is Dim light and 0 is Bright light.(These are corrent)
What test do I have to do for extend component?
Please let me know it and I will follow it up~
Please provide the feedback of:
# target humidity?
mirobo --ip 192.168.0.25 --token 689c4056fe28ebb3a2e8c2fe350e51ba raw_command get_prop '["limit_hum"]'
# parental controls?
mirobo --ip 192.168.0.25 --token 689c4056fe28ebb3a2e8c2fe350e51ba raw_command get_prop '["child_lock"]'
# sounds?
mirobo --ip 192.168.0.25 --token 689c4056fe28ebb3a2e8c2fe350e51ba raw_command get_prop '["volume"]'
pi@Tommy:~ $ sudo su -s /bin/bash homeassistant
homeassistant@Tommy:/home/pi $ source /srv/homeassistant/bin/activate
(homeassistant) homeassistant@Tommy:/home/pi $ # target humidity?
mirobo --ip 192.168.0.25 --token 689c4056fe28ebb3a2e8c2fe350e51ba raw_command get_prop ‘[“limit_hum”]’
parental controls?
mirobo --ip 192.168.0.25 --token 689c4056fe28ebb3a2e8c2fe350e51ba raw_command get_prop ‘[“child_lock”]’
sounds?
(homeassistant) homeassistant@Tommy:/home/pi $ mirobo --ip 192.168.0.25 --token 689c4056fe28ebb3a2e8c2fe350e51ba raw_command get_prop ‘[“limit_hum”]’
Sending cmd get_prop with params [‘limit_hum’]
[40]
(homeassistant) homeassistant@Tommy:/home/pi $ # parental controls?
(homeassistant) homeassistant@Tommy:/home/pi $ mirobo --ip 192.168.0.25 --token 689c4056fe28ebb3a2e8c2fe350e51ba raw_command get_prop ‘[“child_lock”]’
Sending cmd get_prop with params [‘child_lock’]
[‘off’]
(homeassistant) homeassistant@Tommy:/home/pi $ # sounds?
(homeassistant) homeassistant@Tommy:/home/pi $ mirobo --ip 192.168.0.25 --token 689c4056fe28ebb3a2e8c2fe350e51ba raw_command get_prop ‘[“volume”]’
Sending cmd get_prop with params [‘volume’]
[None]
(homeassistant) homeassistant@Tommy:/home/pi $
if you need more, pls let me konw it.