Skip to content

Commit

Permalink
Merge pull request #331 from xaviml/feat/support-light-group
Browse files Browse the repository at this point in the history
feat(entity_groups): add support for more Group Integrations
  • Loading branch information
xaviml authored Jul 5, 2021
2 parents cfc44d0 + a535e2c commit 69d80ea
Show file tree
Hide file tree
Showing 69 changed files with 339 additions and 291 deletions.
2 changes: 1 addition & 1 deletion .cz.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.commitizen]
name = "cz_conventional_commits"
version = "4.13.0"
version = "4.14.0b0"
tag_format = "v$major.$minor.$patch$prerelease"
version_files = [
"apps/controllerx/cx_version.py",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ livingroom_controller:
class: E1810Controller
controller: sensor.livingroom_controller_action
integration: z2m
light: light.bedroom
light: light.livingroom
```
## Documentation
Expand Down
14 changes: 8 additions & 6 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ _This minor change does not contain any breaking changes._
_Note: Remember to restart the AppDaemon addon/server after updating to a new version._
PRERELEASE_NOTE

<!--
## :pencil2: Features
-->

## :hammer: Fixes
## :pencil2: Features

- Clean action handle when there is an error. This will help for error logging.
- Support for Light Group integration. To know how entity groups work, read [here](https://xaviml.github.io/controllerx/advanced/entity-groups)

<!--
## :clock2: Performance
## :hammer: Fixes
-->

## :clock2: Performance
- Reduce calls to HA when entity is a group.

<!--
## :scroll: Docs
-->
Expand All @@ -26,6 +26,8 @@ PRERELEASE_NOTE
## :wrench: Refactor
-->

<!--
## :video_game: New devices
- [E1812](https://xaviml.github.io/controllerx/controllers/E1812) - add ZHA support [ #324 ]
-->
26 changes: 19 additions & 7 deletions apps/controllerx/cx_core/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@
Tuple,
TypeVar,
Union,
overload,
)

import appdaemon.utils as utils
import cx_version
from appdaemon.plugins.hass.hassapi import Hass # type: ignore
from appdaemon.plugins.mqtt.mqttapi import Mqtt # type: ignore
from appdaemon.plugins.hass.hassapi import Hass
from appdaemon.plugins.mqtt.mqttapi import Mqtt
from cx_const import (
ActionEvent,
ActionFunction,
Expand Down Expand Up @@ -85,6 +86,7 @@ class Controller(Hass, Mqtt):
This is the parent Controller, all controllers must extend from this class.
"""

args: Dict[str, Any]
integration: Integration
actions_mapping: ActionsMapping
action_handles: DefaultDict[ActionEvent, Optional["Future[None]"]]
Expand Down Expand Up @@ -222,7 +224,15 @@ def get_default_actions_mapping(
raise ValueError(f"This controller does not support {integration.name}.")
return actions_mapping

def get_list(self, entities: Union[List[T], T]) -> List[T]:
@overload
def get_list(self, entities: List[T]) -> List[T]:
...

@overload
def get_list(self, entities: T) -> List[T]:
...

def get_list(self, entities):
if isinstance(entities, (list, tuple)):
return list(entities)
return [entities]
Expand Down Expand Up @@ -307,7 +317,7 @@ async def call_service(self, service: str, **attributes) -> Optional[Any]:
value = f"{value:.2f}"
to_log.append(f" - {attribute}: {value}")
self.log("\n".join(to_log), level="INFO", ascii_encode=False)
return await Hass.call_service(self, service, **attributes) # type: ignore
return await Hass.call_service(self, service, **attributes)

@utils.sync_wrapper
async def get_state(
Expand All @@ -319,7 +329,9 @@ async def get_state(
**kwargs,
) -> Optional[Any]:
rendered_entity_id = await self.render_value(entity_id)
return await super().get_state(rendered_entity_id, attribute, default, copy, **kwargs) # type: ignore
return await super().get_state(
rendered_entity_id, attribute, default, copy, **kwargs
)

async def handle_action(
self, action_key: str, extra: Optional[EventData] = None
Expand Down Expand Up @@ -395,15 +407,15 @@ async def call_action(
if delay > 0:
handle = self.action_delay_handles[action_key]
if handle is not None:
await self.cancel_timer(handle) # type: ignore
await self.cancel_timer(handle)
self.log(
f"🕒 Running action(s) from `{action_key}` in {delay} seconds",
level="INFO",
ascii_encode=False,
)
new_handle = await self.run_in(
self.action_timer_callback, delay, action_key=action_key, extra=extra
) # type: ignore
)
self.action_delay_handles[action_key] = new_handle
else:
await self.action_timer_callback({"action_key": action_key, "extra": extra})
Expand Down
7 changes: 2 additions & 5 deletions apps/controllerx/cx_core/feature_support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,16 @@

class FeatureSupport:

entity_id: str
controller: "TypeController"
update_supported_features: bool
_supported_features: Optional[int]

def __init__(
self,
entity_id: str,
controller: "TypeController",
supported_features: Optional[int] = None,
update_supported_features=False,
) -> None:
self.entity_id = entity_id
self.controller = controller
self._supported_features = supported_features
self.update_supported_features = update_supported_features
Expand All @@ -27,13 +24,13 @@ def __init__(
async def supported_features(self) -> int:
if self._supported_features is None or self.update_supported_features:
bitfield: str = await self.controller.get_entity_state(
self.entity_id, attribute="supported_features"
attribute="supported_features"
)
if bitfield is not None:
self._supported_features = int(bitfield)
else:
raise ValueError(
f"`supported_features` could not be read from `{self.entity_id}`. Entity might not be available."
f"`supported_features` could not be read from `{self.controller.entity}`. Entity might not be available."
)
return self._supported_features

Expand Down
2 changes: 1 addition & 1 deletion apps/controllerx/cx_core/integration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def _all_integration_subclasses(
subclasses = set(cls_.__subclasses__()).union(
[s for c in cls_.__subclasses__() for s in _all_integration_subclasses(c)]
)
return list(subclasses) # type: ignore
return list(subclasses)


def get_integrations(controller, kwargs) -> List[Integration]:
Expand Down
2 changes: 1 addition & 1 deletion apps/controllerx/cx_core/integration/lutron_caseta.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional

from appdaemon.plugins.hass.hassapi import Hass # type: ignore
from appdaemon.plugins.hass.hassapi import Hass
from cx_const import DefaultActionsMapping
from cx_core.integration import EventData, Integration

Expand Down
2 changes: 1 addition & 1 deletion apps/controllerx/cx_core/integration/mqtt.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional

from appdaemon.plugins.mqtt.mqttapi import Mqtt # type: ignore
from appdaemon.plugins.mqtt.mqttapi import Mqtt
from cx_const import DefaultActionsMapping
from cx_core.integration import EventData, Integration

Expand Down
2 changes: 1 addition & 1 deletion apps/controllerx/cx_core/integration/state.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional

from appdaemon.plugins.hass.hassapi import Hass # type: ignore
from appdaemon.plugins.hass.hassapi import Hass
from cx_const import DefaultActionsMapping
from cx_core.integration import Integration

Expand Down
4 changes: 2 additions & 2 deletions apps/controllerx/cx_core/integration/z2m.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import json
from typing import Optional

from appdaemon.plugins.hass.hassapi import Hass # type: ignore
from appdaemon.plugins.mqtt.mqttapi import Mqtt # type: ignore
from appdaemon.plugins.hass.hassapi import Hass
from appdaemon.plugins.mqtt.mqttapi import Mqtt
from cx_const import DefaultActionsMapping
from cx_core.integration import EventData, Integration

Expand Down
2 changes: 1 addition & 1 deletion apps/controllerx/cx_core/integration/zha.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional

from appdaemon.plugins.hass.hassapi import Hass # type: ignore
from appdaemon.plugins.hass.hassapi import Hass
from cx_const import DefaultActionsMapping
from cx_core.integration import EventData, Integration

Expand Down
6 changes: 3 additions & 3 deletions apps/controllerx/cx_core/type/cover_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ async def open(self) -> None:
await self.call_service("cover/open_cover", entity_id=self.entity.name)
else:
self.log(
f"⚠️ `{self.entity.name}` does not support SET_COVER_POSITION or OPEN",
f"⚠️ `{self.entity}` does not support SET_COVER_POSITION or OPEN",
level="WARNING",
ascii_encode=False,
)
Expand All @@ -73,7 +73,7 @@ async def close(self) -> None:
await self.call_service("cover/close_cover", entity_id=self.entity.name)
else:
self.log(
f"⚠️ `{self.entity.name}` does not support SET_COVER_POSITION or CLOSE",
f"⚠️ `{self.entity}` does not support SET_COVER_POSITION or CLOSE",
level="WARNING",
ascii_encode=False,
)
Expand All @@ -84,7 +84,7 @@ async def stop(self) -> None:

@action
async def toggle(self, action: Callable) -> None:
cover_state = await self.get_entity_state(self.entity.name)
cover_state = await self.get_entity_state()
if cover_state == "opening" or cover_state == "closing":
await self.stop()
else:
Expand Down
17 changes: 11 additions & 6 deletions apps/controllerx/cx_core/type/light_controller.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Dict, Optional, Type, Union
from typing import Any, Dict, List, Optional, Type, Union

from cx_const import Light, PredefinedActionsMapping
from cx_core.color_helper import get_color_wheel
Expand Down Expand Up @@ -34,8 +34,13 @@
class LightEntity(Entity):
color_mode: ColorMode

def __init__(self, name: str, color_mode: ColorMode = "auto") -> None:
super().__init__(name)
def __init__(
self,
name: str,
entities: Optional[List[str]] = None,
color_mode: ColorMode = "auto",
) -> None:
super().__init__(name, entities)
self.color_mode = color_mode


Expand Down Expand Up @@ -532,11 +537,11 @@ async def get_value_attribute(self, attribute: str) -> Union[float, int]:
or attribute == LightController.ATTRIBUTE_WHITE_VALUE
or attribute == LightController.ATTRIBUTE_COLOR_TEMP
):
value = await self.get_entity_state(self.entity.name, attribute)
value = await self.get_entity_state(attribute=attribute)
if value is None:
raise ValueError(
f"Value for `{attribute}` attribute could not be retrieved "
f"from `{self.entity.name}`. "
f"from `{self.entity.main}`. "
"Check the FAQ to know more about this error: "
"https://xaviml.github.io/controllerx/faq"
)
Expand Down Expand Up @@ -565,7 +570,7 @@ async def before_action(self, action: str, *args, **kwargs) -> bool:
to_return = True
if action in ("click", "hold"):
attribute, direction = args
light_state: str = await self.get_entity_state(self.entity.name)
light_state: str = await self.get_entity_state()
self.smooth_power_on_check = self.check_smooth_power_on(
attribute, direction, light_state
)
Expand Down
8 changes: 3 additions & 5 deletions apps/controllerx/cx_core/type/media_player_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ def get_predefined_actions_mapping(self) -> PredefinedActionsMapping:

@action
async def change_source_list(self, direction: str) -> None:
entity_states = await self.get_entity_state(self.entity.name, attribute="all")
entity_states = await self.get_entity_state(attribute="all")
entity_attributes = entity_states["attributes"]
source_list = entity_attributes.get("source_list")
if len(source_list) == 0 or source_list is None:
self.log(
f"⚠️ There is no `source_list` parameter in `{self.entity.name}`",
f"⚠️ There is no `source_list` parameter in `{self.entity}`",
level="WARNING",
ascii_encode=False,
)
Expand Down Expand Up @@ -114,9 +114,7 @@ async def hold(self, direction: str) -> None: # type: ignore
await super().hold(direction)

async def prepare_volume_change(self) -> None:
volume_level = await self.get_entity_state(
self.entity.name, attribute="volume_level"
)
volume_level = await self.get_entity_state(attribute="volume_level")
if volume_level is not None:
self.volume_level = volume_level

Expand Down
Loading

0 comments on commit 69d80ea

Please sign in to comment.