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

WIP: Add basic Xiaomi Air Condition support #439

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions miio/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# flake8: noqa
from miio.aircondition import AirCondition
from miio.airconditioningcompanion import AirConditioningCompanion
from miio.airconditioningcompanion import AirConditioningCompanionV3
from miio.airfresh import AirFresh
Expand Down
177 changes: 177 additions & 0 deletions miio/aircondition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import enum
import logging
from collections import defaultdict
from typing import Optional

from .click_common import command, format_output
from .device import Device, DeviceException

_LOGGER = logging.getLogger(__name__)

MODEL_AIRCONDITION_MA1 = 'zhimi.aircondition.ma1'

MODELS_SUPPORTED = [MODEL_AIRCONDITION_MA1]


class AirConditionException(DeviceException):
pass


class OperationMode(enum.Enum):
Arefaction = 'arefaction'
Cool = 'cooling'
Heat = 'heat'
Wind = 'wind'


class AirConditionStatus:
"""Container for status reports of the Xiaomi Air Condition."""

def __init__(self, data):
"""
Device model: zhimi.aircondition.ma1

{'mode': 'cooling',
'lcd_auto': 'off',
'lcd_level': 1,
'volume': 'off',
'idle_timer': 0,
'open_timer': 0,
'power': 'on',
'temp_dec': 244,
'st_temp_dec': 320,
'humidity': null,
'speed_level': 5,
'vertical_swing': 'on',
'ptc': 'off',
'ptc_rt': 'off',
'silent': 'off',
'vertical_end': 60,
'vertical_rt': 19,
'ele_quantity': null,
'ex_humidity': null,
'remote_mac': null,
'htsensor_mac': null,
'speed_level': 5,
'vertical_swing': 'on',
'ht_sensor': null,
'comfort': 'off',
'ot_run_temp': 7,
'ep_temp': 27,
'es_temp': 13,
'he_temp': 39,
'ot_humidity': null,
'compressor_frq': 0,
'motor_speed': 1000}

"""
self.data = data

@property
def power(self) -> str:
"""Current power state."""
return self.data['power']

@property
def is_on(self) -> bool:
"""True if the device is turned on."""
return self.power == 'on'

@property
def temperature(self) -> int:
"""Current temperature."""
return self.data['temp_dec'] / 10.0

@property
def target_temperature(self) -> int:
"""Target temperature."""
return self.data['st_temp_dec'] / 10.0

@property
def mode(self) -> Optional[OperationMode]:
"""Current operation mode."""
try:
return OperationMode(self.data['mode'])
except TypeError:
return None

def __repr__(self) -> str:
s = "<AirConditionStatus " \
"power=%s, " \
"temperature=%s, " \
"target_temperature=%s, " \
"mode=%s>" % \
(self.power,
self.temperature,
self.target_temperature,
self.mode)
return s

def __json__(self):
return self.data


class AirCondition(Device):
"""Main class representing Xiaomi Air Condition MA1."""

def __init__(self, ip: str = None, token: str = None, start_id: int = 0,
debug: int = 0, lazy_discover: bool = True,
model: str = MODEL_AIRCONDITION_MA1) -> None:
super().__init__(ip, token, start_id, debug, lazy_discover)

if model in MODELS_SUPPORTED:
self.model = model
else:
self.model = MODEL_AIRCONDITION_MA1
_LOGGER.error("Device model %s unsupported. Falling back to %s.", model, self.model)

@command(
default_output=format_output(
"",
"Power: {result.power}\n"
"Temperature: {result.temperature} °C\n"
"Target temperature: {result.target_temperature} °C\n"
"Mode: {result.mode}\n"
)
)
def status(self) -> AirConditionStatus:
"""Retrieve properties."""

properties = ['mode', 'lcd_auto', 'lcd_level', 'volume', 'idle_timer', 'open_timer', 'power', 'temp_dec',

Choose a reason for hiding this comment

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

line too long (113 > 100 characters)

'st_temp_dec', 'speed_level', 'vertical_swing', 'ptc', 'ptc_rt', 'silent', 'vertical_end',

Choose a reason for hiding this comment

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

line too long (112 > 100 characters)

'vertical_rt', 'speed_level', 'vertical_swing', 'comfort', 'ot_run_temp', 'ep_temp', 'es_temp',

Choose a reason for hiding this comment

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

line too long (117 > 100 characters)

'he_temp', 'compressor_frq', 'motor_speed', 'humidity', 'ele_quantity', 'ex_humidity',

Choose a reason for hiding this comment

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

line too long (108 > 100 characters)

'remote_mac', 'htsensor_mac', 'ht_sensor', 'ot_humidity']

# A single request is limited to 16 properties. Therefore the
# properties are divided into multiple requests
_props = properties.copy()
values = []
while _props:
values.extend(self.send("get_prop", _props[:15]))
_props[:] = _props[15:]

properties_count = len(properties)
values_count = len(values)
if properties_count != values_count:
_LOGGER.debug(
"Count (%s) of requested properties does not match the "
"count (%s) of received values.",
properties_count, values_count)

return AirConditionStatus(
defaultdict(lambda: None, zip(properties, values)))

@command(
default_output=format_output("Powering the air condition on"),
)
def on(self):
"""Turn the air condition on."""
return self.send("set_power", ["on"])

@command(
default_output=format_output("Powering the air condition off"),
)
def off(self):
"""Turn the air condition off."""
return self.send("set_power", ["off"])
4 changes: 3 additions & 1 deletion miio/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from . import (Device, Vacuum, ChuangmiPlug, PowerStrip, AirPurifier, AirFresh, Ceil,
PhilipsBulb, PhilipsEyecare, PhilipsMoonlight, ChuangmiIr,
AirHumidifier, WaterPurifier, WifiSpeaker, WifiRepeater,
Yeelight, Fan, Cooker, AirConditioningCompanion, AirQualityMonitor, AqaraCamera)
Yeelight, Fan, Cooker, AirCondition, AirConditioningCompanion,
AirQualityMonitor, AqaraCamera, )

from .airconditioningcompanion import (MODEL_ACPARTNER_V1, MODEL_ACPARTNER_V2, MODEL_ACPARTNER_V3, )
from .airhumidifier import (MODEL_HUMIDIFIER_CA1, MODEL_HUMIDIFIER_V1, )
Expand Down Expand Up @@ -75,6 +76,7 @@
"zhimi-fan-v3": partial(Fan, model=MODEL_FAN_V3),
"zhimi-fan-sa1": partial(Fan, model=MODEL_FAN_SA1),
"zhimi-fan-za1": partial(Fan, model=MODEL_FAN_ZA1),
"zhimi-aircondition-ma1": AirCondition,
"zhimi-airfresh-va2": AirFresh,
"zhimi-airmonitor-v1": AirQualityMonitor,
"lumi-gateway-": lambda x: other_package_info(
Expand Down