-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e8594db
Showing
13 changed files
with
798 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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,2 @@ | ||
# Auto detect text files and perform LF normalization | ||
* text=auto |
This file contains 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 @@ | ||
__pycache__/ |
This file contains 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,17 @@ | ||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. |
This file contains 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,21 @@ | ||
# Cover Time-based Component | ||
|
||
Forked from [@davidramosweb](https://github.com/davidramosweb/home-assistant-custom-components-cover-time-based) @ 2021, | ||
this custom component now integrates easily in Home Assistant. | ||
|
||
Convert your (dummy) `switch` into a `cover`, and allow to control its position. | ||
|
||
## Install | ||
|
||
[![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=duhow&repository=hass-cover-time-based&category=integration) | ||
|
||
## Usage | ||
|
||
[![Open your Home Assistant instance and show your helper entities.](https://my.home-assistant.io/badges/helpers.svg)](https://my.home-assistant.io/redirect/helpers/) | ||
|
||
Check **Change device type to a Cover time-based**. | ||
|
||
## Credits | ||
|
||
* [@davidramosweb](https://github.com/davidramosweb) for its original code base. | ||
* [xknx](https://xknx.io/) Python library for the `TravelCalculator` control class. |
This file contains 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,154 @@ | ||
"""Component to wrap switch entities in entities of other domains.""" | ||
|
||
from __future__ import annotations | ||
|
||
import logging | ||
|
||
import voluptuous as vol | ||
|
||
from homeassistant.components.homeassistant import exposed_entities | ||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import CONF_ENTITY_ID | ||
from homeassistant.core import Event, HomeAssistant, callback | ||
from homeassistant.helpers import device_registry as dr, entity_registry as er | ||
from homeassistant.helpers.event import async_track_entity_registry_updated_event | ||
|
||
from homeassistant.components.cover import ( | ||
DOMAIN as COVER_DOMAIN | ||
) | ||
|
||
from .const import CONF_INVERT, CONF_TARGET_DEVICE_CLASS, CONF_ENTITY_UP, CONF_ENTITY_DOWN | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
@callback | ||
def async_add_to_device( | ||
hass: HomeAssistant, entry: ConfigEntry, entity_id: str | ||
) -> str | None: | ||
"""Add our config entry to the tracked entity's device.""" | ||
registry = er.async_get(hass) | ||
device_registry = dr.async_get(hass) | ||
device_id = None | ||
|
||
if ( | ||
not (wrapped_switch := registry.async_get(entity_id)) | ||
or not (device_id := wrapped_switch.device_id) | ||
or not (device_registry.async_get(device_id)) | ||
): | ||
return device_id | ||
|
||
device_registry.async_update_device(device_id, add_config_entry_id=entry.entry_id) | ||
|
||
return device_id | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Check light-swich up and down exist.""" | ||
registry = er.async_get(hass) | ||
device_registry = dr.async_get(hass) | ||
try: | ||
for entity in [CONF_ENTITY_UP, CONF_ENTITY_DOWN]: | ||
entity_id = er.async_validate_entity_id(registry, entry.options[entity]) | ||
except vol.Invalid: | ||
# The entity is identified by an unknown entity registry ID | ||
_LOGGER.error( | ||
"Failed to setup cover_time_based for unknown entity %s", | ||
entry.options[entity], | ||
) | ||
return False | ||
|
||
async def async_registry_updated( | ||
event: Event[er.EventEntityRegistryUpdatedData], | ||
) -> None: | ||
"""Handle entity registry update.""" | ||
data = event.data | ||
if data["action"] == "remove": | ||
await hass.config_entries.async_remove(entry.entry_id) | ||
|
||
if data["action"] != "update": | ||
return | ||
|
||
if "entity_id" in data["changes"]: | ||
# Entity_id changed, reload the config entry | ||
await hass.config_entries.async_reload(entry.entry_id) | ||
|
||
if device_id and "device_id" in data["changes"]: | ||
# If the tracked switch is no longer in the device, remove our config entry | ||
# from the device | ||
if ( | ||
not (entity_entry := registry.async_get(data[CONF_ENTITY_ID])) | ||
or not device_registry.async_get(device_id) | ||
or entity_entry.device_id == device_id | ||
): | ||
# No need to do any cleanup | ||
return | ||
|
||
device_registry.async_update_device( | ||
device_id, remove_config_entry_id=entry.entry_id | ||
) | ||
|
||
entry.async_on_unload( | ||
async_track_entity_registry_updated_event( | ||
hass, entity_id, async_registry_updated | ||
) | ||
) | ||
entry.async_on_unload(entry.add_update_listener(config_entry_update_listener)) | ||
|
||
device_id = async_add_to_device(hass, entry, entity_id) | ||
|
||
await hass.config_entries.async_forward_entry_setups( | ||
entry, (COVER_DOMAIN,) | ||
) | ||
return True | ||
|
||
|
||
async def config_entry_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: | ||
"""Update listener, called when the config entry options are changed.""" | ||
await hass.config_entries.async_reload(entry.entry_id) | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
return await hass.config_entries.async_unload_platforms( | ||
entry, (COVER_DOMAIN,) | ||
) | ||
|
||
|
||
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: | ||
"""Unload a config entry. | ||
This will unhide the wrapped entity and restore assistant expose settings. | ||
""" | ||
registry = er.async_get(hass) | ||
try: | ||
switch_entity_id = er.async_validate_entity_id( | ||
registry, entry.options[CONF_ENTITY_ID] | ||
) | ||
except vol.Invalid: | ||
# The source entity has been removed from the entity registry | ||
return | ||
|
||
if not (switch_entity_entry := registry.async_get(switch_entity_id)): | ||
return | ||
|
||
# Unhide the wrapped entity | ||
if switch_entity_entry.hidden_by == er.RegistryEntryHider.INTEGRATION: | ||
registry.async_update_entity(switch_entity_id, hidden_by=None) | ||
|
||
switch_as_x_entries = er.async_entries_for_config_entry(registry, entry.entry_id) | ||
if not switch_as_x_entries: | ||
return | ||
|
||
switch_as_x_entry = switch_as_x_entries[0] | ||
|
||
# Restore assistant expose settings | ||
expose_settings = exposed_entities.async_get_entity_settings( | ||
hass, switch_as_x_entry.entity_id | ||
) | ||
for assistant, settings in expose_settings.items(): | ||
if (should_expose := settings.get("should_expose")) is None: | ||
continue | ||
exposed_entities.async_expose_entity( | ||
hass, assistant, switch_entity_id, should_expose | ||
) |
This file contains 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,98 @@ | ||
"""Config flow for Cover Time-based integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
from collections.abc import Mapping | ||
from typing import Any | ||
|
||
import voluptuous as vol | ||
|
||
from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, Platform | ||
from homeassistant.components.cover import CoverDeviceClass | ||
from homeassistant.helpers import entity_registry as er, selector | ||
from homeassistant.helpers.schema_config_entry_flow import ( | ||
SchemaConfigFlowHandler, | ||
SchemaFlowFormStep, | ||
wrapped_entity_config_entry_title, | ||
) | ||
|
||
from .const import CONF_ENTITY_UP, CONF_ENTITY_DOWN, CONF_INVERT, CONF_TARGET_DEVICE_CLASS, CONF_TIME_OPEN, CONF_TIME_CLOSE, DOMAIN | ||
|
||
CONFIG_FLOW = { | ||
"user": SchemaFlowFormStep( | ||
vol.Schema( | ||
{ | ||
vol.Required(CONF_NAME): selector.TextSelector(), | ||
vol.Required(CONF_ENTITY_UP): selector.EntitySelector( | ||
selector.EntitySelectorConfig(domain=[Platform.SWITCH, Platform.LIGHT]) | ||
), | ||
vol.Required(CONF_ENTITY_DOWN): selector.EntitySelector( | ||
selector.EntitySelectorConfig(domain=[Platform.SWITCH, Platform.LIGHT]) | ||
), | ||
vol.Required(CONF_TIME_OPEN, default=25): selector.NumberSelector( | ||
selector.NumberSelectorConfig( | ||
mode=selector.NumberSelectorMode.BOX, min=2, | ||
max=120, | ||
step="any", | ||
unit_of_measurement="sec" | ||
) | ||
), | ||
vol.Optional(CONF_TIME_CLOSE): selector.NumberSelector( | ||
selector.NumberSelectorConfig( | ||
mode=selector.NumberSelectorMode.BOX, | ||
max=120, | ||
step="any", | ||
unit_of_measurement="sec" | ||
) | ||
), | ||
} | ||
) | ||
) | ||
} | ||
|
||
OPTIONS_FLOW = { | ||
"init": SchemaFlowFormStep( | ||
vol.Schema({ | ||
vol.Required(CONF_TIME_OPEN): selector.NumberSelector( | ||
selector.NumberSelectorConfig( | ||
mode=selector.NumberSelectorMode.BOX, min=2, | ||
max=120, | ||
step="any", | ||
unit_of_measurement="sec" | ||
) | ||
), | ||
vol.Optional(CONF_TIME_CLOSE): selector.NumberSelector( | ||
selector.NumberSelectorConfig( | ||
mode=selector.NumberSelectorMode.BOX, | ||
max=120, | ||
step="any", | ||
unit_of_measurement="sec" | ||
) | ||
), | ||
}) | ||
), | ||
} | ||
|
||
|
||
class CoverTimeBasedConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN): | ||
"""Handle a config flow for Cover Time-based.""" | ||
|
||
config_flow = CONFIG_FLOW | ||
options_flow = OPTIONS_FLOW | ||
|
||
VERSION = 1 | ||
MINOR_VERSION = 2 | ||
|
||
def async_config_entry_title(self, options: Mapping[str, Any]) -> str: | ||
"""Return config entry title and hide the wrapped entity if registered.""" | ||
# Hide the wrapped entry if registered | ||
registry = er.async_get(self.hass) | ||
|
||
for entity in [CONF_ENTITY_UP, CONF_ENTITY_DOWN]: | ||
entity_entry = registry.async_get(options[entity]) | ||
if entity_entry is not None and not entity_entry.hidden: | ||
registry.async_update_entity( | ||
options[entity], hidden_by=er.RegistryEntryHider.INTEGRATION | ||
) | ||
|
||
return options[CONF_NAME] |
This file contains 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,12 @@ | ||
"""Constants for the Cover Time-based integration.""" | ||
|
||
from typing import Final | ||
|
||
DOMAIN: Final = "cover_time_based" | ||
|
||
CONF_INVERT: Final = "invert" | ||
CONF_TARGET_DEVICE_CLASS: Final = "target_device" | ||
CONF_ENTITY_UP: Final = "up" | ||
CONF_ENTITY_DOWN: Final = "down" | ||
CONF_TIME_OPEN: Final = "time_open" | ||
CONF_TIME_CLOSE: Final = "time_close" |
Oops, something went wrong.