Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
ad353a3
Add Xiaomi miio Alarm Control Panel
starkillerOG Feb 22, 2020
913b052
black formatting
starkillerOG Feb 23, 2020
3e5d32f
fix lint issues
starkillerOG Feb 23, 2020
f325631
fix issort
starkillerOG Feb 23, 2020
ca9568f
black formatting
starkillerOG Feb 23, 2020
4be1eda
Add Xiaomi miio aqara gateway alarm
starkillerOG Mar 5, 2020
24d0762
update style of boilerplate
starkillerOG Mar 9, 2020
9ebf225
bump python-miio version to 0.5.0
starkillerOG Mar 29, 2020
e7a8574
bump python-miio to v0.5.0.1
starkillerOG Mar 29, 2020
d69c11e
Centralize configuration
starkillerOG Apr 17, 2020
c8fd03f
Centralize configuration
starkillerOG Apr 17, 2020
f853a4b
fix overwrites
starkillerOG Apr 17, 2020
4f3202d
Implement Xiaomi Miio Config Flow
starkillerOG Apr 18, 2020
bd13d1f
Implement Xiaomi Miio Config Flow
starkillerOG Apr 18, 2020
df0f481
Implement Xiaomi Miio Config Flow
starkillerOG Apr 18, 2020
c0fd481
Implement Xiaomi Miio Config Flow
starkillerOG Apr 18, 2020
f063cb8
Implement Xiaomi Miio Config Flow
starkillerOG Apr 18, 2020
96f5550
Implement Xiaomi Miio Config Flow
starkillerOG Apr 18, 2020
a35fa11
Implement Xiaomi Miio Config Flow
starkillerOG Apr 18, 2020
e9df0ef
fix overwrites
starkillerOG Apr 18, 2020
921e2dc
remove unused imports
starkillerOG Apr 18, 2020
a3af0f0
remove unused imports
starkillerOG Apr 18, 2020
b8f0cb1
remove unused imports
starkillerOG Apr 18, 2020
0a50d3c
fix issort
starkillerOG Apr 18, 2020
2f4a162
fix issort
starkillerOG Apr 18, 2020
d158067
fix pyupgrade
starkillerOG Apr 18, 2020
f23962f
fix pyupgrade
starkillerOG Apr 18, 2020
0e24d25
fix faulty unused_import
starkillerOG Apr 18, 2020
d31cc01
typo
starkillerOG Apr 20, 2020
42e3e12
typo
starkillerOG Apr 20, 2020
06e3691
process revieuw requests
starkillerOG Apr 20, 2020
ea00bb3
process revieuw comments
starkillerOG Apr 20, 2020
132ccad
cleanup async_setup
starkillerOG Apr 20, 2020
16754ec
Do not return value if not used
starkillerOG Apr 20, 2020
577f3d6
process revieuw comments
starkillerOG Apr 20, 2020
20c3dd1
Create gateway.py
starkillerOG Apr 20, 2020
248777c
improve Xiaomi Miio Config Flow
starkillerOG Apr 20, 2020
364ca28
Split up async_setup_entry & async connect
starkillerOG Apr 20, 2020
8007af5
remove unused imports
starkillerOG Apr 20, 2020
fbed14c
black formatting
starkillerOG Apr 21, 2020
b5ade6c
black formatting
starkillerOG Apr 21, 2020
7b01467
black formatting
starkillerOG Apr 21, 2020
386f9ed
add missing docstring
starkillerOG Apr 21, 2020
8f859f8
process revieuw comments
starkillerOG Apr 21, 2020
da73200
process revieuw comments
starkillerOG Apr 21, 2020
5fc97f0
use key acces instead of .get
starkillerOG Apr 21, 2020
d351332
Run I/O in executor thread pool
starkillerOG Apr 23, 2020
b76bee6
schedule I/O in executor thread pool
starkillerOG Apr 23, 2020
1bd1ed3
schedule I/O in executor thread pool
starkillerOG Apr 23, 2020
120f9a6
Update homeassistant/components/xiaomi_miio/gateway.py
starkillerOG Apr 24, 2020
c10010b
pass hass at initialize
starkillerOG Apr 24, 2020
0ca6943
pass hass at init
starkillerOG Apr 24, 2020
37551f2
pass hass at init
starkillerOG Apr 24, 2020
ff7d3b8
create Config Flow test for Xiaomi Miio
starkillerOG Apr 24, 2020
de2bb8f
fix test_config_flow with black and isort
starkillerOG Apr 24, 2020
f43733b
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG Apr 25, 2020
769299d
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG Apr 25, 2020
f84f292
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG Apr 25, 2020
8ee9f7d
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG Apr 25, 2020
a9d1cfa
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG Apr 25, 2020
92ad343
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG Apr 25, 2020
8897f3b
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG Apr 25, 2020
a643970
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG Apr 25, 2020
a8b4382
fix test_config_flow
starkillerOG Apr 25, 2020
33f71e1
fix function docstrings
starkillerOG Apr 26, 2020
4876e99
include xiaomi_miio config flow
starkillerOG Apr 26, 2020
580b28d
Update .coveragerc
starkillerOG Apr 26, 2020
f939865
rename translations folder
starkillerOG Apr 26, 2020
cfa5533
add missing title in translations
starkillerOG Apr 27, 2020
a4da181
add missing title in translations
starkillerOG Apr 27, 2020
dcd0113
undo title in translations
starkillerOG Apr 27, 2020
847f7a3
undow add title in tanslation
starkillerOG Apr 27, 2020
f8c82c6
update AlarmControlPanel to AlarmControlPanelEntity
starkillerOG Apr 27, 2020
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
12 changes: 11 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,17 @@ omit =
homeassistant/components/xfinity/device_tracker.py
homeassistant/components/xiaomi/camera.py
homeassistant/components/xiaomi_aqara/*
homeassistant/components/xiaomi_miio/*
homeassistant/components/xiaomi_miio/__init__.py
homeassistant/components/xiaomi_miio/air_quality.py
homeassistant/components/xiaomi_miio/alarm_control_panel.py
homeassistant/components/xiaomi_miio/device_tracker.py
homeassistant/components/xiaomi_miio/fan.py
homeassistant/components/xiaomi_miio/gateway.py
homeassistant/components/xiaomi_miio/light.py
homeassistant/components/xiaomi_miio/remote.py
homeassistant/components/xiaomi_miio/sensor.py
homeassistant/components/xiaomi_miio/switch.py
homeassistant/components/xiaomi_miio/vacuum.py
homeassistant/components/xiaomi_tv/media_player.py
homeassistant/components/xmpp/notify.py
homeassistant/components/xs1/*
Expand Down
68 changes: 68 additions & 0 deletions homeassistant/components/xiaomi_miio/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,69 @@
"""Support for Xiaomi Miio."""
import logging

from homeassistant import config_entries, core
from homeassistant.const import CONF_HOST, CONF_TOKEN
from homeassistant.helpers import device_registry as dr

from .config_flow import CONF_FLOW_TYPE, CONF_GATEWAY
from .const import DOMAIN
from .gateway import ConnectXiaomiGateway

_LOGGER = logging.getLogger(__name__)

GATEWAY_PLATFORMS = ["alarm_control_panel"]


async def async_setup(hass: core.HomeAssistant, config: dict):
"""Set up the Xiaomi Miio component."""
return True


async def async_setup_entry(
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
):
"""Set up the Xiaomi Miio components from a config entry."""
hass.data[DOMAIN] = {}
if entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY:
if not await async_setup_gateway_entry(hass, entry):
return False

return True


async def async_setup_gateway_entry(
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
):
"""Set up the Xiaomi Gateway component from a config entry."""
host = entry.data[CONF_HOST]
token = entry.data[CONF_TOKEN]
name = entry.title
gateway_id = entry.data["gateway_id"]

# Connect to gateway
gateway = ConnectXiaomiGateway(hass)
if not await gateway.async_connect_gateway(host, token):
return False
gateway_info = gateway.gateway_info

hass.data[DOMAIN][entry.entry_id] = gateway.gateway_device

gateway_model = f"{gateway_info.model}-{gateway_info.hardware_version}"
Comment thread
starkillerOG marked this conversation as resolved.

device_registry = await dr.async_get_registry(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
connections={(dr.CONNECTION_NETWORK_MAC, gateway_info.mac_address)},
identifiers={(DOMAIN, gateway_id)},
manufacturer="Xiaomi",
name=name,
model=gateway_model,
sw_version=gateway_info.firmware_version,
)

for component in GATEWAY_PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)

return True
150 changes: 150 additions & 0 deletions homeassistant/components/xiaomi_miio/alarm_control_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
"""Support for Xiomi Gateway alarm control panels."""

from functools import partial
import logging

from miio import DeviceException

from homeassistant.components.alarm_control_panel import (
SUPPORT_ALARM_ARM_AWAY,
AlarmControlPanelEntity,
)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMING,
STATE_ALARM_DISARMED,
)

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

XIAOMI_STATE_ARMED_VALUE = "on"
XIAOMI_STATE_DISARMED_VALUE = "off"
XIAOMI_STATE_ARMING_VALUE = "oning"
Comment thread
MartinHjelmare marked this conversation as resolved.


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Xiaomi Gateway Alarm from a config entry."""
entities = []
gateway = hass.data[DOMAIN][config_entry.entry_id]
entity = XiaomiGatewayAlarm(
gateway,
f"{config_entry.title} Alarm",
config_entry.data["model"],
config_entry.data["mac"],
config_entry.data["gateway_id"],
)
entities.append(entity)
async_add_entities(entities)


class XiaomiGatewayAlarm(AlarmControlPanelEntity):
"""Representation of the XiaomiGatewayAlarm."""

def __init__(
self, gateway_device, gateway_name, model, mac_address, gateway_device_id
):
"""Initialize the entity."""
self._gateway = gateway_device
self._name = gateway_name
self._gateway_device_id = gateway_device_id
self._unique_id = f"{model}-{mac_address}"
self._icon = "mdi:shield-home"
self._available = None
self._state = None

@property
def unique_id(self):
"""Return an unique ID."""
return self._unique_id

@property
def device_id(self):
"""Return the device id of the gateway."""
return self._gateway_device_id

@property
def device_info(self):
"""Return the device info of the gateway."""
return {
"identifiers": {(DOMAIN, self._gateway_device_id)},
}

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

@property
def icon(self):
"""Return the icon to use for device if any."""
return self._icon

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

@property
def state(self):
"""Return the state of the device."""
return self._state

@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return SUPPORT_ALARM_ARM_AWAY

async def _try_command(self, mask_error, func, *args, **kwargs):
"""Call a device command handling error messages."""
try:
result = await self.hass.async_add_executor_job(
partial(func, *args, **kwargs)
)
_LOGGER.debug("Response received from miio device: %s", result)
except DeviceException as exc:
_LOGGER.error(mask_error, exc)

async def async_alarm_arm_away(self, code=None):
"""Turn on."""
await self._try_command(
"Turning the alarm on failed: %s", self._gateway.alarm.on
)

async def async_alarm_disarm(self, code=None):
"""Turn off."""
await self._try_command(
"Turning the alarm off failed: %s", self._gateway.alarm.off
)

async def async_update(self):
"""Fetch state from the device."""
try:
state = await self.hass.async_add_executor_job(self._gateway.alarm.status)
except DeviceException as ex:
Comment thread
starkillerOG marked this conversation as resolved.
self._available = False
_LOGGER.error("Got exception while fetching the state: %s", ex)
return

_LOGGER.debug("Got new state: %s", state)

self._available = True

if state == XIAOMI_STATE_ARMED_VALUE:
self._state = STATE_ALARM_ARMED_AWAY
elif state == XIAOMI_STATE_DISARMED_VALUE:
self._state = STATE_ALARM_DISARMED
elif state == XIAOMI_STATE_ARMING_VALUE:
self._state = STATE_ALARM_ARMING
else:
_LOGGER.warning(
"New state (%s) doesn't match expected values: %s/%s/%s",
state,
XIAOMI_STATE_ARMED_VALUE,
XIAOMI_STATE_DISARMED_VALUE,
XIAOMI_STATE_ARMING_VALUE,
)
self._state = None

_LOGGER.debug("State value: %s", self._state)
82 changes: 82 additions & 0 deletions homeassistant/components/xiaomi_miio/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""Config flow to configure Xiaomi Miio."""
import logging

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN

# pylint: disable=unused-import
from .const import DOMAIN
from .gateway import ConnectXiaomiGateway

_LOGGER = logging.getLogger(__name__)

CONF_FLOW_TYPE = "config_flow_device"
CONF_GATEWAY = "gateway"
DEFAULT_GATEWAY_NAME = "Xiaomi Gateway"

GATEWAY_CONFIG = vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Required(CONF_TOKEN): vol.All(str, vol.Length(min=32, max=32)),
vol.Optional(CONF_NAME, default=DEFAULT_GATEWAY_NAME): str,
}
)

CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_GATEWAY, default=False): bool})


class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a Xiaomi Miio config flow."""

VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL

async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
errors = {}
if user_input is not None:
# Check which device needs to be connected.
if user_input[CONF_GATEWAY]:
return await self.async_step_gateway()

errors["base"] = "no_device_selected"

return self.async_show_form(
step_id="user", data_schema=CONFIG_SCHEMA, errors=errors
)

async def async_step_gateway(self, user_input=None):
"""Handle a flow initialized by the user to configure a gateway."""
errors = {}
if user_input is not None:
host = user_input[CONF_HOST]
token = user_input[CONF_TOKEN]

# Try to connect to a Xiaomi Gateway.
connect_gateway_class = ConnectXiaomiGateway(self.hass)
await connect_gateway_class.async_connect_gateway(host, token)
gateway_info = connect_gateway_class.gateway_info

if gateway_info is not None:
unique_id = f"{gateway_info.model}-{gateway_info.mac_address}-gateway"
await self.async_set_unique_id(unique_id)
self._abort_if_unique_id_configured()
return self.async_create_entry(
Comment thread
starkillerOG marked this conversation as resolved.
title=user_input[CONF_NAME],
data={
CONF_FLOW_TYPE: CONF_GATEWAY,
CONF_HOST: host,
CONF_TOKEN: token,
"gateway_id": unique_id,
"model": gateway_info.model,
"mac": gateway_info.mac_address,
},
)

errors["base"] = "connect_error"

return self.async_show_form(
step_id="gateway", data_schema=GATEWAY_CONFIG, errors=errors
)
47 changes: 47 additions & 0 deletions homeassistant/components/xiaomi_miio/gateway.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Code to handle a Xiaomi Gateway."""
import logging

from miio import DeviceException, gateway

_LOGGER = logging.getLogger(__name__)


class ConnectXiaomiGateway:
Comment thread
starkillerOG marked this conversation as resolved.
"""Class to async connect to a Xiaomi Gateway."""

def __init__(self, hass):
Comment thread
starkillerOG marked this conversation as resolved.
"""Initialize the entity."""
self._hass = hass
self._gateway_device = None
self._gateway_info = None

@property
def gateway_device(self):
"""Return the class containing all connections to the gateway."""
return self._gateway_device

@property
def gateway_info(self):
"""Return the class containing gateway info."""
return self._gateway_info

async def async_connect_gateway(self, host, token):
"""Connect to the Xiaomi Gateway."""
_LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
try:
self._gateway_device = gateway.Gateway(host, token)
self._gateway_info = await self._hass.async_add_executor_job(
self._gateway_device.info
)
except DeviceException:
_LOGGER.error(
"DeviceException during setup of xiaomi gateway with host %s", host
)
return False
_LOGGER.debug(
"%s %s %s detected",
self._gateway_info.model,
self._gateway_info.firmware_version,
self._gateway_info.hardware_version,
)
return True
3 changes: 2 additions & 1 deletion homeassistant/components/xiaomi_miio/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"domain": "xiaomi_miio",
"name": "Xiaomi miio",
"name": "Xiaomi Miio",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/xiaomi_miio",
"requirements": ["construct==2.9.45", "python-miio==0.5.0.1"],
"codeowners": ["@rytilahti", "@syssi"]
Expand Down
Loading