-
-
Notifications
You must be signed in to change notification settings - Fork 37.4k
Add BleBox integration #32664
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
MartinHjelmare
merged 83 commits into
home-assistant:dev
from
blebox:add-blebox-integration
May 5, 2020
Merged
Add BleBox integration #32664
Changes from all commits
Commits
Show all changes
83 commits
Select commit
Hold shift + click to select a range
d936b4c
add BleBox integration (11 devices)
gadgetmobile 764e419
bump blebox_uniapi version
gadgetmobile f2e11a9
disable harmless pylint warnings (TODOs)
gadgetmobile 9ffdefa
increase coverage + cleanup TODOs
gadgetmobile 5e8016b
limit PR to one platform (covers)
gadgetmobile 8386c99
increase coverage + cleanup TODOs
gadgetmobile 6d90f25
remove unused pylint comment
gadgetmobile 4bc4354
set PARALLEL_UPDATES=1 for current blebox_uniapi
gadgetmobile 9e88d98
remove TODO comment
gadgetmobile 5029946
add missing errors.py file
gadgetmobile 80753c4
rework config flow
gadgetmobile e09a46f
reorganize strings + translations
gadgetmobile 8a78b42
use a fixed timeout of 3 seconds
gadgetmobile a9a6077
allow parallel updates
gadgetmobile 64a1a17
bump blebox_uniapi to 1.2.0
gadgetmobile bec878a
rework error handling + increase coverage
gadgetmobile 6f6ddbf
increase coverage to 100%
gadgetmobile 7cca926
fix pylint error
gadgetmobile 94cd788
Update homeassistant/components/blebox/__init__.py
gadgetmobile e3b472d
Update homeassistant/components/blebox/__init__.py
gadgetmobile 511cda8
Update homeassistant/components/blebox/__init__.py
gadgetmobile 5f8a90e
Update homeassistant/components/blebox/cover.py
gadgetmobile 1ac1425
rename CommonEntity to BleBoxEntity
gadgetmobile d85be83
bump to blebox_uniapi 1.3.0
gadgetmobile 68a37cf
add device registry entries
gadgetmobile d2d08bd
fix docsstring
gadgetmobile 3d6ff37
remove async_setup_platform
gadgetmobile 35fed4d
make timeout a const
gadgetmobile a87d90f
improve error handling
gadgetmobile b5ba36d
refactor config_flow
gadgetmobile 0888a11
raise if set_cover_position is not supported
gadgetmobile fd19d76
remove test classes
gadgetmobile 136480c
remove unused semver dependency
gadgetmobile 0dba805
rename handle to handle_step_exception
gadgetmobile c892048
change param order for bitwise AND operator
gadgetmobile 58276f3
move state and device class maps to consts
gadgetmobile 6edba6d
rename method to async_create_blebox_entities
gadgetmobile 127dc76
improve naming clarity and consistency
gadgetmobile 88c9120
refactor async_create_blebox_entities
gadgetmobile bce496f
use new DEVICE_CLASS_GATE constant
gadgetmobile dad247a
regenerate dependency requirements
gadgetmobile f1a99b3
update broken test
gadgetmobile ac823c5
fix pylint error
gadgetmobile aad0ddb
rework shared tests
gadgetmobile e25efaf
remove unused CONFIG_SCHEMA
gadgetmobile 74efef9
refactor + improve config_flow tests
gadgetmobile 0b7a21f
fix typo in TODO
gadgetmobile 5aa08b0
bump blebox_uniapi dependency to 1.3.1
gadgetmobile fc14b85
use ConfigEntryNotReady and rework setup
gadgetmobile b43e799
fix pyupgrade errors
gadgetmobile 6e89ab6
log errors only for unknown exceptions
gadgetmobile f8f09da
move constants to consts.py
gadgetmobile 13039e8
remove unused translation strings
gadgetmobile 8410dd8
add comment for translations string consts
gadgetmobile 83c5813
improve component unloading + missing test
gadgetmobile 3b6883f
fix integration translation validation for v0.109
gadgetmobile 6aa8630
update docstrings
gadgetmobile f1fe2a4
avoid using format()
gadgetmobile 5ab6925
remove unused file
gadgetmobile e954a85
rename translations dir to match new HA version
gadgetmobile 6df4121
properly use async_init in tests + refactor
gadgetmobile 2d97e5e
remove async property on non-async method
gadgetmobile 2dd6e59
remove return value which is never used
gadgetmobile 26a393a
implicitly inherit Entity for clarity
gadgetmobile eaf9913
inline method which is used only once
gadgetmobile d15b191
bump blebox_uniapi version to 1.3.2
gadgetmobile 1e67f8d
stop sharing logger with blebox_uniapi
gadgetmobile 3b86818
avoid inheriting deprecated CoverDevice class
gadgetmobile e349efc
remove property side-effect
gadgetmobile d84fa14
assume core handles unsupported case properly
gadgetmobile ca612d7
remove unnecessary position presence checking
gadgetmobile d94f49e
remove empty dependencies section in manifest
gadgetmobile 10c1998
rename constant to ADDRESS_ALREADY_CONFIGURED
gadgetmobile 6b119fa
use tests.async_mock instead of asynctest
gadgetmobile bd70c23
rework cover tests and helper classes
gadgetmobile d4187c4
remove test helper methods
gadgetmobile 9cde7b0
assure that async_update is initially properly mocked
gadgetmobile 7712790
remove wrapper class
gadgetmobile c1c4900
rename entity to entry
gadgetmobile 2f1c14d
properly use async_setup and caplog in tests
gadgetmobile 4ecbc62
fix pylint warning about unused var
gadgetmobile 92e76e6
check entry state properly
gadgetmobile e27c7f4
remove impossible test case
gadgetmobile 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 |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| """The BleBox devices integration.""" | ||
| import asyncio | ||
| import logging | ||
|
|
||
| from blebox_uniapi.error import Error | ||
| from blebox_uniapi.products import Products | ||
| from blebox_uniapi.session import ApiHost | ||
|
|
||
| from homeassistant.config_entries import ConfigEntry | ||
| from homeassistant.const import CONF_HOST, CONF_PORT | ||
| from homeassistant.core import HomeAssistant, callback | ||
| from homeassistant.exceptions import ConfigEntryNotReady | ||
| from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
| from homeassistant.helpers.entity import Entity | ||
|
|
||
| from .const import DEFAULT_SETUP_TIMEOUT, DOMAIN, PRODUCT | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| PLATFORMS = ["cover"] | ||
|
|
||
| PARALLEL_UPDATES = 0 | ||
|
|
||
|
|
||
| async def async_setup(hass: HomeAssistant, config: dict): | ||
| """Set up the BleBox devices component.""" | ||
| return True | ||
|
|
||
|
|
||
| async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): | ||
| """Set up BleBox devices from a config entry.""" | ||
|
|
||
| websession = async_get_clientsession(hass) | ||
|
|
||
| host = entry.data[CONF_HOST] | ||
| port = entry.data[CONF_PORT] | ||
| timeout = DEFAULT_SETUP_TIMEOUT | ||
|
|
||
| api_host = ApiHost(host, port, timeout, websession, hass.loop) | ||
|
|
||
| try: | ||
| product = await Products.async_from_host(api_host) | ||
| except Error as ex: | ||
| _LOGGER.error("Identify failed at %s:%d (%s)", api_host.host, api_host.port, ex) | ||
| raise ConfigEntryNotReady from ex | ||
|
|
||
| domain = hass.data.setdefault(DOMAIN, {}) | ||
| domain_entry = domain.setdefault(entry.entry_id, {}) | ||
| product = domain_entry.setdefault(PRODUCT, product) | ||
|
|
||
| for component in PLATFORMS: | ||
| hass.async_create_task( | ||
| hass.config_entries.async_forward_entry_setup(entry, component) | ||
| ) | ||
|
|
||
| return True | ||
|
|
||
|
|
||
| async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): | ||
| """Unload a config entry.""" | ||
| unload_ok = all( | ||
| await asyncio.gather( | ||
| *[ | ||
| hass.config_entries.async_forward_entry_unload(entry, platform) | ||
| for platform in PLATFORMS | ||
| ] | ||
| ) | ||
| ) | ||
|
|
||
| if unload_ok: | ||
| hass.data[DOMAIN].pop(entry.entry_id) | ||
|
|
||
| return unload_ok | ||
|
|
||
|
|
||
| @callback | ||
| def create_blebox_entities(product, async_add, entity_klass, entity_type): | ||
| """Create entities from a BleBox product's features.""" | ||
|
|
||
| entities = [] | ||
| for feature in product.features[entity_type]: | ||
| entities.append(entity_klass(feature)) | ||
|
|
||
| async_add(entities, True) | ||
|
|
||
|
|
||
| class BleBoxEntity(Entity): | ||
| """Implements a common class for entities representing a BleBox feature.""" | ||
|
|
||
| def __init__(self, feature): | ||
| """Initialize a BleBox entity.""" | ||
| self._feature = feature | ||
|
|
||
| @property | ||
| def name(self): | ||
| """Return the internal entity name.""" | ||
| return self._feature.full_name | ||
|
|
||
| @property | ||
| def unique_id(self): | ||
| """Return a unique id.""" | ||
| return self._feature.unique_id | ||
|
|
||
| async def async_update(self): | ||
| """Update the entity state.""" | ||
| try: | ||
| await self._feature.async_update() | ||
| except Error as ex: | ||
| _LOGGER.error("Updating '%s' failed: %s", self.name, ex) | ||
|
bdraco marked this conversation as resolved.
Outdated
|
||
|
|
||
| @property | ||
| def device_info(self): | ||
| """Return device information for this entity.""" | ||
| product = self._feature.product | ||
| return { | ||
| "identifiers": {(DOMAIN, product.unique_id)}, | ||
| "name": product.name, | ||
| "manufacturer": product.brand, | ||
| "model": product.model, | ||
| "sw_version": product.firmware_version, | ||
| } | ||
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,128 @@ | ||
| """Config flow for BleBox devices integration.""" | ||
| import logging | ||
|
|
||
| from blebox_uniapi.error import Error, UnsupportedBoxVersion | ||
| from blebox_uniapi.products import Products | ||
| from blebox_uniapi.session import ApiHost | ||
| import voluptuous as vol | ||
|
|
||
| from homeassistant import config_entries | ||
| from homeassistant.const import CONF_HOST, CONF_PORT | ||
| from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
|
|
||
| from .const import ( | ||
| ADDRESS_ALREADY_CONFIGURED, | ||
| CANNOT_CONNECT, | ||
| DEFAULT_HOST, | ||
| DEFAULT_PORT, | ||
| DEFAULT_SETUP_TIMEOUT, | ||
| DOMAIN, | ||
| UNKNOWN, | ||
| UNSUPPORTED_VERSION, | ||
| ) | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def host_port(data): | ||
| """Return a list with host and port.""" | ||
| return (data[CONF_HOST], data[CONF_PORT]) | ||
|
|
||
|
|
||
| def create_schema(previous_input=None): | ||
| """Create a schema with given values as default.""" | ||
| if previous_input is not None: | ||
| host, port = host_port(previous_input) | ||
| else: | ||
| host = DEFAULT_HOST | ||
| port = DEFAULT_PORT | ||
|
|
||
| return vol.Schema( | ||
| { | ||
| vol.Required(CONF_HOST, default=host): str, | ||
| vol.Required(CONF_PORT, default=port): int, | ||
| } | ||
| ) | ||
|
|
||
|
|
||
| LOG_MSG = { | ||
| UNSUPPORTED_VERSION: "Outdated firmware", | ||
| CANNOT_CONNECT: "Failed to identify device", | ||
| UNKNOWN: "Unknown error while identifying device", | ||
| } | ||
|
|
||
|
|
||
| class BleBoxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): | ||
| """Handle a config flow for BleBox devices.""" | ||
|
|
||
| VERSION = 1 | ||
| CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL | ||
|
|
||
| def __init__(self): | ||
| """Initialize the BleBox config flow.""" | ||
| self.device_config = {} | ||
|
|
||
| def handle_step_exception( | ||
| self, step, exception, schema, host, port, message_id, log_fn | ||
| ): | ||
| """Handle step exceptions.""" | ||
|
|
||
| log_fn("%s at %s:%d (%s)", LOG_MSG[message_id], host, port, exception) | ||
|
|
||
| return self.async_show_form( | ||
| step_id="user", | ||
| data_schema=schema, | ||
| errors={"base": message_id}, | ||
| description_placeholders={"address": f"{host}:{port}"}, | ||
| ) | ||
|
|
||
| async def async_step_user(self, user_input=None): | ||
| """Handle initial user-triggered config step.""" | ||
|
|
||
| hass = self.hass | ||
| schema = create_schema(user_input) | ||
|
|
||
| if user_input is None: | ||
| return self.async_show_form( | ||
| step_id="user", | ||
| data_schema=schema, | ||
| errors={}, | ||
| description_placeholders={}, | ||
| ) | ||
|
|
||
| addr = host_port(user_input) | ||
|
|
||
| for entry in hass.config_entries.async_entries(DOMAIN): | ||
| if addr == host_port(entry.data): | ||
| host, port = addr | ||
| return self.async_abort( | ||
| reason=ADDRESS_ALREADY_CONFIGURED, | ||
| description_placeholders={"address": f"{host}:{port}"}, | ||
| ) | ||
|
|
||
| websession = async_get_clientsession(hass) | ||
| api_host = ApiHost(*addr, DEFAULT_SETUP_TIMEOUT, websession, hass.loop, _LOGGER) | ||
|
gadgetmobile marked this conversation as resolved.
|
||
|
|
||
| try: | ||
| product = await Products.async_from_host(api_host) | ||
|
|
||
| except UnsupportedBoxVersion as ex: | ||
| return self.handle_step_exception( | ||
| "user", ex, schema, *addr, UNSUPPORTED_VERSION, _LOGGER.debug | ||
| ) | ||
|
|
||
| except Error as ex: | ||
| return self.handle_step_exception( | ||
| "user", ex, schema, *addr, CANNOT_CONNECT, _LOGGER.warning | ||
| ) | ||
|
|
||
| except RuntimeError as ex: | ||
| return self.handle_step_exception( | ||
| "user", ex, schema, *addr, UNKNOWN, _LOGGER.error | ||
| ) | ||
|
|
||
| # Check if configured but IP changed since | ||
| await self.async_set_unique_id(product.unique_id) | ||
| self._abort_if_unique_id_configured() | ||
|
|
||
| return self.async_create_entry(title=product.name, data=user_input) | ||
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,45 @@ | ||
| """Constants for the BleBox devices integration.""" | ||
|
|
||
| from homeassistant.components.cover import ( | ||
| DEVICE_CLASS_DOOR, | ||
| DEVICE_CLASS_GATE, | ||
| DEVICE_CLASS_SHUTTER, | ||
| STATE_CLOSED, | ||
| STATE_CLOSING, | ||
| STATE_OPEN, | ||
| STATE_OPENING, | ||
| ) | ||
|
|
||
| DOMAIN = "blebox" | ||
| PRODUCT = "product" | ||
|
|
||
| DEFAULT_SETUP_TIMEOUT = 3 | ||
|
|
||
| # translation strings | ||
| ADDRESS_ALREADY_CONFIGURED = "address_already_configured" | ||
| CANNOT_CONNECT = "cannot_connect" | ||
| UNSUPPORTED_VERSION = "unsupported_version" | ||
| UNKNOWN = "unknown" | ||
|
|
||
| BLEBOX_TO_HASS_DEVICE_CLASSES = { | ||
| "shutter": DEVICE_CLASS_SHUTTER, | ||
| "gatebox": DEVICE_CLASS_DOOR, | ||
| "gate": DEVICE_CLASS_GATE, | ||
| } | ||
|
|
||
| BLEBOX_TO_HASS_COVER_STATES = { | ||
| None: None, | ||
| 0: STATE_CLOSING, # moving down | ||
| 1: STATE_OPENING, # moving up | ||
| 2: STATE_OPEN, # manually stopped | ||
| 3: STATE_CLOSED, # lower limit | ||
| 4: STATE_OPEN, # upper limit / open | ||
| # gateController | ||
| 5: STATE_OPEN, # overload | ||
| 6: STATE_OPEN, # motor failure | ||
| # 7 is not used | ||
| 8: STATE_OPEN, # safety stop | ||
| } | ||
|
|
||
| DEFAULT_HOST = "192.168.0.2" | ||
| DEFAULT_PORT = 80 |
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.