-
-
Notifications
You must be signed in to change notification settings - Fork 37.4k
Add Xiaomi miio Alarm Control Panel #32091
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
Merged
Merged
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 913b052
black formatting
starkillerOG 3e5d32f
fix lint issues
starkillerOG f325631
fix issort
starkillerOG ca9568f
black formatting
starkillerOG 4be1eda
Add Xiaomi miio aqara gateway alarm
starkillerOG 24d0762
update style of boilerplate
starkillerOG 9ebf225
bump python-miio version to 0.5.0
starkillerOG e7a8574
bump python-miio to v0.5.0.1
starkillerOG d69c11e
Centralize configuration
starkillerOG c8fd03f
Centralize configuration
starkillerOG f853a4b
fix overwrites
starkillerOG 4f3202d
Implement Xiaomi Miio Config Flow
starkillerOG bd13d1f
Implement Xiaomi Miio Config Flow
starkillerOG df0f481
Implement Xiaomi Miio Config Flow
starkillerOG c0fd481
Implement Xiaomi Miio Config Flow
starkillerOG f063cb8
Implement Xiaomi Miio Config Flow
starkillerOG 96f5550
Implement Xiaomi Miio Config Flow
starkillerOG a35fa11
Implement Xiaomi Miio Config Flow
starkillerOG e9df0ef
fix overwrites
starkillerOG 921e2dc
remove unused imports
starkillerOG a3af0f0
remove unused imports
starkillerOG b8f0cb1
remove unused imports
starkillerOG 0a50d3c
fix issort
starkillerOG 2f4a162
fix issort
starkillerOG d158067
fix pyupgrade
starkillerOG f23962f
fix pyupgrade
starkillerOG 0e24d25
fix faulty unused_import
starkillerOG d31cc01
typo
starkillerOG 42e3e12
typo
starkillerOG 06e3691
process revieuw requests
starkillerOG ea00bb3
process revieuw comments
starkillerOG 132ccad
cleanup async_setup
starkillerOG 16754ec
Do not return value if not used
starkillerOG 577f3d6
process revieuw comments
starkillerOG 20c3dd1
Create gateway.py
starkillerOG 248777c
improve Xiaomi Miio Config Flow
starkillerOG 364ca28
Split up async_setup_entry & async connect
starkillerOG 8007af5
remove unused imports
starkillerOG fbed14c
black formatting
starkillerOG b5ade6c
black formatting
starkillerOG 7b01467
black formatting
starkillerOG 386f9ed
add missing docstring
starkillerOG 8f859f8
process revieuw comments
starkillerOG da73200
process revieuw comments
starkillerOG 5fc97f0
use key acces instead of .get
starkillerOG d351332
Run I/O in executor thread pool
starkillerOG b76bee6
schedule I/O in executor thread pool
starkillerOG 1bd1ed3
schedule I/O in executor thread pool
starkillerOG 120f9a6
Update homeassistant/components/xiaomi_miio/gateway.py
starkillerOG c10010b
pass hass at initialize
starkillerOG 0ca6943
pass hass at init
starkillerOG 37551f2
pass hass at init
starkillerOG ff7d3b8
create Config Flow test for Xiaomi Miio
starkillerOG de2bb8f
fix test_config_flow with black and isort
starkillerOG f43733b
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG 769299d
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG f84f292
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG 8ee9f7d
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG a9d1cfa
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG 92ad343
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG 8897f3b
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG a643970
Update tests/components/xiaomi_miio/test_config_flow.py
starkillerOG a8b4382
fix test_config_flow
starkillerOG 33f71e1
fix function docstrings
starkillerOG 4876e99
include xiaomi_miio config flow
starkillerOG 580b28d
Update .coveragerc
starkillerOG f939865
rename translations folder
starkillerOG cfa5533
add missing title in translations
starkillerOG a4da181
add missing title in translations
starkillerOG dcd0113
undo title in translations
starkillerOG 847f7a3
undow add title in tanslation
starkillerOG f8c82c6
update AlarmControlPanel to AlarmControlPanelEntity
starkillerOG File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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}" | ||
|
|
||
| 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
150
homeassistant/components/xiaomi_miio/alarm_control_panel.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" | ||
|
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: | ||
|
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) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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( | ||
|
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 | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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: | ||
|
starkillerOG marked this conversation as resolved.
|
||
| """Class to async connect to a Xiaomi Gateway.""" | ||
|
|
||
| def __init__(self, hass): | ||
|
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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.