chore: untrack vendor code, remove powerocean leftover
- voice_satellite/ — Wake-Word ML-Modell (extern trainiert, von TaterTotterson), nicht eigener Config; via .gitignore ausgenommen. - appdaemon/apps/controllerx/ — ControllerX v5.2.3 wird via HACS-AppDaemon gemanagt (xaviml/controllerx); via .gitignore ausgenommen. apps.yaml bleibt versioniert (eigene Mapping-Config). - powerocean_powerpulse/ — alte Version (2025.01.07) der niltrip/powerocean Komponente, lag im falschen Pfad (/config/ statt custom_components/), wurde nie geladen. Aktive Version ist custom_components/powerocean_dev/ (2026.05.01). Komplett entfernt.
This commit is contained in:
@@ -1,39 +0,0 @@
|
||||
"""
|
||||
Create controller-based automations with ease to control your home devices and scenes.
|
||||
|
||||
https://github.com/xaviml/controllerx
|
||||
"""
|
||||
|
||||
from cx_core import (
|
||||
Controller,
|
||||
CoverController,
|
||||
LightController,
|
||||
MediaPlayerController,
|
||||
SwitchController,
|
||||
Z2MLightController,
|
||||
)
|
||||
from cx_devices.adeo import *
|
||||
from cx_devices.aqara import *
|
||||
from cx_devices.aurora import *
|
||||
from cx_devices.homematic import *
|
||||
from cx_devices.ikea import *
|
||||
from cx_devices.legrand import *
|
||||
from cx_devices.linkind import *
|
||||
from cx_devices.livarno import *
|
||||
from cx_devices.lutron import *
|
||||
from cx_devices.muller_licht import *
|
||||
from cx_devices.namron import *
|
||||
from cx_devices.osram import *
|
||||
from cx_devices.philips import *
|
||||
from cx_devices.prolight import *
|
||||
from cx_devices.rgb_genie import *
|
||||
from cx_devices.robb import *
|
||||
from cx_devices.sengled import *
|
||||
from cx_devices.shelly import *
|
||||
from cx_devices.smartkontakten import *
|
||||
from cx_devices.smartthings import *
|
||||
from cx_devices.sonoff import *
|
||||
from cx_devices.tasmota import *
|
||||
from cx_devices.terncy import *
|
||||
from cx_devices.trust import *
|
||||
from cx_devices.tuya import *
|
||||
@@ -1,150 +0,0 @@
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
ActionFunction = Callable[..., Awaitable[Any]]
|
||||
ActionParams = tuple[Any, ...]
|
||||
ActionFunctionWithParams = tuple[ActionFunction, ActionParams]
|
||||
TypeAction = Union[ActionFunction, ActionFunctionWithParams]
|
||||
ActionEvent = Union[str, int]
|
||||
PredefinedActionsMapping = dict[str, TypeAction]
|
||||
DefaultActionsMapping = dict[ActionEvent, Optional[str]]
|
||||
|
||||
CustomAction = Union[str, dict[str, Any]]
|
||||
CustomActions = Union[list[CustomAction], CustomAction]
|
||||
CustomActionsMapping = dict[ActionEvent, Optional[CustomActions]]
|
||||
|
||||
Number = Union[int, float]
|
||||
|
||||
|
||||
class Light:
|
||||
ON = "on"
|
||||
OFF = "off"
|
||||
TOGGLE = "toggle"
|
||||
TOGGLE_FULL_BRIGHTNESS = "toggle_full_brightness"
|
||||
TOGGLE_FULL_WHITE_VALUE = "toggle_full_white_value"
|
||||
TOGGLE_FULL_COLOR_TEMP = "toggle_full_color_temp"
|
||||
TOGGLE_MIN_BRIGHTNESS = "toggle_min_brightness"
|
||||
TOGGLE_MIN_WHITE_VALUE = "toggle_min_white_value"
|
||||
TOGGLE_MIN_COLOR_TEMP = "toggle_min_color_temp"
|
||||
RELEASE = "release"
|
||||
ON_FULL_BRIGHTNESS = "on_full_brightness"
|
||||
ON_FULL_WHITE_VALUE = "on_full_white_value"
|
||||
ON_FULL_COLOR_TEMP = "on_full_color_temp"
|
||||
ON_MIN_BRIGHTNESS = "on_min_brightness"
|
||||
ON_MIN_WHITE_VALUE = "on_min_white_value"
|
||||
ON_MIN_COLOR_TEMP = "on_min_color_temp"
|
||||
ON_MIN_MAX_BRIGHTNESS = "on_min_max_brightness"
|
||||
ON_MAX_MIN_BRIGHTNESS = "on_max_min_brightness"
|
||||
ON_MIN_MAX_COLOR_TEMP = "on_min_max_color_temp"
|
||||
ON_MAX_MIN_COLOR_TEMP = "on_max_min_color_temp"
|
||||
SET_HALF_BRIGHTNESS = "set_half_brightness"
|
||||
SET_HALF_WHITE_VALUE = "set_half_white_value"
|
||||
SET_HALF_COLOR_TEMP = "set_half_color_temp"
|
||||
SYNC = "sync"
|
||||
CLICK = "click"
|
||||
CLICK_BRIGHTNESS_UP = "click_brightness_up"
|
||||
CLICK_BRIGHTNESS_DOWN = "click_brightness_down"
|
||||
CLICK_WHITE_VALUE_UP = "click_white_value_up"
|
||||
CLICK_WHITE_VALUE_DOWN = "click_white_value_down"
|
||||
CLICK_COLOR_UP = "click_color_up"
|
||||
CLICK_COLOR_DOWN = "click_color_down"
|
||||
CLICK_COLOR_TEMP_UP = "click_colortemp_up"
|
||||
CLICK_COLOR_TEMP_DOWN = "click_colortemp_down"
|
||||
CLICK_XY_COLOR_UP = "click_xycolor_up"
|
||||
CLICK_XY_COLOR_DOWN = "click_xycolor_down"
|
||||
HOLD = "hold"
|
||||
HOLD_BRIGHTNESS_UP = "hold_brightness_up"
|
||||
HOLD_BRIGHTNESS_DOWN = "hold_brightness_down"
|
||||
HOLD_BRIGHTNESS_TOGGLE = "hold_brightness_toggle"
|
||||
HOLD_WHITE_VALUE_UP = "hold_white_value_up"
|
||||
HOLD_WHITE_VALUE_DOWN = "hold_white_value_down"
|
||||
HOLD_WHITE_VALUE_TOGGLE = "hold_white_value_toggle"
|
||||
HOLD_COLOR_UP = "hold_color_up"
|
||||
HOLD_COLOR_DOWN = "hold_color_down"
|
||||
HOLD_COLOR_TOGGLE = "hold_color_toggle"
|
||||
HOLD_COLOR_TEMP_UP = "hold_colortemp_up"
|
||||
HOLD_COLOR_TEMP_DOWN = "hold_colortemp_down"
|
||||
HOLD_COLOR_TEMP_TOGGLE = "hold_colortemp_toggle"
|
||||
HOLD_XY_COLOR_UP = "hold_xycolor_up"
|
||||
HOLD_XY_COLOR_DOWN = "hold_xycolor_down"
|
||||
HOLD_XY_COLOR_TOGGLE = "hold_xycolor_toggle"
|
||||
XYCOLOR_FROM_CONTROLLER = "xycolor_from_controller"
|
||||
COLORTEMP_FROM_CONTROLLER = "colortemp_from_controller"
|
||||
COLORTEMP_FROM_CONTROLLER_STEP = "colortemp_from_controller_step"
|
||||
BRIGHTNESS_FROM_CONTROLLER_LEVEL = "brightness_from_controller_level"
|
||||
BRIGHTNESS_FROM_CONTROLLER_ANGLE = "brightness_from_controller_angle"
|
||||
BRIGHTNESS_FROM_CONTROLLER_STEP = "brightness_from_controller_step"
|
||||
|
||||
|
||||
class Z2MLight:
|
||||
ON = "on"
|
||||
OFF = "off"
|
||||
TOGGLE = "toggle"
|
||||
RELEASE = "release"
|
||||
ON_FULL_BRIGHTNESS = "on_full_brightness"
|
||||
ON_FULL_COLOR_TEMP = "on_full_color_temp"
|
||||
ON_MIN_BRIGHTNESS = "on_min_brightness"
|
||||
ON_MIN_COLOR_TEMP = "on_min_color_temp"
|
||||
SET_HALF_BRIGHTNESS = "set_half_brightness"
|
||||
SET_HALF_COLOR_TEMP = "set_half_color_temp"
|
||||
CLICK = "click"
|
||||
CLICK_BRIGHTNESS_UP = "click_brightness_up"
|
||||
CLICK_BRIGHTNESS_DOWN = "click_brightness_down"
|
||||
CLICK_COLOR_TEMP_UP = "click_colortemp_up"
|
||||
CLICK_COLOR_TEMP_DOWN = "click_colortemp_down"
|
||||
HOLD = "hold"
|
||||
HOLD_BRIGHTNESS_UP = "hold_brightness_up"
|
||||
HOLD_BRIGHTNESS_DOWN = "hold_brightness_down"
|
||||
HOLD_BRIGHTNESS_TOGGLE = "hold_brightness_toggle"
|
||||
HOLD_COLOR_TEMP_UP = "hold_colortemp_up"
|
||||
HOLD_COLOR_TEMP_DOWN = "hold_colortemp_down"
|
||||
HOLD_COLOR_TEMP_TOGGLE = "hold_colortemp_toggle"
|
||||
XYCOLOR_FROM_CONTROLLER = "xycolor_from_controller"
|
||||
COLORTEMP_FROM_CONTROLLER = "colortemp_from_controller"
|
||||
BRIGHTNESS_FROM_CONTROLLER_LEVEL = "brightness_from_controller_level"
|
||||
BRIGHTNESS_FROM_CONTROLLER_ANGLE = "brightness_from_controller_angle"
|
||||
|
||||
|
||||
class MediaPlayer:
|
||||
HOLD_VOLUME_DOWN = "hold_volume_down"
|
||||
HOLD_VOLUME_UP = "hold_volume_up"
|
||||
CLICK_VOLUME_DOWN = "click_volume_down"
|
||||
CLICK_VOLUME_UP = "click_volume_up"
|
||||
VOLUME_SET = "volume_set"
|
||||
RELEASE = "release"
|
||||
PLAY = "play"
|
||||
PAUSE = "pause"
|
||||
PLAY_PAUSE = "play_pause"
|
||||
NEXT_TRACK = "next_track"
|
||||
PREVIOUS_TRACK = "previous_track"
|
||||
NEXT_SOURCE = "next_source"
|
||||
PREVIOUS_SOURCE = "previous_source"
|
||||
MUTE = "mute"
|
||||
TTS = "tts"
|
||||
VOLUME_FROM_CONTROLLER_ANGLE = "volume_from_controller_angle"
|
||||
|
||||
|
||||
class Switch:
|
||||
ON = "on"
|
||||
OFF = "off"
|
||||
TOGGLE = "toggle"
|
||||
|
||||
|
||||
class Cover:
|
||||
OPEN = "open"
|
||||
CLOSE = "close"
|
||||
STOP = "stop"
|
||||
TOGGLE_OPEN = "toggle_open"
|
||||
TOGGLE_CLOSE = "toggle_close"
|
||||
|
||||
|
||||
class StepperDir:
|
||||
UP = "up"
|
||||
DOWN = "down"
|
||||
TOGGLE = "toggle"
|
||||
|
||||
|
||||
class StepperMode:
|
||||
STOP = "stop"
|
||||
LOOP = "loop"
|
||||
BOUNCE = "bounce"
|
||||
@@ -1,18 +0,0 @@
|
||||
from cx_core.controller import Controller, action
|
||||
from cx_core.release_hold_controller import ReleaseHoldController
|
||||
from cx_core.type.cover_controller import CoverController
|
||||
from cx_core.type.light_controller import LightController
|
||||
from cx_core.type.media_player_controller import MediaPlayerController
|
||||
from cx_core.type.switch_controller import SwitchController
|
||||
from cx_core.type.z2m_light_controller import Z2MLightController
|
||||
|
||||
__all__ = [
|
||||
"Controller",
|
||||
"ReleaseHoldController",
|
||||
"LightController",
|
||||
"Z2MLightController",
|
||||
"MediaPlayerController",
|
||||
"SwitchController",
|
||||
"CoverController",
|
||||
"action",
|
||||
]
|
||||
@@ -1,46 +0,0 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from cx_const import ActionEvent, CustomAction, CustomActions
|
||||
from cx_core.action_type.base import ActionType
|
||||
from cx_core.action_type.call_service_action_type import CallServiceActionType
|
||||
from cx_core.action_type.delay_action_type import DelayActionType
|
||||
from cx_core.action_type.predefined_action_type import PredefinedActionType
|
||||
from cx_core.action_type.scene_action_type import SceneActionType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cx_core import Controller
|
||||
|
||||
ActionsMapping = dict[ActionEvent, list[ActionType]]
|
||||
|
||||
action_type_mapping: dict[str, type[ActionType]] = {
|
||||
"action": PredefinedActionType,
|
||||
"service": CallServiceActionType,
|
||||
"scene": SceneActionType,
|
||||
"delay": DelayActionType,
|
||||
}
|
||||
|
||||
|
||||
def parse_actions(controller: "Controller", data: CustomActions) -> list[ActionType]:
|
||||
actions: CustomActions
|
||||
if isinstance(data, (list, tuple)):
|
||||
actions = list(data)
|
||||
else:
|
||||
actions = [data]
|
||||
|
||||
return [_parse_action(controller, action) for action in actions]
|
||||
|
||||
|
||||
def _parse_action(controller: "Controller", action: CustomAction) -> ActionType:
|
||||
if isinstance(action, str):
|
||||
return PredefinedActionType(controller, {"action": action})
|
||||
try:
|
||||
return next(
|
||||
action_type(controller, action)
|
||||
for key in action
|
||||
for action_type_key, action_type in action_type_mapping.items()
|
||||
if key == action_type_key
|
||||
)
|
||||
except StopIteration:
|
||||
raise ValueError(
|
||||
f"Not able to parse `{action}`. Available keys are: {list(action_type_mapping.keys())}"
|
||||
)
|
||||
@@ -1,25 +0,0 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from cx_core.integration import EventData
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cx_core import Controller
|
||||
|
||||
|
||||
class ActionType(ABC):
|
||||
controller: "Controller"
|
||||
|
||||
def __init__(self, controller: "Controller", action: dict[str, Any]) -> None:
|
||||
self.controller = controller
|
||||
self.initialize(**action)
|
||||
|
||||
def initialize(self, **kwargs: Any) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def run(self, extra: EventData | None = None) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.__class__.__name__}"
|
||||
@@ -1,52 +0,0 @@
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from cx_core.action_type.base import ActionType
|
||||
from cx_core.integration import EventData
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cx_core.type_controller import Entity, TypeController
|
||||
|
||||
|
||||
class CallServiceActionType(ActionType):
|
||||
service: str
|
||||
# Priority order for entity_id:
|
||||
# - Inside data
|
||||
# - In the same level as "service"
|
||||
# - From the main config if the domain matches
|
||||
entity_id: str | None
|
||||
data: dict[str, Any]
|
||||
|
||||
def initialize(self, **kwargs: Any) -> None:
|
||||
self.service = kwargs["service"]
|
||||
self.data = kwargs.get("data", {})
|
||||
|
||||
self.entity_id = self.data.get("entity_id") or kwargs.get("entity_id")
|
||||
if (
|
||||
self.entity_id is None
|
||||
and self._check_controller_isinstance_type_controller()
|
||||
):
|
||||
type_controller = cast("TypeController[Entity]", self.controller)
|
||||
if self._get_service_domain(self.service) in type_controller.domains:
|
||||
self.entity_id = type_controller.entity.name
|
||||
if "entity_id" in self.data:
|
||||
del self.data["entity_id"]
|
||||
|
||||
def _check_controller_isinstance_type_controller(self) -> bool:
|
||||
# This is checked dynamically without the isinstance to avoid
|
||||
# circular dependency
|
||||
class_names = [c.__name__ for c in type(self.controller).mro()]
|
||||
return "TypeController" in class_names
|
||||
|
||||
def _get_service_domain(self, service: str) -> str:
|
||||
return service.replace(".", "/").split("/")[0]
|
||||
|
||||
async def run(self, extra: EventData | None = None) -> None:
|
||||
if self.entity_id:
|
||||
await self.controller.call_service(
|
||||
self.service, entity_id=self.entity_id, **self.data
|
||||
)
|
||||
else:
|
||||
await self.controller.call_service(self.service, **self.data)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Service ({self.service})"
|
||||
@@ -1,17 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
from cx_core.action_type.base import ActionType
|
||||
from cx_core.integration import EventData
|
||||
|
||||
|
||||
class DelayActionType(ActionType):
|
||||
delay: int
|
||||
|
||||
def initialize(self, **kwargs: Any) -> None:
|
||||
self.delay = kwargs["delay"]
|
||||
|
||||
async def run(self, extra: EventData | None = None) -> None:
|
||||
await self.controller.sleep(self.delay)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Delay ({self.delay} seconds)"
|
||||
@@ -1,121 +0,0 @@
|
||||
import inspect
|
||||
from typing import Any
|
||||
|
||||
from cx_const import (
|
||||
ActionFunction,
|
||||
ActionFunctionWithParams,
|
||||
ActionParams,
|
||||
PredefinedActionsMapping,
|
||||
TypeAction,
|
||||
)
|
||||
from cx_core.action_type.base import ActionType
|
||||
from cx_core.integration import EventData
|
||||
|
||||
|
||||
def _get_action(action_value: TypeAction) -> ActionFunctionWithParams:
|
||||
if isinstance(action_value, tuple):
|
||||
return action_value
|
||||
else:
|
||||
return (action_value, tuple())
|
||||
|
||||
|
||||
def _get_arguments(
|
||||
action: ActionFunction,
|
||||
args: ActionParams,
|
||||
predefined_action_kwargs: dict[str, Any],
|
||||
extra: EventData | None,
|
||||
) -> tuple[ActionParams, dict[str, Any]]:
|
||||
action_parameters = inspect.signature(action).parameters
|
||||
action_parameters_without_extra = {
|
||||
key: param for key, param in action_parameters.items() if key != "extra"
|
||||
}
|
||||
action_parameters_without_default = {
|
||||
key: param
|
||||
for key, param in action_parameters.items()
|
||||
if param.default is inspect.Signature.empty
|
||||
}
|
||||
action_args: dict[str, Any] = dict(
|
||||
zip(action_parameters_without_extra.keys(), args)
|
||||
) # ControllerX args
|
||||
action_positional_args = set(action_args.keys())
|
||||
action_args.update(predefined_action_kwargs) # User args
|
||||
action_args.update({"extra": extra} if "extra" in action_parameters else {})
|
||||
action_args = {
|
||||
key: value for key, value in action_args.items() if key in action_parameters
|
||||
}
|
||||
|
||||
if len(set(action_parameters_without_default).difference(action_args)) != 0:
|
||||
error_msg = [
|
||||
f"`{action.__name__}` action is missing some parameters. Parameters available:"
|
||||
]
|
||||
for key, param in action_parameters_without_extra.items():
|
||||
if hasattr(param.annotation, "__name__"):
|
||||
attr_msg = f" {key}: {param.annotation.__name__}"
|
||||
else:
|
||||
attr_msg = f" {key}:"
|
||||
if param.default is not inspect.Signature.empty:
|
||||
attr_msg += f" [default: {param.default}]"
|
||||
if key in action_args:
|
||||
attr_msg += f" (value given: {action_args[key]})"
|
||||
elif param.default is inspect.Signature.empty:
|
||||
attr_msg += " (missing)"
|
||||
error_msg.append(attr_msg)
|
||||
raise ValueError("\n".join(error_msg))
|
||||
|
||||
positional = tuple(
|
||||
value for key, value in action_args.items() if key in action_positional_args
|
||||
)
|
||||
action_args = {
|
||||
key: value
|
||||
for key, value in action_args.items()
|
||||
if key not in action_positional_args
|
||||
}
|
||||
return positional, action_args
|
||||
|
||||
|
||||
class PredefinedActionType(ActionType):
|
||||
predefined_action_key: str
|
||||
predefined_action_kwargs: dict[str, Any]
|
||||
predefined_actions_mapping: PredefinedActionsMapping
|
||||
|
||||
def _raise_action_key_not_found(
|
||||
self, predefined_action_key: str, predefined_actions: PredefinedActionsMapping
|
||||
) -> None:
|
||||
raise ValueError(
|
||||
f"`{predefined_action_key}` is not one of the predefined actions. "
|
||||
f"Available actions are: {list(predefined_actions.keys())}."
|
||||
"See more in: https://xaviml.github.io/controllerx/advanced/custom-controllers"
|
||||
)
|
||||
|
||||
def initialize(self, **kwargs: Any) -> None:
|
||||
self.predefined_action_key = kwargs.pop("action")
|
||||
self.predefined_action_kwargs = kwargs
|
||||
self.predefined_actions_mapping = (
|
||||
self.controller.get_predefined_actions_mapping()
|
||||
)
|
||||
if not self.predefined_actions_mapping:
|
||||
raise ValueError(
|
||||
f"Cannot use predefined actions for `{self.controller.__class__.__name__}` class."
|
||||
)
|
||||
if (
|
||||
not self.controller.contains_templating(self.predefined_action_key)
|
||||
and self.predefined_action_key not in self.predefined_actions_mapping
|
||||
):
|
||||
self._raise_action_key_not_found(
|
||||
self.predefined_action_key, self.predefined_actions_mapping
|
||||
)
|
||||
|
||||
async def run(self, extra: EventData | None = None) -> None:
|
||||
action_key = await self.controller.render_value(self.predefined_action_key)
|
||||
if action_key not in self.predefined_actions_mapping:
|
||||
self._raise_action_key_not_found(
|
||||
action_key, self.predefined_actions_mapping
|
||||
)
|
||||
action, args = _get_action(self.predefined_actions_mapping[action_key])
|
||||
positional, action_args = _get_arguments(
|
||||
action, args, self.predefined_action_kwargs, extra
|
||||
)
|
||||
await action(*positional, **action_args)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Predefined ({self.predefined_action_key})"
|
||||
@@ -1,17 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
from cx_core.action_type.base import ActionType
|
||||
from cx_core.integration import EventData
|
||||
|
||||
|
||||
class SceneActionType(ActionType):
|
||||
scene: str
|
||||
|
||||
def initialize(self, **kwargs: Any) -> None:
|
||||
self.scene = kwargs["scene"]
|
||||
|
||||
async def run(self, extra: EventData | None = None) -> None:
|
||||
await self.controller.call_service("scene/turn_on", entity_id=self.scene)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Scene ({self.scene})"
|
||||
@@ -1,81 +0,0 @@
|
||||
Color = tuple[float, float]
|
||||
Colors = list[Color]
|
||||
|
||||
# These are the 24 colors that appear in the circle color of home assistant
|
||||
default_color_wheel = [
|
||||
(0.701, 0.299),
|
||||
(0.667, 0.284),
|
||||
(0.581, 0.245),
|
||||
(0.477, 0.196),
|
||||
(0.385, 0.155),
|
||||
(0.301, 0.116),
|
||||
(0.217, 0.077),
|
||||
(0.157, 0.05),
|
||||
(0.136, 0.04),
|
||||
(0.137, 0.065),
|
||||
(0.141, 0.137),
|
||||
(0.146, 0.238),
|
||||
(0.323, 0.329), # 12; white color middle
|
||||
(0.151, 0.343),
|
||||
(0.157, 0.457),
|
||||
(0.164, 0.591),
|
||||
(0.17, 0.703),
|
||||
(0.172, 0.747),
|
||||
(0.199, 0.724),
|
||||
(0.269, 0.665),
|
||||
(0.36, 0.588),
|
||||
(0.444, 0.517),
|
||||
(0.527, 0.447),
|
||||
(0.612, 0.374),
|
||||
(0.677, 0.319),
|
||||
]
|
||||
|
||||
# These are the xy colors translated from color temperature (2000K to 6488K)
|
||||
# They were extracted from here https://www.waveformlighting.com/files/blackBodyLocus_1.txt
|
||||
color_temp_wheel = [
|
||||
(0.527, 0.413),
|
||||
(0.507, 0.415),
|
||||
(0.489, 0.415),
|
||||
(0.472, 0.413),
|
||||
(0.456, 0.41),
|
||||
(0.442, 0.406),
|
||||
(0.428, 0.401),
|
||||
(0.416, 0.396),
|
||||
(0.406, 0.391),
|
||||
(0.396, 0.386),
|
||||
(0.386, 0.38),
|
||||
(0.378, 0.375),
|
||||
(0.37, 0.37),
|
||||
(0.363, 0.365),
|
||||
(0.357, 0.361),
|
||||
(0.351, 0.358),
|
||||
(0.345, 0.355),
|
||||
(0.34, 0.352),
|
||||
(0.336, 0.349),
|
||||
(0.331, 0.346),
|
||||
(0.327, 0.343),
|
||||
(0.323, 0.339),
|
||||
(0.32, 0.336),
|
||||
(0.317, 0.333),
|
||||
(0.314, 0.33),
|
||||
]
|
||||
|
||||
COLOR_WHEELS = {
|
||||
"default_color_wheel": default_color_wheel,
|
||||
"color_temp_wheel": color_temp_wheel,
|
||||
}
|
||||
|
||||
|
||||
def get_color_wheel(colors: str | Colors) -> Colors:
|
||||
if isinstance(colors, str):
|
||||
if colors not in COLOR_WHEELS:
|
||||
raise ValueError(
|
||||
f"`{colors}` is not an option for `color_wheel`. Options are: {list(COLOR_WHEELS.keys())}"
|
||||
)
|
||||
return COLOR_WHEELS[colors]
|
||||
elif isinstance(colors, (list, tuple)):
|
||||
return colors
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Type {type(colors)} is not supported for `color_wheel` attribute"
|
||||
)
|
||||
@@ -1,611 +0,0 @@
|
||||
import asyncio
|
||||
import re
|
||||
import time
|
||||
from ast import literal_eval
|
||||
from asyncio import CancelledError, Task
|
||||
from collections import Counter, defaultdict
|
||||
from collections.abc import Awaitable, Callable
|
||||
from functools import wraps
|
||||
from typing import (
|
||||
Any,
|
||||
DefaultDict,
|
||||
Literal,
|
||||
Optional,
|
||||
TypeVar,
|
||||
overload,
|
||||
)
|
||||
|
||||
import appdaemon.utils as utils
|
||||
import cx_core.integration as integration_module
|
||||
import cx_version
|
||||
from appdaemon.adapi import ADAPI
|
||||
from appdaemon.plugins.hass.hassapi import Hass
|
||||
from appdaemon.plugins.mqtt.mqttapi import Mqtt
|
||||
from cx_const import (
|
||||
ActionEvent,
|
||||
ActionFunction,
|
||||
CustomActionsMapping,
|
||||
DefaultActionsMapping,
|
||||
PredefinedActionsMapping,
|
||||
)
|
||||
from cx_core.action_type import ActionsMapping, parse_actions
|
||||
from cx_core.action_type.base import ActionType
|
||||
from cx_core.integration import EventData, Integration
|
||||
|
||||
DEFAULT_ACTION_DELTA = 300 # In milliseconds
|
||||
DEFAULT_MULTIPLE_CLICK_DELAY = 500 # In milliseconds
|
||||
MULTIPLE_CLICK_TOKEN = "$"
|
||||
|
||||
MODE_SINGLE = "single"
|
||||
MODE_RESTART = "restart"
|
||||
MODE_QUEUED = "queued"
|
||||
MODE_PARALLEL = "parallel"
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def action(method: Callable[..., Awaitable[Any]]) -> ActionFunction:
|
||||
@wraps(method)
|
||||
async def _action_impl(controller: "Controller", *args: Any, **kwargs: Any) -> None:
|
||||
continue_call = await controller.before_action(method.__name__, *args, **kwargs)
|
||||
if continue_call:
|
||||
await method(controller, *args, **kwargs)
|
||||
|
||||
return _action_impl
|
||||
|
||||
|
||||
def run_in(fn: Callable[..., Any], delay: float, **kwargs: Any) -> "Task[None]":
|
||||
"""
|
||||
It runs the function (fn) in running event loop in `delay` seconds.
|
||||
This function has been created because the default run_in function
|
||||
from AppDaemon does not accept microseconds.
|
||||
"""
|
||||
|
||||
async def inner() -> None:
|
||||
await asyncio.sleep(delay)
|
||||
await fn(kwargs)
|
||||
|
||||
task = asyncio.create_task(inner())
|
||||
return task
|
||||
|
||||
|
||||
class Controller(Hass, Mqtt): # type: ignore[misc]
|
||||
"""
|
||||
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["Task[None]"]]
|
||||
action_delay_handles: dict[ActionEvent, str | None]
|
||||
multiple_click_actions: set[ActionEvent]
|
||||
action_delay: dict[ActionEvent, int]
|
||||
action_delta: dict[ActionEvent, int]
|
||||
action_times: dict[str, float]
|
||||
previous_states: dict[ActionEvent, str | None]
|
||||
multiple_click_action_times: dict[str, float]
|
||||
click_counter: Counter[ActionEvent]
|
||||
multiple_click_action_delay_tasks: DefaultDict[ActionEvent, Optional["Task[None]"]]
|
||||
multiple_click_delay: int
|
||||
|
||||
async def initialize(self) -> None:
|
||||
self.log(f"🎮 ControllerX {cx_version.__version__}", ascii_encode=False)
|
||||
await self.init()
|
||||
|
||||
async def init(self) -> None:
|
||||
controllers_ids: list[str] = self.get_list(self.args["controller"])
|
||||
self.integration = self.get_integration(self.args["integration"])
|
||||
|
||||
if "mapping" in self.args and "merge_mapping" in self.args:
|
||||
raise ValueError("`mapping` and `merge_mapping` cannot be used together")
|
||||
|
||||
custom_mapping: CustomActionsMapping | None = self.args.get("mapping", None)
|
||||
merge_mapping: CustomActionsMapping | None = self.args.get(
|
||||
"merge_mapping", None
|
||||
)
|
||||
|
||||
if custom_mapping is None:
|
||||
default_actions_mapping = self.get_default_actions_mapping(self.integration)
|
||||
self.actions_mapping = self.parse_action_mapping(default_actions_mapping) # type: ignore[arg-type]
|
||||
else:
|
||||
self.actions_mapping = self.parse_action_mapping(custom_mapping)
|
||||
|
||||
if merge_mapping is not None:
|
||||
self.actions_mapping.update(self.parse_action_mapping(merge_mapping))
|
||||
|
||||
# Filter actions with include and exclude
|
||||
if "actions" in self.args and "excluded_actions" in self.args:
|
||||
raise ValueError("`actions` and `excluded_actions` cannot be used together")
|
||||
include: list[ActionEvent] = self.get_list(
|
||||
self.args.get("actions", list(self.actions_mapping.keys()))
|
||||
)
|
||||
exclude: list[ActionEvent] = self.get_list(
|
||||
self.args.get("excluded_actions", [])
|
||||
)
|
||||
self.actions_mapping = self.filter_actions(
|
||||
self.actions_mapping, set(include), set(exclude)
|
||||
)
|
||||
|
||||
# Action delay
|
||||
self.action_delay = self.get_mapping_per_action(
|
||||
self.actions_mapping,
|
||||
custom=self.args.get("action_delay"),
|
||||
default=0,
|
||||
)
|
||||
self.action_delay_handles = defaultdict(lambda: None)
|
||||
self.action_handles = defaultdict(lambda: None)
|
||||
|
||||
# Action delta
|
||||
self.action_delta = self.get_mapping_per_action(
|
||||
self.actions_mapping,
|
||||
custom=self.args.get("action_delta"),
|
||||
default=DEFAULT_ACTION_DELTA,
|
||||
)
|
||||
self.action_times = defaultdict(float)
|
||||
|
||||
# Previous state
|
||||
self.previous_states = self.get_mapping_per_action(
|
||||
self.actions_mapping,
|
||||
custom=self.args.get("previous_state"),
|
||||
default=None,
|
||||
)
|
||||
|
||||
# Multiple click
|
||||
self.multiple_click_actions = self.get_multiple_click_actions(
|
||||
self.actions_mapping
|
||||
)
|
||||
self.multiple_click_delay = self.args.get(
|
||||
"multiple_click_delay", DEFAULT_MULTIPLE_CLICK_DELAY
|
||||
)
|
||||
self.multiple_click_action_times = defaultdict(float)
|
||||
self.click_counter = Counter()
|
||||
self.multiple_click_action_delay_tasks = defaultdict(lambda: None)
|
||||
|
||||
# Mode
|
||||
self.mode = self.get_mapping_per_action(
|
||||
self.actions_mapping, custom=self.args.get("mode"), default=MODE_SINGLE
|
||||
)
|
||||
|
||||
# Listen for device changes
|
||||
for controller_id in controllers_ids:
|
||||
await self.integration.listen_changes(controller_id)
|
||||
|
||||
def filter_actions(
|
||||
self,
|
||||
actions_mapping: ActionsMapping,
|
||||
include: set[ActionEvent],
|
||||
exclude: set[ActionEvent],
|
||||
) -> ActionsMapping:
|
||||
allowed_actions = include - exclude
|
||||
return {
|
||||
key: value
|
||||
for key, value in actions_mapping.items()
|
||||
if key in allowed_actions
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_option(value: str, options: list[str], ctx: str | None = None) -> str:
|
||||
if value in options:
|
||||
return value
|
||||
else:
|
||||
raise ValueError(
|
||||
f"{f'{ctx} - ' if ctx is not None else ''}`{value}` is not an option. "
|
||||
f"The options are {options}"
|
||||
)
|
||||
|
||||
def parse_integration(
|
||||
self, integration: str | dict[str, Any] | Any
|
||||
) -> dict[str, str]:
|
||||
if isinstance(integration, str):
|
||||
return {"name": integration}
|
||||
elif isinstance(integration, dict):
|
||||
if "name" in integration:
|
||||
return integration
|
||||
else:
|
||||
raise ValueError("'name' attribute is mandatory")
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Type {type(integration)} is not supported for `integration` attribute"
|
||||
)
|
||||
|
||||
def get_integration(self, integration: str | dict[str, Any]) -> Integration:
|
||||
parsed_integration = self.parse_integration(integration)
|
||||
kwargs = {k: v for k, v in parsed_integration.items() if k != "name"}
|
||||
integrations = integration_module.get_integrations(self, kwargs)
|
||||
integration_argument = self.get_option(
|
||||
parsed_integration["name"], [i.name for i in integrations]
|
||||
)
|
||||
return next(i for i in integrations if i.name == integration_argument)
|
||||
|
||||
def get_default_actions_mapping(
|
||||
self, integration: Integration
|
||||
) -> DefaultActionsMapping:
|
||||
actions_mapping = integration.get_default_actions_mapping()
|
||||
if actions_mapping is None:
|
||||
raise ValueError(
|
||||
f"This controller does not support {integration.name}. Use `mapping` to define the actions."
|
||||
)
|
||||
return actions_mapping
|
||||
|
||||
@overload
|
||||
def get_list(self, entities: list[T]) -> list[T]:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def get_list(self, entities: T) -> list[T]:
|
||||
pass
|
||||
|
||||
def get_list(self, entities: list[T] | T) -> list[T]:
|
||||
if isinstance(entities, (list, tuple)):
|
||||
return list(entities)
|
||||
return [entities]
|
||||
|
||||
@overload
|
||||
def get_mapping_per_action(
|
||||
self,
|
||||
actions_mapping: ActionsMapping,
|
||||
*,
|
||||
custom: T | dict[ActionEvent, T] | None,
|
||||
default: None,
|
||||
) -> dict[ActionEvent, T | None]:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def get_mapping_per_action(
|
||||
self,
|
||||
actions_mapping: ActionsMapping,
|
||||
*,
|
||||
custom: T | dict[ActionEvent, T] | None,
|
||||
default: T,
|
||||
) -> dict[ActionEvent, T]:
|
||||
pass
|
||||
|
||||
def get_mapping_per_action(
|
||||
self,
|
||||
actions_mapping: ActionsMapping,
|
||||
*,
|
||||
custom: T | dict[ActionEvent, T] | None,
|
||||
default: None | T,
|
||||
) -> dict[ActionEvent, T | None] | dict[ActionEvent, T]:
|
||||
if custom is not None and not isinstance(custom, dict):
|
||||
default = custom
|
||||
mapping = {action: default for action in actions_mapping}
|
||||
if custom is not None and isinstance(custom, dict):
|
||||
mapping.update(custom)
|
||||
return mapping
|
||||
|
||||
def parse_action_mapping(self, mapping: CustomActionsMapping) -> ActionsMapping:
|
||||
return {
|
||||
event: parse_actions(self, action)
|
||||
for event, action in mapping.items()
|
||||
if action is not None
|
||||
}
|
||||
|
||||
def get_multiple_click_actions(self, mapping: ActionsMapping) -> set[ActionEvent]:
|
||||
to_return: set[ActionEvent] = set()
|
||||
for key in mapping.keys():
|
||||
if not isinstance(key, str) or MULTIPLE_CLICK_TOKEN not in key:
|
||||
continue
|
||||
splitted = key.split(MULTIPLE_CLICK_TOKEN)
|
||||
assert 1 <= len(splitted) <= 2
|
||||
action_key, _ = splitted
|
||||
try:
|
||||
to_return.add(int(action_key))
|
||||
except ValueError:
|
||||
to_return.add(action_key)
|
||||
return to_return
|
||||
|
||||
def format_multiple_click_action(
|
||||
self, action_key: ActionEvent, click_count: int
|
||||
) -> str:
|
||||
return (
|
||||
str(action_key) + MULTIPLE_CLICK_TOKEN + str(click_count)
|
||||
) # e.g. toggle$2
|
||||
|
||||
async def _render_template(self, template: str) -> Any:
|
||||
result = await self.render_template(template)
|
||||
if result is None:
|
||||
raise ValueError(f"Template {template} returned None")
|
||||
try:
|
||||
return literal_eval(result)
|
||||
except (SyntaxError, ValueError):
|
||||
return result
|
||||
|
||||
_TEMPLATE_RE = re.compile(r"\s*\{\{.*\}\}")
|
||||
|
||||
def contains_templating(self, template: str) -> bool:
|
||||
is_template = self._TEMPLATE_RE.search(template) is not None
|
||||
if not is_template:
|
||||
self.log(f"`{template}` is not recognized as a template", level="DEBUG")
|
||||
return is_template
|
||||
|
||||
async def render_value(self, value: Any) -> Any:
|
||||
if isinstance(value, str) and self.contains_templating(value):
|
||||
return await self._render_template(value)
|
||||
else:
|
||||
return value
|
||||
|
||||
async def render_attributes(self, attributes: dict[str, Any]) -> dict[str, Any]:
|
||||
new_attributes: dict[str, Any] = {}
|
||||
for key, value in attributes.items():
|
||||
new_value = await self.render_value(value)
|
||||
if isinstance(value, dict):
|
||||
new_value = await self.render_attributes(value)
|
||||
new_attributes[key] = new_value
|
||||
return new_attributes
|
||||
|
||||
async def call_service(
|
||||
self, service: str, render_template: bool = True, **attributes: Any
|
||||
) -> Any | None:
|
||||
service = service.replace(".", "/")
|
||||
to_log = ["\n", f"🤖 Service: \033[1m{service.replace('/', '.')}\033[0m"]
|
||||
if service != "template/render" and render_template:
|
||||
attributes = await self.render_attributes(attributes)
|
||||
for attribute, value in attributes.items():
|
||||
if isinstance(value, float):
|
||||
value = f"{value:.2f}"
|
||||
to_log.append(f" - {attribute}: {value}")
|
||||
self.log("\n".join(to_log), level="INFO", ascii_encode=False)
|
||||
return await ADAPI.call_service(self, service, **attributes)
|
||||
|
||||
@utils.sync_decorator # type: ignore[untyped-decorator]
|
||||
async def get_state(
|
||||
self,
|
||||
entity_id: str | None = None,
|
||||
attribute: str | Literal["all"] | None = None,
|
||||
default: Any | None = None,
|
||||
namespace: str | None = None,
|
||||
copy: bool = True,
|
||||
**kwargs: dict[str, Any], # left in intentionally for compatibility
|
||||
) -> Any | dict[str, Any] | None:
|
||||
rendered_entity_id = await self.render_value(entity_id)
|
||||
return await super().get_state(
|
||||
rendered_entity_id, attribute, default=default, copy=copy
|
||||
)
|
||||
|
||||
async def handle_action(
|
||||
self,
|
||||
action_key: str,
|
||||
previous_state: str | None = None,
|
||||
extra: EventData | None = None,
|
||||
) -> None:
|
||||
if (
|
||||
action_key in self.actions_mapping
|
||||
and self.previous_states[action_key] is not None
|
||||
and previous_state != self.previous_states[action_key]
|
||||
):
|
||||
self.log(
|
||||
f"🎮 `{action_key}` not triggered because previous action was `{previous_state}`",
|
||||
level="DEBUG",
|
||||
ascii_encode=False,
|
||||
)
|
||||
return
|
||||
if (
|
||||
action_key in self.actions_mapping
|
||||
and action_key not in self.multiple_click_actions
|
||||
):
|
||||
previous_call_time = self.action_times[action_key]
|
||||
now = time.time() * 1000
|
||||
self.action_times[action_key] = now
|
||||
if now - previous_call_time > self.action_delta[action_key]:
|
||||
await self.call_action(action_key, extra=extra)
|
||||
elif action_key in self.multiple_click_actions:
|
||||
now = time.time() * 1000
|
||||
previous_call_time = self.multiple_click_action_times.get(action_key, now)
|
||||
self.multiple_click_action_times[action_key] = now
|
||||
if now - previous_call_time > self.multiple_click_delay:
|
||||
pass
|
||||
|
||||
previous_task = self.multiple_click_action_delay_tasks[action_key]
|
||||
if previous_task is not None:
|
||||
previous_task.cancel()
|
||||
|
||||
self.click_counter[action_key] += 1
|
||||
click_count = self.click_counter[action_key]
|
||||
|
||||
new_task = run_in(
|
||||
self.multiple_click_call_action,
|
||||
self.multiple_click_delay / 1000,
|
||||
action_key=action_key,
|
||||
extra=extra,
|
||||
click_count=click_count,
|
||||
)
|
||||
self.multiple_click_action_delay_tasks[action_key] = new_task
|
||||
else:
|
||||
self.log(
|
||||
f"🎮 Button event triggered, but not registered: `{action_key}`",
|
||||
level="DEBUG",
|
||||
ascii_encode=False,
|
||||
)
|
||||
|
||||
async def multiple_click_call_action(self, kwargs: dict[str, Any]) -> None:
|
||||
action_key: ActionEvent = kwargs["action_key"]
|
||||
extra: EventData = kwargs["extra"]
|
||||
click_count: int = kwargs["click_count"]
|
||||
self.multiple_click_action_delay_tasks[action_key] = None
|
||||
self.log(
|
||||
f"🎮 {action_key} clicked `{click_count}` time(s)",
|
||||
level="DEBUG",
|
||||
ascii_encode=False,
|
||||
)
|
||||
self.click_counter[action_key] = 0
|
||||
click_action_key = self.format_multiple_click_action(action_key, click_count)
|
||||
if click_action_key in self.actions_mapping:
|
||||
await self.call_action(click_action_key, extra=extra)
|
||||
elif action_key in self.actions_mapping and click_count == 1:
|
||||
await self.call_action(action_key, extra=extra)
|
||||
|
||||
async def call_action(
|
||||
self, action_key: ActionEvent, extra: EventData | None = None
|
||||
) -> None:
|
||||
self.log(
|
||||
f"🎮 Button event triggered: `{action_key}`",
|
||||
level="INFO",
|
||||
ascii_encode=False,
|
||||
)
|
||||
self.log(
|
||||
f"Extra:\n{extra}",
|
||||
level="DEBUG",
|
||||
)
|
||||
delay = self.action_delay[action_key]
|
||||
if delay > 0:
|
||||
handle = self.action_delay_handles[action_key]
|
||||
if handle is not None:
|
||||
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
|
||||
)
|
||||
self.action_delay_handles[action_key] = new_handle
|
||||
else:
|
||||
await self.action_timer_callback({"action_key": action_key, "extra": extra})
|
||||
|
||||
async def _apply_mode_strategy(self, action_key: ActionEvent) -> bool:
|
||||
previous_task = self.action_handles[action_key]
|
||||
if previous_task is None or previous_task.done():
|
||||
return False
|
||||
if self.mode[action_key] == MODE_SINGLE:
|
||||
self.log(
|
||||
f"There is already an action executing for `{action_key}`. "
|
||||
"If you want a different behaviour change `mode` parameter, "
|
||||
"the default value is `single`.",
|
||||
level="WARNING",
|
||||
)
|
||||
return True
|
||||
elif self.mode[action_key] == MODE_RESTART:
|
||||
previous_task.cancel()
|
||||
elif self.mode[action_key] == MODE_QUEUED:
|
||||
await previous_task
|
||||
elif self.mode[action_key] == MODE_PARALLEL:
|
||||
pass
|
||||
else:
|
||||
raise ValueError(
|
||||
f"`{self.mode[action_key]}` is not a possible value for `mode` parameter."
|
||||
"Possible values: `single`, `restart`, `queued` and `parallel`."
|
||||
)
|
||||
return False
|
||||
|
||||
async def action_timer_callback(self, kwargs: dict[str, Any]) -> None:
|
||||
action_key: ActionEvent = kwargs["action_key"]
|
||||
extra: EventData = kwargs["extra"]
|
||||
self.action_delay_handles[action_key] = None
|
||||
skip = await self._apply_mode_strategy(action_key)
|
||||
if skip:
|
||||
return
|
||||
action_types = self.actions_mapping[action_key]
|
||||
task = asyncio.create_task(self.call_action_types(action_types, extra))
|
||||
self.action_handles[action_key] = task
|
||||
try:
|
||||
await task
|
||||
except CancelledError:
|
||||
self.log(
|
||||
f"Task(s) from `{action_key}` was/were canceled and executed again",
|
||||
level="DEBUG",
|
||||
)
|
||||
|
||||
async def call_action_types(
|
||||
self, action_types: list[ActionType], extra: EventData | None = None
|
||||
) -> None:
|
||||
for action_type in action_types:
|
||||
self.log(
|
||||
f"🏃 Running `{action_type}` now",
|
||||
level="INFO",
|
||||
ascii_encode=False,
|
||||
)
|
||||
await action_type.run(extra=extra)
|
||||
|
||||
async def before_action(self, action: str, *args: str, **kwargs: Any) -> bool:
|
||||
"""
|
||||
Controllers have the option to implement this function, which is called
|
||||
everytime before an action is called and it has the check_before_action decorator.
|
||||
It should return True if the action shoul be called.
|
||||
Otherwise it should return False.
|
||||
"""
|
||||
return True
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
"""
|
||||
Controllers can implement this function. It should return a dict
|
||||
with the states that a controller can take and the functions as values.
|
||||
This is used for zigbee2mqtt support.
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
"""
|
||||
Controllers can implement this function. It should return a dict
|
||||
with the event id that a controller can take and the functions as values.
|
||||
This is used for deCONZ support.
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
"""
|
||||
Controllers can implement this function. It should return a dict
|
||||
with the command that a controller can take and the functions as values.
|
||||
This is used for ZHA support.
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str | None:
|
||||
"""
|
||||
This method can be override for controllers that do not support
|
||||
the standard extraction of the actions on cx_core/integration/zha.py
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_lutron_caseta_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
"""
|
||||
Controllers can implement this function. It should return a dict
|
||||
with the command that a controller can take and the functions as values.
|
||||
This is used for Lutron support.
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_state_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
"""
|
||||
Controllers can implement this function. It should return a dict
|
||||
with the command that a controller can take and the functions as values.
|
||||
This is used for State integration support.
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_homematic_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
"""
|
||||
Controllers can implement this function. It should return a dict
|
||||
with the command that a controller can take and the functions as values.
|
||||
This is used for Homematic support.
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_shelly_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
"""
|
||||
Controllers can implement this function. It should return a dict
|
||||
with the command that a controller can take and the functions as values.
|
||||
This is used for Shelly support.
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_shellyforhass_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
"""
|
||||
Controllers can implement this function. It should return a dict
|
||||
with the command that a controller can take and the functions as values.
|
||||
This is used for Shelly for HASS support.
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_tasmota_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
"""
|
||||
Controllers can implement this function. It should return a dict
|
||||
with the command that a controller can take and the functions as values.
|
||||
This is used for Tasmota support.
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_predefined_actions_mapping(self) -> PredefinedActionsMapping:
|
||||
return {}
|
||||
@@ -1,40 +0,0 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cx_core.type_controller import Entity, TypeController
|
||||
|
||||
|
||||
class FeatureSupport:
|
||||
controller: "TypeController[Entity]"
|
||||
update_supported_features: bool
|
||||
_supported_features: int | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
controller: "TypeController[Entity]",
|
||||
supported_features: int | None = None,
|
||||
update_supported_features: bool = False,
|
||||
) -> None:
|
||||
self.controller = controller
|
||||
self._supported_features = supported_features
|
||||
self.update_supported_features = update_supported_features
|
||||
|
||||
@property
|
||||
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(
|
||||
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.controller.entity}`. Entity might not be available."
|
||||
)
|
||||
return self._supported_features
|
||||
|
||||
async def is_supported(self, feature: int) -> bool:
|
||||
return feature & await self.supported_features != 0
|
||||
|
||||
async def not_supported(self, feature: int) -> bool:
|
||||
return not await self.is_supported(feature)
|
||||
@@ -1,9 +0,0 @@
|
||||
class CoverSupport:
|
||||
OPEN = 1
|
||||
CLOSE = 2
|
||||
SET_COVER_POSITION = 4
|
||||
STOP = 8
|
||||
OPEN_TILT = 16
|
||||
CLOSE_TILT = 32
|
||||
STOP_TILT = 64
|
||||
SET_TILT_POSITION = 128
|
||||
@@ -1,4 +0,0 @@
|
||||
class LightSupport:
|
||||
EFFECT = 4
|
||||
FLASH = 8
|
||||
TRANSITION = 32
|
||||
@@ -1,20 +0,0 @@
|
||||
class MediaPlayerSupport:
|
||||
PAUSE = 1
|
||||
SEEK = 2
|
||||
VOLUME_SET = 4
|
||||
VOLUME_MUTE = 8
|
||||
PREVIOUS_TRACK = 16
|
||||
NEXT_TRACK = 32
|
||||
TURN_ON = 128
|
||||
TURN_OFF = 256
|
||||
PLAY_MEDIA = 512
|
||||
VOLUME_STEP = 1024
|
||||
SELECT_SOURCE = 2048
|
||||
STOP = 4096
|
||||
CLEAR_PLAYLIST = 8192
|
||||
PLAY = 16384
|
||||
SHUFFLE_SET = 32768
|
||||
SELECT_SOUND_MODE = 65536
|
||||
SUPPORT_BROWSE_MEDIA = 131072
|
||||
SUPPORT_REPEAT_SET = 262144
|
||||
SUPPORT_GROUPING = 524288
|
||||
@@ -1,35 +0,0 @@
|
||||
import abc
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from cx_const import DefaultActionsMapping
|
||||
from cx_helper import get_classes
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cx_core.controller import Controller
|
||||
|
||||
EventData = dict[str, Any]
|
||||
|
||||
|
||||
class Integration(abc.ABC):
|
||||
name: str
|
||||
controller: "Controller"
|
||||
kwargs: dict[str, Any]
|
||||
|
||||
def __init__(self, controller: "Controller", kwargs: dict[str, Any]):
|
||||
self.controller = controller
|
||||
self.kwargs = kwargs
|
||||
|
||||
def get_default_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
return None
|
||||
|
||||
@abc.abstractmethod
|
||||
async def listen_changes(self, controller_id: str) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def get_integrations(
|
||||
controller: "Controller", kwargs: dict[str, Any]
|
||||
) -> list[Integration]:
|
||||
integration_classes = get_classes(__file__, __package__, Integration)
|
||||
integrations = [cls_(controller, kwargs) for cls_ in integration_classes]
|
||||
return integrations
|
||||
@@ -1,34 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
from appdaemon.plugins.hass.hassapi import Hass
|
||||
from cx_const import DefaultActionsMapping
|
||||
from cx_core.integration import EventData, Integration
|
||||
|
||||
LISTENS_TO_ID = "id"
|
||||
LISTENS_TO_UNIQUE_ID = "unique_id"
|
||||
|
||||
|
||||
class DeCONZIntegration(Integration):
|
||||
name = "deconz"
|
||||
|
||||
def get_default_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
return self.controller.get_deconz_actions_mapping()
|
||||
|
||||
async def listen_changes(self, controller_id: str) -> None:
|
||||
listens_to = self.kwargs.get("listen_to", LISTENS_TO_ID)
|
||||
if listens_to not in (LISTENS_TO_ID, LISTENS_TO_UNIQUE_ID):
|
||||
raise ValueError(
|
||||
"`listens_to` for deCONZ integration should either be `id` or `unique_id`"
|
||||
)
|
||||
await Hass.listen_event(
|
||||
self.controller,
|
||||
self.event_callback,
|
||||
"deconz_event",
|
||||
**{listens_to: controller_id}
|
||||
)
|
||||
|
||||
async def event_callback(
|
||||
self, event_name: str, data: EventData, kwargs: dict[str, Any]
|
||||
) -> None:
|
||||
type_ = self.kwargs.get("type", "event")
|
||||
await self.controller.handle_action(data[type_], extra=data)
|
||||
@@ -1,41 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
from appdaemon.plugins.hass.hassapi import Hass
|
||||
from cx_core.integration import EventData, Integration
|
||||
|
||||
|
||||
class EventIntegration(Integration):
|
||||
name = "event"
|
||||
|
||||
def get_arg(self, arg: str) -> Any:
|
||||
try:
|
||||
return self.kwargs[arg]
|
||||
except KeyError:
|
||||
raise ValueError(f"{arg} is a mandatory field for event integration.")
|
||||
|
||||
async def listen_changes(self, controller_id: str) -> None:
|
||||
event_type: str = self.get_arg("event_type")
|
||||
controller_key: str = self.get_arg("controller_key")
|
||||
self.controller.log(
|
||||
f"Listening to `{event_type}` events for controller `{controller_key}={controller_id}`"
|
||||
)
|
||||
await Hass.listen_event(
|
||||
self.controller,
|
||||
self.event_callback,
|
||||
event_type,
|
||||
**{controller_key: controller_id},
|
||||
)
|
||||
|
||||
async def event_callback(
|
||||
self, event_name: str, data: EventData, kwargs: dict[str, Any]
|
||||
) -> None:
|
||||
action_template: str = self.get_arg("action_template")
|
||||
try:
|
||||
action = action_template.format(**data)
|
||||
except Exception:
|
||||
self.controller.log(
|
||||
f"Template `{action_template}` could not be rendered with data={data}",
|
||||
level="WARNING",
|
||||
)
|
||||
return
|
||||
await self.controller.handle_action(action)
|
||||
@@ -1,36 +0,0 @@
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from appdaemon.plugins.hass.hassapi import Hass
|
||||
from cx_const import DefaultActionsMapping
|
||||
from cx_core.integration import EventData, Integration
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cx_core.controller import Controller
|
||||
|
||||
|
||||
class HomematicIntegration(Integration):
|
||||
name = "homematic"
|
||||
_registered_controller_ids: set[str]
|
||||
|
||||
def __init__(self, controller: "Controller", kwargs: dict[str, Any]):
|
||||
self._registered_controller_ids = set()
|
||||
super().__init__(controller, kwargs)
|
||||
|
||||
def get_default_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
return self.controller.get_homematic_actions_mapping()
|
||||
|
||||
async def listen_changes(self, controller_id: str) -> None:
|
||||
self._registered_controller_ids.add(controller_id)
|
||||
await Hass.listen_event(
|
||||
self.controller, self.event_callback, "homematic.keypress"
|
||||
)
|
||||
|
||||
async def event_callback(
|
||||
self, event_name: str, data: EventData, kwargs: dict[str, Any]
|
||||
) -> None:
|
||||
if data["name"] not in self._registered_controller_ids:
|
||||
return
|
||||
param = data["param"]
|
||||
channel = data["channel"]
|
||||
action = f"{param}_{channel}"
|
||||
await self.controller.handle_action(action, extra=data)
|
||||
@@ -1,28 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
from appdaemon.plugins.hass.hassapi import Hass
|
||||
from cx_const import DefaultActionsMapping
|
||||
from cx_core.integration import EventData, Integration
|
||||
|
||||
|
||||
class LutronIntegration(Integration):
|
||||
name = "lutron_caseta"
|
||||
|
||||
def get_default_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
return self.controller.get_lutron_caseta_actions_mapping()
|
||||
|
||||
async def listen_changes(self, controller_id: str) -> None:
|
||||
await Hass.listen_event(
|
||||
self.controller,
|
||||
self.event_callback,
|
||||
"lutron_caseta_button_event",
|
||||
serial=controller_id,
|
||||
)
|
||||
|
||||
async def event_callback(
|
||||
self, event_name: str, data: EventData, kwargs: dict[str, Any]
|
||||
) -> None:
|
||||
button = data["button_number"]
|
||||
action_type = data["action"]
|
||||
action = f"button_{button}_{action_type}"
|
||||
await self.controller.handle_action(action, extra=data)
|
||||
@@ -1,43 +0,0 @@
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from appdaemon.plugins.mqtt.mqttapi import Mqtt
|
||||
from cx_const import DefaultActionsMapping
|
||||
from cx_core.integration import EventData, Integration
|
||||
|
||||
|
||||
class MQTTIntegration(Integration):
|
||||
name = "mqtt"
|
||||
|
||||
def get_default_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
return self.controller.get_z2m_actions_mapping()
|
||||
|
||||
async def listen_changes(self, controller_id: str) -> None:
|
||||
await Mqtt.listen_event(
|
||||
self.controller, self.event_callback, topic=controller_id, namespace="mqtt"
|
||||
)
|
||||
|
||||
async def event_callback(
|
||||
self, event_name: str, data: EventData, kwargs: dict[str, Any]
|
||||
) -> None:
|
||||
self.controller.log(f"MQTT data event: {data}", level="DEBUG")
|
||||
payload_key = self.kwargs.get("key")
|
||||
if "payload" not in data:
|
||||
return
|
||||
payload = data["payload"]
|
||||
action_key: str
|
||||
if payload_key is None:
|
||||
action_key = payload
|
||||
else:
|
||||
try:
|
||||
action_key = str(json.loads(payload)[payload_key]).lower()
|
||||
except json.decoder.JSONDecodeError:
|
||||
raise ValueError(
|
||||
f"`key` is being used ({payload_key}). "
|
||||
f"Following payload is not a valid JSON: {payload}"
|
||||
)
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
f"Following payload does not contain `{payload_key}`: {payload}"
|
||||
)
|
||||
await self.controller.handle_action(action_key)
|
||||
@@ -1,25 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
from appdaemon.plugins.hass.hassapi import Hass
|
||||
from cx_const import DefaultActionsMapping
|
||||
from cx_core.integration import EventData, Integration
|
||||
|
||||
|
||||
class ShellyIntegration(Integration):
|
||||
name = "shelly"
|
||||
|
||||
def get_default_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
return self.controller.get_shelly_actions_mapping()
|
||||
|
||||
async def listen_changes(self, controller_id: str) -> None:
|
||||
await Hass.listen_event(
|
||||
self.controller, self.event_callback, "shelly.click", device=controller_id
|
||||
)
|
||||
|
||||
async def event_callback(
|
||||
self, event_name: str, data: EventData, kwargs: dict[str, Any]
|
||||
) -> None:
|
||||
click_type = data["click_type"]
|
||||
channel = data["channel"]
|
||||
action = f"{click_type}_{channel}"
|
||||
await self.controller.handle_action(action, extra=data)
|
||||
@@ -1,27 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
from appdaemon.plugins.hass.hassapi import Hass
|
||||
from cx_const import DefaultActionsMapping
|
||||
from cx_core.integration import EventData, Integration
|
||||
|
||||
|
||||
class ShellyForHASSIntegration(Integration):
|
||||
name = "shellyforhass"
|
||||
|
||||
def get_default_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
return self.controller.get_shellyforhass_actions_mapping()
|
||||
|
||||
async def listen_changes(self, controller_id: str) -> None:
|
||||
await Hass.listen_event(
|
||||
self.controller,
|
||||
self.event_callback,
|
||||
"shellyforhass.click",
|
||||
entity_id=controller_id,
|
||||
)
|
||||
|
||||
async def event_callback(
|
||||
self, event_name: str, data: EventData, kwargs: dict[str, Any]
|
||||
) -> None:
|
||||
click_type = data["click_type"]
|
||||
action = f"{click_type}"
|
||||
await self.controller.handle_action(action, extra=data)
|
||||
@@ -1,28 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
from appdaemon.plugins.hass.hassapi import Hass
|
||||
from cx_const import DefaultActionsMapping
|
||||
from cx_core.integration import Integration
|
||||
|
||||
|
||||
class StateIntegration(Integration):
|
||||
name = "state"
|
||||
|
||||
def get_default_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
return self.controller.get_state_actions_mapping()
|
||||
|
||||
async def listen_changes(self, controller_id: str) -> None:
|
||||
attribute = self.kwargs.get("attribute", None)
|
||||
await Hass.listen_state(
|
||||
self.controller, self.state_callback, controller_id, attribute=attribute
|
||||
)
|
||||
|
||||
async def state_callback(
|
||||
self,
|
||||
entity: str | None,
|
||||
attribute: str | None,
|
||||
old: str | None,
|
||||
new: str,
|
||||
kwargs: dict[str, Any],
|
||||
) -> None:
|
||||
await self.controller.handle_action(new, previous_state=old)
|
||||
@@ -1,51 +0,0 @@
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from appdaemon.plugins.mqtt.mqttapi import Mqtt
|
||||
from cx_const import DefaultActionsMapping
|
||||
from cx_core.integration import EventData, Integration
|
||||
|
||||
|
||||
class TasmotaIntegration(Integration):
|
||||
name = "tasmota"
|
||||
|
||||
def get_default_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
return self.controller.get_tasmota_actions_mapping()
|
||||
|
||||
async def listen_changes(self, controller_id: str) -> None:
|
||||
component_key = self.kwargs.get("component")
|
||||
if component_key is None:
|
||||
raise ValueError(
|
||||
"`component` attribute is mandatory. "
|
||||
"Check example from https://xaviml.github.io/controllerx/start/integrations/tasmota"
|
||||
)
|
||||
await Mqtt.listen_event(
|
||||
self.controller, self.event_callback, topic=controller_id, namespace="mqtt"
|
||||
)
|
||||
|
||||
async def event_callback(
|
||||
self, event_name: str, data: EventData, kwargs: dict[str, Any]
|
||||
) -> None:
|
||||
self.controller.log(f"MQTT data event: {data}", level="DEBUG")
|
||||
component_key: str = self.kwargs["component"]
|
||||
payload_key: str = self.kwargs.get("key", "Action")
|
||||
if "payload" not in data:
|
||||
return
|
||||
payload: str = data["payload"]
|
||||
# Even though this checks if "compoenent_key" is in the payload
|
||||
# (as string, not dictionary), it is preferred for its performance
|
||||
if component_key not in payload:
|
||||
return
|
||||
try:
|
||||
action_key = str(json.loads(payload)[component_key][payload_key])
|
||||
except json.decoder.JSONDecodeError:
|
||||
raise ValueError(
|
||||
f"`key` is being used ({payload_key}). "
|
||||
f"Following payload is not a valid JSON: {payload}"
|
||||
)
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
"Following payload does not contain "
|
||||
f"payload_key=`{payload_key}`: {payload}"
|
||||
)
|
||||
await self.controller.handle_action(action_key)
|
||||
@@ -1,85 +0,0 @@
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
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
|
||||
|
||||
LISTENS_TO_HA = "ha"
|
||||
LISTENS_TO_MQTT = "mqtt"
|
||||
LISTENS_TO_EVENT = "event"
|
||||
|
||||
|
||||
class Z2MIntegration(Integration):
|
||||
name = "z2m"
|
||||
|
||||
def get_default_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
return self.controller.get_z2m_actions_mapping()
|
||||
|
||||
async def listen_changes(self, controller_id: str) -> None:
|
||||
listens_to = self.kwargs.get("listen_to", LISTENS_TO_HA)
|
||||
if listens_to == LISTENS_TO_HA:
|
||||
self.controller.log(
|
||||
"⚠️ Listening to HA sensor actions is now deprecated and will be removed in the future. Use `listen_to: mqtt` or `listen_to: event` instead."
|
||||
" Read more about it here: https://xaviml.github.io/controllerx/others/z2m-ha-sensor-deprecated",
|
||||
level="WARNING",
|
||||
ascii_encode=False,
|
||||
)
|
||||
await Hass.listen_state(self.controller, self.state_callback, controller_id)
|
||||
elif listens_to == LISTENS_TO_MQTT:
|
||||
topic_prefix = self.kwargs.get("topic_prefix", "zigbee2mqtt")
|
||||
await Mqtt.listen_event(
|
||||
self.controller,
|
||||
self.event_callback,
|
||||
topic=f"{topic_prefix}/{controller_id}",
|
||||
namespace="mqtt",
|
||||
)
|
||||
elif listens_to == LISTENS_TO_EVENT:
|
||||
await Hass.listen_state(
|
||||
self.controller,
|
||||
self.state_callback,
|
||||
f"event.{controller_id}",
|
||||
attribute="event_type",
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
"`listen_to` has to be either `ha`, `mqtt` or `event`. Default is `ha`."
|
||||
)
|
||||
|
||||
async def event_callback(
|
||||
self, event_name: str, data: EventData, kwargs: dict[str, Any]
|
||||
) -> None:
|
||||
self.controller.log(f"MQTT data event: {data}", level="DEBUG")
|
||||
action_key = self.kwargs.get("action_key", "action")
|
||||
action_group_key = self.kwargs.get("action_group_key", "action_group")
|
||||
if "payload" not in data:
|
||||
return
|
||||
payload = json.loads(data["payload"])
|
||||
if action_key not in payload:
|
||||
self.controller.log(
|
||||
f"⚠️ There is no `{action_key}` in the MQTT topic payload",
|
||||
level="WARNING",
|
||||
ascii_encode=False,
|
||||
)
|
||||
return
|
||||
if action_group_key in payload and "action_group" in self.kwargs:
|
||||
action_group = self.controller.get_list(self.kwargs["action_group"])
|
||||
if payload["action_group"] not in action_group:
|
||||
self.controller.log(
|
||||
f"Action group {payload['action_group']} not found in "
|
||||
f"action groups: {action_group}",
|
||||
level="DEBUG",
|
||||
)
|
||||
return
|
||||
await self.controller.handle_action(payload[action_key], extra=payload)
|
||||
|
||||
async def state_callback(
|
||||
self,
|
||||
entity: str | None,
|
||||
attribute: str | None,
|
||||
old: str | None,
|
||||
new: str,
|
||||
kwargs: dict[str, Any],
|
||||
) -> None:
|
||||
await self.controller.handle_action(new, previous_state=old)
|
||||
@@ -1,46 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
from appdaemon.plugins.hass.hassapi import Hass
|
||||
from cx_const import DefaultActionsMapping
|
||||
from cx_core.integration import EventData, Integration
|
||||
|
||||
|
||||
class ZHAIntegration(Integration):
|
||||
name = "zha"
|
||||
|
||||
def get_default_actions_mapping(self) -> DefaultActionsMapping | None:
|
||||
return self.controller.get_zha_actions_mapping()
|
||||
|
||||
async def listen_changes(self, controller_id: str) -> None:
|
||||
await Hass.listen_event(
|
||||
self.controller, self.event_callback, "zha_event", device_ieee=controller_id
|
||||
)
|
||||
|
||||
def get_action(self, data: EventData) -> str:
|
||||
command = data["command"]
|
||||
args = data["args"]
|
||||
if isinstance(args, dict):
|
||||
args = args["args"]
|
||||
args = list(map(str, args))
|
||||
action = command
|
||||
if not (command == "stop" or command == "release"):
|
||||
if len(args) > 0:
|
||||
action += "_" + "_".join(args)
|
||||
return action
|
||||
|
||||
async def event_callback(
|
||||
self, event_name: str, data: EventData, kwargs: dict[str, Any]
|
||||
) -> None:
|
||||
action = self.controller.get_zha_action(data)
|
||||
if action is None:
|
||||
# If there is no action extracted from the controller then
|
||||
# we extract with the standard function
|
||||
try:
|
||||
action = self.get_action(data)
|
||||
except Exception:
|
||||
self.controller.log(
|
||||
f"The following event could not be parsed: {data}", level="WARNING"
|
||||
)
|
||||
return
|
||||
|
||||
await self.controller.handle_action(action, extra=data)
|
||||
@@ -1,69 +0,0 @@
|
||||
import abc
|
||||
from typing import Any
|
||||
|
||||
from cx_core.controller import Controller, action
|
||||
|
||||
DEFAULT_DELAY = 350 # In milliseconds
|
||||
DEFAULT_RELEASE_DELAY = 0 # In seconds
|
||||
|
||||
|
||||
class ReleaseHoldController(Controller, abc.ABC):
|
||||
DEFAULT_MAX_LOOPS = 50
|
||||
|
||||
on_hold: bool
|
||||
delay: float
|
||||
max_loops: int
|
||||
hold_release_toggle: bool
|
||||
release_delay: int
|
||||
|
||||
async def init(self) -> None:
|
||||
self.on_hold = False
|
||||
self.delay = self.args.get("delay", self.default_delay())
|
||||
self.max_loops = self.args.get(
|
||||
"max_loops", ReleaseHoldController.DEFAULT_MAX_LOOPS
|
||||
)
|
||||
self.hold_release_toggle = self.args.get("hold_release_toggle", False)
|
||||
self.release_delay = self.args.get("release_delay", DEFAULT_RELEASE_DELAY)
|
||||
await super().init()
|
||||
|
||||
@action
|
||||
async def release(self) -> None:
|
||||
if self.release_delay > 0:
|
||||
await self.sleep(self.release_delay)
|
||||
self.on_hold = False
|
||||
|
||||
async def hold(self, *args: Any) -> None:
|
||||
loops = 0
|
||||
self.on_hold = True
|
||||
stop = False
|
||||
while self.on_hold and not stop:
|
||||
stop = await self.hold_loop(*args)
|
||||
# Stop the iteration if we either stop from the hold_loop
|
||||
# or we reached the max loop number
|
||||
stop = stop or loops >= self.max_loops
|
||||
await self.sleep(self.delay / 1000)
|
||||
loops += 1
|
||||
self.on_hold = False
|
||||
|
||||
async def before_action(self, action: str, *args: Any, **kwargs: Any) -> bool:
|
||||
super_before_action = await super().before_action(action, *args, **kwargs)
|
||||
to_return = not (action == "hold" and self.on_hold)
|
||||
if action == "hold" and self.on_hold and self.hold_release_toggle:
|
||||
self.on_hold = False
|
||||
return super_before_action and to_return
|
||||
|
||||
@abc.abstractmethod
|
||||
async def hold_loop(self, *args: Any) -> bool:
|
||||
"""
|
||||
This function is called by the ReleaseHoldController depending on the settings.
|
||||
It stops calling the function once release action is called or when this function
|
||||
returns True.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def default_delay(self) -> int:
|
||||
"""
|
||||
This function can be overwritten for each device to indeicate the delay
|
||||
for the specific device, by default it returns the default delay from the app
|
||||
"""
|
||||
return DEFAULT_DELAY
|
||||
@@ -1,112 +0,0 @@
|
||||
import abc
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cx_const import Number, StepperDir
|
||||
|
||||
|
||||
class MinMax:
|
||||
def __init__(self, min: Number, max: Number, margin: float = 0.05) -> None:
|
||||
self._min = min
|
||||
self._max = max
|
||||
self.margin_dist = (max - min) * margin
|
||||
|
||||
@property
|
||||
def min(self) -> Number:
|
||||
return self._min
|
||||
|
||||
@property
|
||||
def max(self) -> Number:
|
||||
return self._max
|
||||
|
||||
def is_min(self, value: Number) -> bool:
|
||||
return self._min == value
|
||||
|
||||
def is_max(self, value: Number) -> bool:
|
||||
return self._max == value
|
||||
|
||||
def is_between(self, value: Number) -> bool:
|
||||
return self._min < value < self._max
|
||||
|
||||
def in_min_boundaries(self, value: Number) -> bool:
|
||||
return self._min <= value <= (self._min + self.margin_dist)
|
||||
|
||||
def in_max_boundaries(self, value: Number) -> bool:
|
||||
return (self._max - self.margin_dist) <= value <= self._max
|
||||
|
||||
def clip(self, value: Number) -> Number:
|
||||
return max(self._min, min(value, self._max))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"MinMax({self.min}, {self.max})"
|
||||
|
||||
|
||||
@dataclass
|
||||
class StepperOutput:
|
||||
next_value: Number
|
||||
next_direction: str | None
|
||||
|
||||
@property
|
||||
def exceeded(self) -> bool:
|
||||
return self.next_direction is None
|
||||
|
||||
|
||||
class Stepper(abc.ABC):
|
||||
sign_mapping = {StepperDir.UP: 1, StepperDir.DOWN: -1}
|
||||
|
||||
min_max: MinMax
|
||||
steps: Number
|
||||
previous_direction: str
|
||||
relative_steps: bool
|
||||
|
||||
@staticmethod
|
||||
def invert_direction(direction: str) -> str:
|
||||
return StepperDir.UP if direction == StepperDir.DOWN else StepperDir.DOWN
|
||||
|
||||
@staticmethod
|
||||
def sign(direction: str) -> int:
|
||||
return Stepper.sign_mapping[direction]
|
||||
|
||||
@staticmethod
|
||||
def apply_sign(value: Number, direction: str) -> Number:
|
||||
return Stepper.sign(direction) * value
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
min_max: MinMax,
|
||||
steps: Number,
|
||||
previous_direction: str = StepperDir.DOWN,
|
||||
relative_steps: bool = True,
|
||||
) -> None:
|
||||
self.min_max = min_max
|
||||
self.steps = steps
|
||||
self.previous_direction = previous_direction
|
||||
self.relative_steps = relative_steps
|
||||
|
||||
def _compute_step(self) -> float:
|
||||
if self.relative_steps:
|
||||
max_ = self.min_max.max
|
||||
min_ = self.min_max.min
|
||||
return (max_ - min_) / self.steps
|
||||
else:
|
||||
return self.steps
|
||||
|
||||
def get_direction(self, value: Number, direction: str) -> str:
|
||||
if direction == StepperDir.TOGGLE:
|
||||
direction = Stepper.invert_direction(self.previous_direction)
|
||||
self.previous_direction = direction
|
||||
return direction
|
||||
|
||||
@abc.abstractmethod
|
||||
def step(self, value: Number, direction: str) -> StepperOutput:
|
||||
"""
|
||||
This function updates the value according to the steps
|
||||
that needs to take and returns the new value together with
|
||||
the new direction it will need to go. If next_direction is
|
||||
None, the loop will stop executing.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class InvertStepper(Stepper):
|
||||
def step(self, value: Number, direction: str) -> StepperOutput:
|
||||
return StepperOutput(self.apply_sign(value, direction), next_direction=None)
|
||||
@@ -1,17 +0,0 @@
|
||||
from cx_const import Number
|
||||
from cx_core.stepper import Stepper, StepperOutput
|
||||
|
||||
|
||||
class BounceStepper(Stepper):
|
||||
def step(self, value: Number, direction: str) -> StepperOutput:
|
||||
value = self.min_max.clip(value)
|
||||
step = self._compute_step()
|
||||
|
||||
new_value = value + Stepper.apply_sign(step, direction)
|
||||
if self.min_max.is_between(new_value):
|
||||
return StepperOutput(round(new_value, 3), next_direction=direction)
|
||||
else:
|
||||
new_value = 2 * self.min_max.clip(new_value) - new_value
|
||||
return StepperOutput(
|
||||
round(new_value, 3), next_direction=Stepper.invert_direction(direction)
|
||||
)
|
||||
@@ -1,31 +0,0 @@
|
||||
from cx_const import Number, StepperDir
|
||||
from cx_core.stepper import MinMax, Stepper, StepperOutput
|
||||
|
||||
|
||||
class IndexLoopStepper(Stepper):
|
||||
def __init__(
|
||||
self,
|
||||
size: int,
|
||||
previous_direction: str = StepperDir.DOWN,
|
||||
relative_steps: bool = True,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
MinMax(0, size - 1),
|
||||
size,
|
||||
previous_direction,
|
||||
relative_steps,
|
||||
)
|
||||
|
||||
def step(self, value: Number, direction: str) -> StepperOutput:
|
||||
value = self.min_max.clip(value)
|
||||
sign = self.sign(direction)
|
||||
# We add +1 to make the max be included
|
||||
max_ = int(self.min_max.max) + 1
|
||||
min_ = int(self.min_max.min)
|
||||
if self.relative_steps:
|
||||
step = (max_ - min_) // self.steps
|
||||
else:
|
||||
step = self.steps
|
||||
|
||||
new_value = (int(value) + step * sign) % (max_ - min_) + min_
|
||||
return StepperOutput(new_value, next_direction=direction)
|
||||
@@ -1,16 +0,0 @@
|
||||
from cx_const import Number
|
||||
from cx_core.stepper import Stepper, StepperOutput
|
||||
|
||||
|
||||
class LoopStepper(Stepper):
|
||||
def step(self, value: Number, direction: str) -> StepperOutput:
|
||||
value = self.min_max.clip(value)
|
||||
max_ = self.min_max.max
|
||||
min_ = self.min_max.min
|
||||
step = self._compute_step()
|
||||
|
||||
new_value = (
|
||||
((value + Stepper.apply_sign(step, direction)) - min_) % (max_ - min_)
|
||||
) + min_
|
||||
new_value = round(new_value, 3)
|
||||
return StepperOutput(new_value, next_direction=direction)
|
||||
@@ -1,26 +0,0 @@
|
||||
from cx_const import Number, StepperDir
|
||||
from cx_core.stepper import Stepper, StepperOutput
|
||||
|
||||
|
||||
class StopStepper(Stepper):
|
||||
def get_direction(self, value: Number, direction: str) -> str:
|
||||
value = self.min_max.clip(value)
|
||||
if direction == StepperDir.TOGGLE and self.min_max.in_min_boundaries(value):
|
||||
self.previous_direction = StepperDir.UP
|
||||
return self.previous_direction
|
||||
if direction == StepperDir.TOGGLE and self.min_max.in_max_boundaries(value):
|
||||
self.previous_direction = StepperDir.DOWN
|
||||
return self.previous_direction
|
||||
return super().get_direction(value, direction)
|
||||
|
||||
def step(self, value: Number, direction: str) -> StepperOutput:
|
||||
value = self.min_max.clip(value)
|
||||
step = self._compute_step()
|
||||
|
||||
new_value = value + Stepper.apply_sign(step, direction)
|
||||
new_value = round(new_value, 3)
|
||||
if self.min_max.is_between(new_value):
|
||||
return StepperOutput(new_value, next_direction=direction)
|
||||
else:
|
||||
new_value = self.min_max.clip(new_value)
|
||||
return StepperOutput(new_value, next_direction=None)
|
||||
@@ -1,125 +0,0 @@
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
|
||||
from cx_const import Cover, PredefinedActionsMapping
|
||||
from cx_core.controller import action
|
||||
from cx_core.feature_support.cover import CoverSupport
|
||||
from cx_core.type_controller import Entity, TypeController
|
||||
|
||||
|
||||
class CoverController(TypeController[Entity]):
|
||||
"""
|
||||
This is the main class that controls the coveres for different devices.
|
||||
Type of actions:
|
||||
- Open
|
||||
- Close
|
||||
Parameters taken:
|
||||
- controller (required): Inherited from Controller
|
||||
- cover (required): cover entity name
|
||||
- open_position (optional): The open position. Default is 100
|
||||
- close_position (optional): The close position. Default is 0
|
||||
"""
|
||||
|
||||
domains = ["cover"]
|
||||
entity_arg = "cover"
|
||||
|
||||
open_position: int
|
||||
close_position: int
|
||||
|
||||
cover_duration: int | None
|
||||
|
||||
is_supposedly_moving: bool = False
|
||||
stop_timer_handle: str | None = None
|
||||
|
||||
async def init(self) -> None:
|
||||
self.open_position = self.args.get("open_position", 100)
|
||||
self.close_position = self.args.get("close_position", 0)
|
||||
self.cover_duration = self.args.get("cover_duration")
|
||||
if self.open_position < self.close_position:
|
||||
raise ValueError("`open_position` must be higher than `close_position`")
|
||||
await super().init()
|
||||
|
||||
def _get_entity_type(self) -> type[Entity]:
|
||||
return Entity
|
||||
|
||||
def get_predefined_actions_mapping(self) -> PredefinedActionsMapping:
|
||||
return {
|
||||
Cover.OPEN: self.open,
|
||||
Cover.CLOSE: self.close,
|
||||
Cover.STOP: self.stop,
|
||||
Cover.TOGGLE_OPEN: (self.toggle, (self.open,)),
|
||||
Cover.TOGGLE_CLOSE: (self.toggle, (self.close,)),
|
||||
}
|
||||
|
||||
async def cover_stopped_cb(self, kwargs: dict[str, Any]) -> None:
|
||||
self.is_supposedly_moving = False
|
||||
self.stop_timer_handle = None
|
||||
|
||||
async def start_timer(self) -> None:
|
||||
if self.cover_duration is None:
|
||||
return
|
||||
await self.stop_timer()
|
||||
self.is_supposedly_moving = True
|
||||
self.stop_timer_handle = await self.run_in(
|
||||
self.cover_stopped_cb, self.cover_duration
|
||||
)
|
||||
|
||||
async def stop_timer(self) -> None:
|
||||
if self.stop_timer_handle is not None:
|
||||
self.is_supposedly_moving = False
|
||||
await self.cancel_timer(self.stop_timer_handle)
|
||||
|
||||
@action
|
||||
async def open(self) -> None:
|
||||
if await self.feature_support.is_supported(CoverSupport.SET_COVER_POSITION):
|
||||
await self.call_service(
|
||||
"cover/set_cover_position",
|
||||
entity_id=self.entity.name,
|
||||
position=self.open_position,
|
||||
)
|
||||
elif await self.feature_support.is_supported(CoverSupport.OPEN):
|
||||
await self.call_service("cover/open_cover", entity_id=self.entity.name)
|
||||
else:
|
||||
self.log(
|
||||
f"⚠️ `{self.entity}` does not support SET_COVER_POSITION or OPEN",
|
||||
level="WARNING",
|
||||
ascii_encode=False,
|
||||
)
|
||||
return
|
||||
await self.start_timer()
|
||||
|
||||
@action
|
||||
async def close(self) -> None:
|
||||
if await self.feature_support.is_supported(CoverSupport.SET_COVER_POSITION):
|
||||
await self.call_service(
|
||||
"cover/set_cover_position",
|
||||
entity_id=self.entity.name,
|
||||
position=self.close_position,
|
||||
)
|
||||
elif await self.feature_support.is_supported(CoverSupport.CLOSE):
|
||||
await self.call_service("cover/close_cover", entity_id=self.entity.name)
|
||||
else:
|
||||
self.log(
|
||||
f"⚠️ `{self.entity}` does not support SET_COVER_POSITION or CLOSE",
|
||||
level="WARNING",
|
||||
ascii_encode=False,
|
||||
)
|
||||
return
|
||||
await self.start_timer()
|
||||
|
||||
@action
|
||||
async def stop(self) -> None:
|
||||
await self.stop_timer()
|
||||
await self.call_service("cover/stop_cover", entity_id=self.entity.name)
|
||||
|
||||
@action
|
||||
async def toggle(self, action: Callable[[], Awaitable[None]]) -> None:
|
||||
cover_state = await self.get_entity_state()
|
||||
if (
|
||||
cover_state == "opening"
|
||||
or cover_state == "closing"
|
||||
or self.is_supposedly_moving
|
||||
):
|
||||
await self.stop()
|
||||
else:
|
||||
await action()
|
||||
@@ -1,958 +0,0 @@
|
||||
import asyncio
|
||||
from functools import lru_cache
|
||||
from typing import Any, Literal
|
||||
|
||||
from cx_const import Light, Number, PredefinedActionsMapping, StepperDir, StepperMode
|
||||
from cx_core.color_helper import Color, get_color_wheel
|
||||
from cx_core.controller import action
|
||||
from cx_core.feature_support.light import LightSupport
|
||||
from cx_core.integration import EventData
|
||||
from cx_core.integration.deconz import DeCONZIntegration
|
||||
from cx_core.integration.z2m import Z2MIntegration
|
||||
from cx_core.integration.zha import ZHAIntegration
|
||||
from cx_core.release_hold_controller import ReleaseHoldController
|
||||
from cx_core.stepper import MinMax, Stepper
|
||||
from cx_core.stepper.bounce_stepper import BounceStepper
|
||||
from cx_core.stepper.index_loop_stepper import IndexLoopStepper
|
||||
from cx_core.stepper.loop_stepper import LoopStepper
|
||||
from cx_core.stepper.stop_stepper import StopStepper
|
||||
from cx_core.type_controller import Entity, TypeController
|
||||
|
||||
DEFAULT_MANUAL_STEPS = 10
|
||||
DEFAULT_AUTOMATIC_STEPS = 10
|
||||
DEFAULT_MIN_BRIGHTNESS = 1
|
||||
DEFAULT_MAX_BRIGHTNESS = 255
|
||||
DEFAULT_MIN_WHITE_VALUE = 1
|
||||
DEFAULT_MAX_WHITE_VALUE = 255
|
||||
DEFAULT_MIN_COLOR_TEMP = 153
|
||||
DEFAULT_MAX_COLOR_TEMP = 500
|
||||
DEFAULT_COLOR_TEMP = (
|
||||
DEFAULT_MAX_COLOR_TEMP - DEFAULT_MIN_COLOR_TEMP
|
||||
) // 2 + DEFAULT_MIN_COLOR_TEMP
|
||||
DEFAULT_TRANSITION = 300
|
||||
DEFAULT_ADD_TRANSITION = True
|
||||
DEFAULT_TRANSITION_TURN_TOGGLE = False
|
||||
DEFAULT_HOLD_TOGGLE_DIRECTION_INIT = "up"
|
||||
|
||||
ColorMode = Literal["auto", "xy_color", "color_temp"]
|
||||
|
||||
COLOR_MODES = {"hs", "xy", "rgb", "rgbw", "rgbww"}
|
||||
STEPPER_MODES: dict[str, type[Stepper]] = {
|
||||
StepperMode.STOP: StopStepper,
|
||||
StepperMode.LOOP: LoopStepper,
|
||||
StepperMode.BOUNCE: BounceStepper,
|
||||
}
|
||||
|
||||
|
||||
class LightEntity(Entity):
|
||||
color_mode: ColorMode
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
entities: list[str] | None = None,
|
||||
color_mode: ColorMode = "auto",
|
||||
) -> None:
|
||||
super().__init__(name, entities)
|
||||
self.color_mode = color_mode
|
||||
|
||||
|
||||
class LightController(TypeController[LightEntity], ReleaseHoldController):
|
||||
"""
|
||||
This is the main class that controls the lights for different devices.
|
||||
Type of actions:
|
||||
- On/Off/Toggle
|
||||
- Brightness click and hold
|
||||
- Color temperature click and hold
|
||||
- xy color click and hold
|
||||
If a light supports xy_color and color_temperature, then xy_color will be the
|
||||
default functionality. Parameters taken:
|
||||
- controller (required): Inherited from Controller
|
||||
- light (required): This is either the light entity name or a dictionary as
|
||||
{name: string, color_mode: auto | xy_color | color_temp}
|
||||
- delay (optional): Inherited from ReleaseHoldController
|
||||
- manual_steps (optional): Number of steps to go from min to max when clicking.
|
||||
- automatic_steps (optional): Number of steps to go from min to max when smoothing.
|
||||
"""
|
||||
|
||||
ATTRIBUTE_BRIGHTNESS = "brightness"
|
||||
ATTRIBUTE_WHITE_VALUE = "white_value"
|
||||
# With the following attribute, it will select color_temp or xy_color, depending on the light.
|
||||
ATTRIBUTE_COLOR = "color"
|
||||
ATTRIBUTE_COLOR_TEMP = "color_temp"
|
||||
ATTRIBUTE_XY_COLOR = "xy_color"
|
||||
|
||||
ATTRIBUTES_LIST = [
|
||||
ATTRIBUTE_BRIGHTNESS,
|
||||
ATTRIBUTE_WHITE_VALUE,
|
||||
ATTRIBUTE_COLOR,
|
||||
ATTRIBUTE_COLOR_TEMP,
|
||||
ATTRIBUTE_XY_COLOR,
|
||||
]
|
||||
|
||||
index_color = 0
|
||||
value_attribute = None
|
||||
|
||||
# These are intermediate variables to store the checked value
|
||||
smooth_power_on_check: bool
|
||||
remove_transition_check: bool
|
||||
next_direction: str | None = None
|
||||
|
||||
manual_steps: Number
|
||||
automatic_steps: Number
|
||||
min_max_attributes: dict[str, MinMax]
|
||||
|
||||
domains = ["light"]
|
||||
entity_arg = "light"
|
||||
|
||||
_supported_color_modes: set[str] | None
|
||||
|
||||
async def init(self) -> None:
|
||||
self.manual_steps = self.args.get("manual_steps", DEFAULT_MANUAL_STEPS)
|
||||
self.automatic_steps = self.args.get("automatic_steps", DEFAULT_AUTOMATIC_STEPS)
|
||||
|
||||
self.min_max_attributes = {
|
||||
self.ATTRIBUTE_BRIGHTNESS: MinMax(
|
||||
self.args.get("min_brightness", DEFAULT_MIN_BRIGHTNESS),
|
||||
self.args.get("max_brightness", DEFAULT_MAX_BRIGHTNESS),
|
||||
),
|
||||
self.ATTRIBUTE_WHITE_VALUE: MinMax(
|
||||
self.args.get("min_white_value", DEFAULT_MIN_WHITE_VALUE),
|
||||
self.args.get("max_white_value", DEFAULT_MAX_WHITE_VALUE),
|
||||
),
|
||||
self.ATTRIBUTE_COLOR_TEMP: MinMax(
|
||||
self.args.get("min_color_temp", DEFAULT_MIN_COLOR_TEMP),
|
||||
self.args.get("max_color_temp", DEFAULT_MAX_COLOR_TEMP),
|
||||
),
|
||||
}
|
||||
|
||||
self.transition = self.args.get("transition", DEFAULT_TRANSITION)
|
||||
self.color_wheel = get_color_wheel(
|
||||
self.args.get("color_wheel", "default_color_wheel")
|
||||
)
|
||||
self._supported_color_modes = self.args.get("supported_color_modes")
|
||||
|
||||
self.smooth_power_on = self.args.get(
|
||||
"smooth_power_on", self.supports_smooth_power_on()
|
||||
)
|
||||
self.add_transition = self.args.get("add_transition", DEFAULT_ADD_TRANSITION)
|
||||
self.add_transition_turn_toggle = self.args.get(
|
||||
"add_transition_turn_toggle", DEFAULT_TRANSITION_TURN_TOGGLE
|
||||
)
|
||||
self.hold_toggle_direction_init = self.get_option(
|
||||
self.args.get(
|
||||
"hold_toggle_direction_init", DEFAULT_HOLD_TOGGLE_DIRECTION_INIT
|
||||
),
|
||||
[StepperDir.UP, StepperDir.DOWN],
|
||||
"`hold_toggle_direction_init`",
|
||||
)
|
||||
await super().init()
|
||||
|
||||
def _get_entity_type(self) -> type[LightEntity]:
|
||||
return LightEntity
|
||||
|
||||
def get_predefined_actions_mapping(self) -> PredefinedActionsMapping:
|
||||
return {
|
||||
Light.ON: self.on,
|
||||
Light.OFF: self.off,
|
||||
Light.TOGGLE: self.toggle,
|
||||
Light.TOGGLE_FULL_BRIGHTNESS: (
|
||||
self.toggle_full,
|
||||
(LightController.ATTRIBUTE_BRIGHTNESS,),
|
||||
),
|
||||
Light.TOGGLE_FULL_WHITE_VALUE: (
|
||||
self.toggle_full,
|
||||
(LightController.ATTRIBUTE_WHITE_VALUE,),
|
||||
),
|
||||
Light.TOGGLE_FULL_COLOR_TEMP: (
|
||||
self.toggle_full,
|
||||
(LightController.ATTRIBUTE_COLOR_TEMP,),
|
||||
),
|
||||
Light.TOGGLE_MIN_BRIGHTNESS: (
|
||||
self.toggle_min,
|
||||
(LightController.ATTRIBUTE_BRIGHTNESS,),
|
||||
),
|
||||
Light.TOGGLE_MIN_WHITE_VALUE: (
|
||||
self.toggle_min,
|
||||
(LightController.ATTRIBUTE_WHITE_VALUE,),
|
||||
),
|
||||
Light.TOGGLE_MIN_COLOR_TEMP: (
|
||||
self.toggle_min,
|
||||
(LightController.ATTRIBUTE_COLOR_TEMP,),
|
||||
),
|
||||
Light.RELEASE: self.release,
|
||||
Light.ON_FULL_BRIGHTNESS: (
|
||||
self.on_full,
|
||||
(LightController.ATTRIBUTE_BRIGHTNESS,),
|
||||
),
|
||||
Light.ON_FULL_WHITE_VALUE: (
|
||||
self.on_full,
|
||||
(LightController.ATTRIBUTE_WHITE_VALUE,),
|
||||
),
|
||||
Light.ON_FULL_COLOR_TEMP: (
|
||||
self.on_full,
|
||||
(LightController.ATTRIBUTE_COLOR_TEMP,),
|
||||
),
|
||||
Light.ON_MIN_BRIGHTNESS: (
|
||||
self.on_min,
|
||||
(LightController.ATTRIBUTE_BRIGHTNESS,),
|
||||
),
|
||||
Light.ON_MIN_WHITE_VALUE: (
|
||||
self.on_min,
|
||||
(LightController.ATTRIBUTE_WHITE_VALUE,),
|
||||
),
|
||||
Light.ON_MIN_COLOR_TEMP: (
|
||||
self.on_min,
|
||||
(LightController.ATTRIBUTE_COLOR_TEMP,),
|
||||
),
|
||||
Light.ON_MIN_MAX_BRIGHTNESS: (
|
||||
self.on_min_max,
|
||||
(LightController.ATTRIBUTE_BRIGHTNESS,),
|
||||
),
|
||||
Light.ON_MAX_MIN_BRIGHTNESS: (
|
||||
self.on_max_min,
|
||||
(LightController.ATTRIBUTE_BRIGHTNESS,),
|
||||
),
|
||||
Light.ON_MIN_MAX_COLOR_TEMP: (
|
||||
self.on_min_max,
|
||||
(LightController.ATTRIBUTE_COLOR_TEMP,),
|
||||
),
|
||||
Light.ON_MAX_MIN_COLOR_TEMP: (
|
||||
self.on_max_min,
|
||||
(LightController.ATTRIBUTE_COLOR_TEMP,),
|
||||
),
|
||||
Light.SET_HALF_BRIGHTNESS: (
|
||||
self.set_value,
|
||||
(
|
||||
LightController.ATTRIBUTE_BRIGHTNESS,
|
||||
0.5,
|
||||
),
|
||||
),
|
||||
Light.SET_HALF_WHITE_VALUE: (
|
||||
self.set_value,
|
||||
(
|
||||
LightController.ATTRIBUTE_WHITE_VALUE,
|
||||
0.5,
|
||||
),
|
||||
),
|
||||
Light.SET_HALF_COLOR_TEMP: (
|
||||
self.set_value,
|
||||
(
|
||||
LightController.ATTRIBUTE_COLOR_TEMP,
|
||||
0.5,
|
||||
),
|
||||
),
|
||||
Light.SYNC: self.sync,
|
||||
Light.CLICK: self.click,
|
||||
Light.CLICK_BRIGHTNESS_UP: (
|
||||
self.click,
|
||||
(
|
||||
LightController.ATTRIBUTE_BRIGHTNESS,
|
||||
StepperDir.UP,
|
||||
),
|
||||
),
|
||||
Light.CLICK_BRIGHTNESS_DOWN: (
|
||||
self.click,
|
||||
(
|
||||
LightController.ATTRIBUTE_BRIGHTNESS,
|
||||
StepperDir.DOWN,
|
||||
),
|
||||
),
|
||||
Light.CLICK_WHITE_VALUE_UP: (
|
||||
self.click,
|
||||
(
|
||||
LightController.ATTRIBUTE_WHITE_VALUE,
|
||||
StepperDir.UP,
|
||||
),
|
||||
),
|
||||
Light.CLICK_WHITE_VALUE_DOWN: (
|
||||
self.click,
|
||||
(
|
||||
LightController.ATTRIBUTE_WHITE_VALUE,
|
||||
StepperDir.DOWN,
|
||||
),
|
||||
),
|
||||
Light.CLICK_COLOR_UP: (
|
||||
self.click,
|
||||
(
|
||||
LightController.ATTRIBUTE_COLOR,
|
||||
StepperDir.UP,
|
||||
),
|
||||
),
|
||||
Light.CLICK_COLOR_DOWN: (
|
||||
self.click,
|
||||
(
|
||||
LightController.ATTRIBUTE_COLOR,
|
||||
StepperDir.DOWN,
|
||||
),
|
||||
),
|
||||
Light.CLICK_COLOR_TEMP_UP: (
|
||||
self.click,
|
||||
(
|
||||
LightController.ATTRIBUTE_COLOR_TEMP,
|
||||
StepperDir.UP,
|
||||
),
|
||||
),
|
||||
Light.CLICK_COLOR_TEMP_DOWN: (
|
||||
self.click,
|
||||
(
|
||||
LightController.ATTRIBUTE_COLOR_TEMP,
|
||||
StepperDir.DOWN,
|
||||
),
|
||||
),
|
||||
Light.CLICK_XY_COLOR_UP: (
|
||||
self.click,
|
||||
(
|
||||
LightController.ATTRIBUTE_XY_COLOR,
|
||||
StepperDir.UP,
|
||||
),
|
||||
),
|
||||
Light.CLICK_XY_COLOR_DOWN: (
|
||||
self.click,
|
||||
(
|
||||
LightController.ATTRIBUTE_XY_COLOR,
|
||||
StepperDir.DOWN,
|
||||
),
|
||||
),
|
||||
Light.HOLD: self.hold,
|
||||
Light.HOLD_BRIGHTNESS_UP: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_BRIGHTNESS,
|
||||
StepperDir.UP,
|
||||
),
|
||||
),
|
||||
Light.HOLD_BRIGHTNESS_DOWN: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_BRIGHTNESS,
|
||||
StepperDir.DOWN,
|
||||
),
|
||||
),
|
||||
Light.HOLD_BRIGHTNESS_TOGGLE: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_BRIGHTNESS,
|
||||
StepperDir.TOGGLE,
|
||||
),
|
||||
),
|
||||
Light.HOLD_WHITE_VALUE_UP: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_WHITE_VALUE,
|
||||
StepperDir.UP,
|
||||
),
|
||||
),
|
||||
Light.HOLD_WHITE_VALUE_DOWN: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_WHITE_VALUE,
|
||||
StepperDir.DOWN,
|
||||
),
|
||||
),
|
||||
Light.HOLD_WHITE_VALUE_TOGGLE: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_WHITE_VALUE,
|
||||
StepperDir.TOGGLE,
|
||||
),
|
||||
),
|
||||
Light.HOLD_COLOR_UP: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_COLOR,
|
||||
StepperDir.UP,
|
||||
),
|
||||
),
|
||||
Light.HOLD_COLOR_DOWN: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_COLOR,
|
||||
StepperDir.DOWN,
|
||||
),
|
||||
),
|
||||
Light.HOLD_COLOR_TOGGLE: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_COLOR,
|
||||
StepperDir.TOGGLE,
|
||||
),
|
||||
),
|
||||
Light.HOLD_COLOR_TEMP_UP: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_COLOR_TEMP,
|
||||
StepperDir.UP,
|
||||
),
|
||||
),
|
||||
Light.HOLD_COLOR_TEMP_DOWN: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_COLOR_TEMP,
|
||||
StepperDir.DOWN,
|
||||
),
|
||||
),
|
||||
Light.HOLD_COLOR_TEMP_TOGGLE: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_COLOR_TEMP,
|
||||
StepperDir.TOGGLE,
|
||||
),
|
||||
),
|
||||
Light.HOLD_XY_COLOR_UP: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_XY_COLOR,
|
||||
StepperDir.UP,
|
||||
),
|
||||
),
|
||||
Light.HOLD_XY_COLOR_DOWN: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_XY_COLOR,
|
||||
StepperDir.DOWN,
|
||||
),
|
||||
),
|
||||
Light.HOLD_XY_COLOR_TOGGLE: (
|
||||
self.hold,
|
||||
(
|
||||
LightController.ATTRIBUTE_XY_COLOR,
|
||||
StepperDir.TOGGLE,
|
||||
),
|
||||
),
|
||||
Light.XYCOLOR_FROM_CONTROLLER: self.xycolor_from_controller,
|
||||
Light.COLORTEMP_FROM_CONTROLLER: self.colortemp_from_controller,
|
||||
Light.COLORTEMP_FROM_CONTROLLER_STEP: (
|
||||
self.attribute_from_controller_step,
|
||||
(LightController.ATTRIBUTE_COLOR_TEMP,),
|
||||
),
|
||||
Light.BRIGHTNESS_FROM_CONTROLLER_LEVEL: self.brightness_from_controller_level,
|
||||
Light.BRIGHTNESS_FROM_CONTROLLER_ANGLE: self.brightness_from_controller_angle,
|
||||
Light.BRIGHTNESS_FROM_CONTROLLER_STEP: (
|
||||
self.attribute_from_controller_step,
|
||||
(LightController.ATTRIBUTE_BRIGHTNESS,),
|
||||
),
|
||||
}
|
||||
|
||||
async def check_remove_transition(self, on_from_user: bool) -> bool:
|
||||
return (
|
||||
not self.add_transition
|
||||
or (on_from_user and not self.add_transition_turn_toggle)
|
||||
or await self.feature_support.not_supported(LightSupport.TRANSITION)
|
||||
)
|
||||
|
||||
async def call_light_service(
|
||||
self, service: str, force_transition: bool = False, **attributes: Any
|
||||
) -> None:
|
||||
if "transition" not in attributes:
|
||||
attributes["transition"] = self.transition / 1000
|
||||
if self.remove_transition_check and not force_transition:
|
||||
del attributes["transition"]
|
||||
await self.call_service(service, entity_id=self.entity.name, **attributes)
|
||||
|
||||
async def _on(self, **attributes: Any) -> None:
|
||||
await self.call_light_service("light/turn_on", **attributes)
|
||||
|
||||
@action
|
||||
async def on(self, attributes: dict[str, float] | None = None) -> None:
|
||||
attributes = {} if attributes is None else attributes
|
||||
await self._on(**attributes)
|
||||
|
||||
async def _off(self, **attributes: Any) -> None:
|
||||
await self.call_light_service("light/turn_off", **attributes)
|
||||
|
||||
@action
|
||||
async def off(self) -> None:
|
||||
await self._off()
|
||||
|
||||
async def _toggle(self, **attributes: Any) -> None:
|
||||
await self.call_light_service("light/toggle", **attributes)
|
||||
|
||||
@action
|
||||
async def toggle(self, attributes: dict[str, float] | None = None) -> None:
|
||||
attributes = {} if attributes is None else attributes
|
||||
await self._toggle(**attributes)
|
||||
|
||||
async def _set_value(self, attribute: str, fraction: float) -> None:
|
||||
fraction = max(0, min(fraction, 1))
|
||||
min_ = self.min_max_attributes[attribute].min
|
||||
max_ = self.min_max_attributes[attribute].max
|
||||
value = (max_ - min_) * fraction + min_
|
||||
await self._on(**{attribute: value})
|
||||
|
||||
@action
|
||||
async def set_value(self, attribute: str, fraction: float) -> None:
|
||||
await self._set_value(attribute, fraction)
|
||||
|
||||
@action
|
||||
async def toggle_full(self, attribute: str) -> None:
|
||||
await self._toggle(**{attribute: self.min_max_attributes[attribute].max})
|
||||
|
||||
@action
|
||||
async def toggle_min(self, attribute: str) -> None:
|
||||
await self._toggle(**{attribute: self.min_max_attributes[attribute].min})
|
||||
|
||||
async def _on_full(self, attribute: str) -> None:
|
||||
await self._set_value(attribute, 1)
|
||||
|
||||
@action
|
||||
async def on_full(self, attribute: str) -> None:
|
||||
await self._on_full(attribute)
|
||||
|
||||
async def _on_min(self, attribute: str) -> None:
|
||||
await self._set_value(attribute, 0)
|
||||
|
||||
@action
|
||||
async def on_min(self, attribute: str) -> None:
|
||||
await self._on_min(attribute)
|
||||
|
||||
@action
|
||||
async def on_min_max(self, attribute: str) -> None:
|
||||
min_ = self.min_max_attributes[attribute].min
|
||||
max_ = self.min_max_attributes[attribute].max
|
||||
await self._on_min_max(attribute, default=min_, other=max_)
|
||||
|
||||
@action
|
||||
async def on_max_min(self, attribute: str) -> None:
|
||||
min_ = self.min_max_attributes[attribute].min
|
||||
max_ = self.min_max_attributes[attribute].max
|
||||
await self._on_min_max(attribute, default=max_, other=min_)
|
||||
|
||||
async def _on_min_max(
|
||||
self, attribute: str, *, default: Number, other: Number
|
||||
) -> None:
|
||||
light_state: str
|
||||
attribute_value: Number
|
||||
light_state, attribute_value = await asyncio.gather(
|
||||
self.get_entity_state(), self.get_entity_state(attribute=attribute)
|
||||
)
|
||||
|
||||
if light_state == "off" or attribute_value != default:
|
||||
await self._on(**{attribute: default})
|
||||
else:
|
||||
await self._on(**{attribute: other})
|
||||
|
||||
@action
|
||||
async def sync(
|
||||
self,
|
||||
brightness: int | None = None,
|
||||
color_temp: int = 370, # 2700K light
|
||||
xy_color: Color = (0.323, 0.329), # white colour
|
||||
) -> None:
|
||||
attributes: dict[Any, Any] = {}
|
||||
try:
|
||||
color_attribute = await self.get_attribute(LightController.ATTRIBUTE_COLOR)
|
||||
if color_attribute == LightController.ATTRIBUTE_COLOR_TEMP:
|
||||
attributes[color_attribute] = color_temp
|
||||
else:
|
||||
attributes[color_attribute] = list(xy_color)
|
||||
except ValueError:
|
||||
self.log(
|
||||
"⚠️ `sync` action will only change brightness",
|
||||
level="WARNING",
|
||||
ascii_encode=False,
|
||||
)
|
||||
await self._on(
|
||||
**attributes,
|
||||
brightness=(
|
||||
brightness
|
||||
or self.min_max_attributes[LightController.ATTRIBUTE_BRIGHTNESS].max
|
||||
),
|
||||
)
|
||||
|
||||
@action
|
||||
async def xycolor_from_controller(self, extra: EventData | None = None) -> None:
|
||||
if extra is None:
|
||||
self.log("No event data present", level="WARNING")
|
||||
return
|
||||
if isinstance(self.integration, Z2MIntegration):
|
||||
if "action_color" not in extra:
|
||||
self.log(
|
||||
"`action_color` is not present in the MQTT payload", level="WARNING"
|
||||
)
|
||||
return
|
||||
xy_color = extra["action_color"]
|
||||
await self._on(xy_color=[xy_color["x"], xy_color["y"]])
|
||||
elif isinstance(self.integration, DeCONZIntegration):
|
||||
if "xy" not in extra:
|
||||
self.log("`xy` is not present in the deCONZ event", level="WARNING")
|
||||
return
|
||||
await self._on(xy_color=list(extra["xy"]))
|
||||
|
||||
@action
|
||||
async def colortemp_from_controller(self, extra: EventData | None = None) -> None:
|
||||
if extra is None:
|
||||
self.log("No event data present", level="WARNING")
|
||||
return
|
||||
if isinstance(self.integration, Z2MIntegration):
|
||||
if "action_color_temperature" not in extra:
|
||||
self.log(
|
||||
"`action_color_temperature` is not present in the MQTT payload",
|
||||
level="WARNING",
|
||||
)
|
||||
return
|
||||
await self._on(color_temp=extra["action_color_temperature"])
|
||||
|
||||
@action
|
||||
async def brightness_from_controller_level(
|
||||
self, extra: EventData | None = None
|
||||
) -> None:
|
||||
if extra is None:
|
||||
self.log("No event data present", level="WARNING")
|
||||
return
|
||||
if isinstance(self.integration, Z2MIntegration):
|
||||
if "action_level" not in extra:
|
||||
self.log(
|
||||
"`action_level` is not present in the MQTT payload",
|
||||
level="WARNING",
|
||||
)
|
||||
return
|
||||
await self._on(brightness=extra["action_level"])
|
||||
|
||||
@action
|
||||
async def attribute_from_controller_step(
|
||||
self, attribute: str, extra: EventData | None = None
|
||||
) -> None:
|
||||
if extra is None:
|
||||
self.log("No event data present", level="WARNING")
|
||||
return
|
||||
if isinstance(self.integration, ZHAIntegration):
|
||||
try:
|
||||
step_mode: int = extra["params"]["step_mode"]
|
||||
step_size: int = extra["params"]["step_size"]
|
||||
stepper = self.generate_stepper(
|
||||
attribute, step_size, StepperMode.STOP, relative_steps=False
|
||||
)
|
||||
direction = StepperDir.UP
|
||||
if (
|
||||
attribute == LightController.ATTRIBUTE_BRIGHTNESS and step_mode == 1
|
||||
) or (
|
||||
attribute == LightController.ATTRIBUTE_COLOR_TEMP and step_mode == 3
|
||||
):
|
||||
direction = StepperDir.DOWN
|
||||
|
||||
# Transition time in seconds
|
||||
transition_time: int = extra["params"]["transition_time"]
|
||||
self.value_attribute = await self.get_value_attribute(attribute)
|
||||
extra_attributes = {
|
||||
"transition": transition_time,
|
||||
"force_transition": True,
|
||||
}
|
||||
await self.change_light_state(
|
||||
self.value_attribute,
|
||||
attribute,
|
||||
direction,
|
||||
stepper,
|
||||
extra_attributes=extra_attributes,
|
||||
)
|
||||
except (KeyError, TypeError):
|
||||
self.log(
|
||||
"`params` should be present in ZHA event with "
|
||||
"`step_mode`, `step_size`, and `transition_time`",
|
||||
level="WARNING",
|
||||
)
|
||||
|
||||
@action
|
||||
async def brightness_from_controller_angle(
|
||||
self,
|
||||
mode: str = StepperMode.STOP,
|
||||
steps: Number | None = None,
|
||||
extra: EventData | None = None,
|
||||
) -> None:
|
||||
if extra is None:
|
||||
self.log("No event data present", level="WARNING")
|
||||
return
|
||||
if isinstance(self.integration, Z2MIntegration):
|
||||
if "action_rotation_angle" not in extra:
|
||||
self.log(
|
||||
"`action_rotation_angle` is not present in the MQTT payload",
|
||||
level="WARNING",
|
||||
)
|
||||
return
|
||||
angle = extra["action_rotation_angle"]
|
||||
direction = StepperDir.UP if angle > 0 else StepperDir.DOWN
|
||||
await self._hold(
|
||||
LightController.ATTRIBUTE_BRIGHTNESS, direction, mode=mode, steps=steps
|
||||
)
|
||||
|
||||
@property
|
||||
async def supported_color_modes(self) -> set[str]:
|
||||
if self._supported_color_modes is None or self.update_supported_features:
|
||||
supported_color_modes: list[str] = await self.get_entity_state(
|
||||
attribute="supported_color_modes"
|
||||
)
|
||||
if supported_color_modes is not None:
|
||||
self._supported_color_modes = set(supported_color_modes)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"`supported_color_modes` could not be read from `{self.entity}`. "
|
||||
"Entity might not be available."
|
||||
)
|
||||
|
||||
return self._supported_color_modes
|
||||
|
||||
async def is_color_supported(self) -> bool:
|
||||
return len(COLOR_MODES.intersection(await self.supported_color_modes)) > 0
|
||||
|
||||
async def is_colortemp_supported(self) -> bool:
|
||||
return "color_temp" in await self.supported_color_modes
|
||||
|
||||
def generate_stepper(
|
||||
self, attribute: str, steps: Number, mode: str, *, relative_steps: bool = True
|
||||
) -> Stepper:
|
||||
previous_direction = Stepper.invert_direction(self.hold_toggle_direction_init)
|
||||
if attribute == LightController.ATTRIBUTE_XY_COLOR:
|
||||
return IndexLoopStepper(
|
||||
len(self.color_wheel),
|
||||
previous_direction,
|
||||
relative_steps,
|
||||
)
|
||||
if mode not in STEPPER_MODES:
|
||||
raise ValueError(
|
||||
f"`{mode}` mode is not available. Options are: {list(STEPPER_MODES.keys())}"
|
||||
)
|
||||
stepper_cls = STEPPER_MODES[mode]
|
||||
return stepper_cls(
|
||||
self.min_max_attributes[attribute],
|
||||
steps,
|
||||
previous_direction,
|
||||
relative_steps,
|
||||
)
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def get_stepper(
|
||||
self, attribute: str, steps: Number, mode: str, *, tag: str
|
||||
) -> Stepper:
|
||||
return self.generate_stepper(attribute, steps, mode)
|
||||
|
||||
async def get_attribute(self, attribute: str) -> str:
|
||||
if attribute == LightController.ATTRIBUTE_COLOR:
|
||||
if self.entity.color_mode == "auto":
|
||||
if await self.is_color_supported():
|
||||
return LightController.ATTRIBUTE_XY_COLOR
|
||||
elif await self.is_colortemp_supported():
|
||||
return LightController.ATTRIBUTE_COLOR_TEMP
|
||||
else:
|
||||
raise ValueError(
|
||||
"This light does not support xy_color or color_temp"
|
||||
)
|
||||
else:
|
||||
return self.entity.color_mode
|
||||
else:
|
||||
return attribute
|
||||
|
||||
async def get_value_attribute(self, attribute: str) -> Number:
|
||||
if self.smooth_power_on_check:
|
||||
return 0
|
||||
if attribute == LightController.ATTRIBUTE_XY_COLOR:
|
||||
return 0
|
||||
elif (
|
||||
attribute == LightController.ATTRIBUTE_BRIGHTNESS
|
||||
or attribute == LightController.ATTRIBUTE_WHITE_VALUE
|
||||
or attribute == LightController.ATTRIBUTE_COLOR_TEMP
|
||||
):
|
||||
value = await self.get_entity_state(attribute=attribute)
|
||||
if value is None and attribute == LightController.ATTRIBUTE_COLOR_TEMP:
|
||||
return float(DEFAULT_COLOR_TEMP)
|
||||
elif value is None and attribute != LightController.ATTRIBUTE_COLOR_TEMP:
|
||||
raise ValueError(
|
||||
f"Value for `{attribute}` attribute could not be retrieved "
|
||||
f"from `{self.entity.main}`. "
|
||||
"Check the FAQ to know more about this error: "
|
||||
"https://xaviml.github.io/controllerx/faq"
|
||||
)
|
||||
else:
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f"Attribute `{attribute}` with `{value}` as a value "
|
||||
"could not be converted to float"
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Attribute `{attribute}` not expected")
|
||||
|
||||
def check_smooth_power_on(
|
||||
self, attribute: str, direction: str, light_state: str
|
||||
) -> bool:
|
||||
return (
|
||||
direction != StepperDir.DOWN
|
||||
and attribute == self.ATTRIBUTE_BRIGHTNESS
|
||||
and self.smooth_power_on
|
||||
and light_state == "off"
|
||||
)
|
||||
|
||||
async def before_action(self, action: str, *args: Any, **kwargs: Any) -> bool:
|
||||
to_return = True
|
||||
self.next_direction = None
|
||||
light_state: str
|
||||
if action in ("click", "hold"):
|
||||
if len(args) == 2:
|
||||
attribute, direction = args
|
||||
elif "attribute" in kwargs and "direction" in kwargs:
|
||||
attribute, direction = kwargs["attribute"], kwargs["direction"]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"`attribute` and `direction` are mandatory fields for `{action}` action"
|
||||
)
|
||||
light_state = await self.get_entity_state()
|
||||
self.smooth_power_on_check = self.check_smooth_power_on(
|
||||
attribute, direction, light_state
|
||||
)
|
||||
self.remove_transition_check = await self.check_remove_transition(
|
||||
on_from_user=False
|
||||
)
|
||||
to_return = (light_state == "on") or self.smooth_power_on_check
|
||||
elif action == "attribute_from_controller_step":
|
||||
light_state = await self.get_entity_state()
|
||||
to_return = light_state == "on"
|
||||
self.smooth_power_on_check = False
|
||||
self.remove_transition_check = False
|
||||
else:
|
||||
self.remove_transition_check = await self.check_remove_transition(
|
||||
on_from_user=True
|
||||
)
|
||||
self.smooth_power_on_check = False
|
||||
return await super().before_action(action, *args, **kwargs) and to_return
|
||||
|
||||
@action
|
||||
async def click(
|
||||
self,
|
||||
attribute: str,
|
||||
direction: str,
|
||||
mode: str = StepperMode.STOP,
|
||||
steps: Number | None = None,
|
||||
) -> None:
|
||||
attribute = self.get_option(
|
||||
attribute, LightController.ATTRIBUTES_LIST, "`click` action"
|
||||
)
|
||||
direction = self.get_option(
|
||||
direction, [StepperDir.UP, StepperDir.DOWN], "`click` action"
|
||||
)
|
||||
mode = self.get_option(
|
||||
mode, [StepperMode.STOP, StepperMode.LOOP], "`click` action"
|
||||
)
|
||||
attribute = await self.get_attribute(attribute)
|
||||
self.value_attribute = await self.get_value_attribute(attribute)
|
||||
await self.change_light_state(
|
||||
self.value_attribute,
|
||||
attribute,
|
||||
direction,
|
||||
self.get_stepper(attribute, steps or self.manual_steps, mode, tag="click"),
|
||||
)
|
||||
|
||||
@action
|
||||
async def hold( # type: ignore[override]
|
||||
self,
|
||||
attribute: str,
|
||||
direction: str,
|
||||
mode: str = StepperMode.STOP,
|
||||
steps: Number | None = None,
|
||||
) -> None:
|
||||
await self._hold(attribute, direction, mode, steps)
|
||||
|
||||
async def _hold(
|
||||
self,
|
||||
attribute: str,
|
||||
direction: str,
|
||||
mode: str = StepperMode.STOP,
|
||||
steps: Number | None = None,
|
||||
) -> None:
|
||||
attribute = self.get_option(
|
||||
attribute, LightController.ATTRIBUTES_LIST, "`hold` action"
|
||||
)
|
||||
direction = self.get_option(
|
||||
direction,
|
||||
[StepperDir.UP, StepperDir.DOWN, StepperDir.TOGGLE],
|
||||
"`hold` action",
|
||||
)
|
||||
mode = self.get_option(
|
||||
mode,
|
||||
[StepperMode.STOP, StepperMode.LOOP, StepperMode.BOUNCE],
|
||||
"`hold` action",
|
||||
)
|
||||
attribute = await self.get_attribute(attribute)
|
||||
self.value_attribute = await self.get_value_attribute(attribute)
|
||||
self.log(
|
||||
f"Attribute value before running the hold action: {self.value_attribute}",
|
||||
level="DEBUG",
|
||||
)
|
||||
stepper = self.get_stepper(
|
||||
attribute, steps or self.automatic_steps, mode, tag="hold"
|
||||
)
|
||||
if direction == StepperDir.TOGGLE:
|
||||
self.log(
|
||||
f"Previous direction: {stepper.previous_direction}",
|
||||
level="DEBUG",
|
||||
)
|
||||
direction = stepper.get_direction(self.value_attribute, direction)
|
||||
self.log(f"Going direction: {direction}", level="DEBUG")
|
||||
await super().hold(attribute, direction, stepper)
|
||||
|
||||
async def hold_loop(
|
||||
self,
|
||||
attribute: str,
|
||||
direction: str,
|
||||
stepper: Stepper,
|
||||
) -> bool:
|
||||
if self.value_attribute is None:
|
||||
return True
|
||||
extra_attributes = {"transition": self.delay / 1000}
|
||||
return await self.change_light_state(
|
||||
self.value_attribute,
|
||||
attribute,
|
||||
direction,
|
||||
stepper,
|
||||
extra_attributes=extra_attributes,
|
||||
)
|
||||
|
||||
async def change_light_state(
|
||||
self,
|
||||
old: float,
|
||||
attribute: str,
|
||||
direction: str,
|
||||
stepper: Stepper,
|
||||
*,
|
||||
extra_attributes: dict[str, Any] | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
This functions changes the state of the light depending on the previous
|
||||
value and attribute. It returns True when no more changes will need to be done.
|
||||
Otherwise, it returns False.
|
||||
"""
|
||||
attributes: dict[str, Any] = (
|
||||
{} if extra_attributes is None else extra_attributes
|
||||
)
|
||||
direction = self.next_direction or direction
|
||||
if attribute == LightController.ATTRIBUTE_XY_COLOR:
|
||||
stepper_output = stepper.step(self.index_color, direction)
|
||||
self.index_color = int(stepper_output.next_value)
|
||||
xy_color = self.color_wheel[self.index_color]
|
||||
attributes[attribute] = list(xy_color)
|
||||
await self._on(**attributes)
|
||||
# In case of xy_color mode it never finishes the loop, the hold loop
|
||||
# will only stop if the hold action is called when releasing the button.
|
||||
# I haven't experimented any problems with it, but a future implementation
|
||||
# would be to force the loop to stop after 4 or 5 loops as a safety measure.
|
||||
return False
|
||||
if self.smooth_power_on_check:
|
||||
await self._on_min(attribute)
|
||||
# # After smooth power on, the light should not brighten up.
|
||||
return True
|
||||
stepper_output = stepper.step(old, direction)
|
||||
self.next_direction = stepper_output.next_direction
|
||||
next_value = int(stepper_output.next_value)
|
||||
attributes[attribute] = next_value
|
||||
await self._on(**attributes)
|
||||
self.value_attribute = next_value
|
||||
return stepper_output.exceeded
|
||||
|
||||
def supports_smooth_power_on(self) -> bool:
|
||||
"""
|
||||
This function can be overrided for each device to indicate the default behaviour of the controller
|
||||
when the associated light is off and an event for incrementing brightness is received.
|
||||
Returns True if the associated light should be turned on with minimum brightness if an event for incrementing
|
||||
brightness is received, while the lamp is off.
|
||||
The behaviour can be overridden by the user with the 'smooth_power_on' option in app configuration.
|
||||
"""
|
||||
return False
|
||||
@@ -1,196 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
from cx_const import MediaPlayer, Number, PredefinedActionsMapping, StepperDir
|
||||
from cx_core.controller import action
|
||||
from cx_core.feature_support.media_player import MediaPlayerSupport
|
||||
from cx_core.integration import EventData
|
||||
from cx_core.integration.z2m import Z2MIntegration
|
||||
from cx_core.release_hold_controller import ReleaseHoldController
|
||||
from cx_core.stepper import MinMax
|
||||
from cx_core.stepper.index_loop_stepper import IndexLoopStepper
|
||||
from cx_core.stepper.stop_stepper import StopStepper
|
||||
from cx_core.type_controller import Entity, TypeController
|
||||
|
||||
DEFAULT_VOLUME_STEPS = 10
|
||||
|
||||
|
||||
class MediaPlayerController(TypeController[Entity], ReleaseHoldController):
|
||||
domains = ["media_player"]
|
||||
entity_arg = "media_player"
|
||||
volume_level: float
|
||||
|
||||
async def init(self) -> None:
|
||||
volume_steps = self.args.get("volume_steps", DEFAULT_VOLUME_STEPS)
|
||||
self.volume_stepper = StopStepper(MinMax(0, 1), volume_steps)
|
||||
self.volume_level = 0.0
|
||||
await super().init()
|
||||
|
||||
def _get_entity_type(self) -> type[Entity]:
|
||||
return Entity
|
||||
|
||||
def get_predefined_actions_mapping(self) -> PredefinedActionsMapping:
|
||||
return {
|
||||
MediaPlayer.HOLD_VOLUME_DOWN: (self.hold, (StepperDir.DOWN,)),
|
||||
MediaPlayer.HOLD_VOLUME_UP: (self.hold, (StepperDir.UP,)),
|
||||
MediaPlayer.CLICK_VOLUME_DOWN: self.volume_down,
|
||||
MediaPlayer.CLICK_VOLUME_UP: self.volume_up,
|
||||
MediaPlayer.VOLUME_SET: self.volume_set,
|
||||
MediaPlayer.RELEASE: self.release,
|
||||
MediaPlayer.PLAY: self.play,
|
||||
MediaPlayer.PAUSE: self.pause,
|
||||
MediaPlayer.PLAY_PAUSE: self.play_pause,
|
||||
MediaPlayer.NEXT_TRACK: self.next_track,
|
||||
MediaPlayer.PREVIOUS_TRACK: self.previous_track,
|
||||
MediaPlayer.NEXT_SOURCE: (self.change_source_list, (StepperDir.UP,)),
|
||||
MediaPlayer.PREVIOUS_SOURCE: (self.change_source_list, (StepperDir.DOWN,)),
|
||||
MediaPlayer.MUTE: self.volume_mute,
|
||||
MediaPlayer.TTS: self.tts,
|
||||
MediaPlayer.VOLUME_FROM_CONTROLLER_ANGLE: self.volume_from_controller_angle,
|
||||
}
|
||||
|
||||
@action
|
||||
async def change_source_list(self, direction: str) -> None:
|
||||
entity_states = await self.get_entity_state(attribute="all")
|
||||
entity_attributes = entity_states["attributes"]
|
||||
source_list: list[str] | None = entity_attributes.get("source_list")
|
||||
if source_list is None or len(source_list) == 0:
|
||||
self.log(
|
||||
f"⚠️ There is no `source_list` parameter in `{self.entity}`",
|
||||
level="WARNING",
|
||||
ascii_encode=False,
|
||||
)
|
||||
return
|
||||
source = entity_attributes.get("source")
|
||||
new_index_source: Number
|
||||
if source is None:
|
||||
new_index_source = 0
|
||||
else:
|
||||
index_source = source_list.index(source)
|
||||
source_stepper = IndexLoopStepper(len(source_list))
|
||||
stepper_output = source_stepper.step(index_source, direction)
|
||||
new_index_source = stepper_output.next_value
|
||||
await self.call_service(
|
||||
"media_player/select_source",
|
||||
entity_id=self.entity.name,
|
||||
source=source_list[int(new_index_source)],
|
||||
)
|
||||
|
||||
@action
|
||||
async def play(self) -> None:
|
||||
await self.call_service("media_player/media_play", entity_id=self.entity.name)
|
||||
|
||||
@action
|
||||
async def pause(self) -> None:
|
||||
await self.call_service("media_player/media_pause", entity_id=self.entity.name)
|
||||
|
||||
@action
|
||||
async def play_pause(self) -> None:
|
||||
await self.call_service(
|
||||
"media_player/media_play_pause", entity_id=self.entity.name
|
||||
)
|
||||
|
||||
@action
|
||||
async def previous_track(self) -> None:
|
||||
await self.call_service(
|
||||
"media_player/media_previous_track", entity_id=self.entity.name
|
||||
)
|
||||
|
||||
@action
|
||||
async def next_track(self) -> None:
|
||||
await self.call_service(
|
||||
"media_player/media_next_track", entity_id=self.entity.name
|
||||
)
|
||||
|
||||
@action
|
||||
async def volume_set(self, volume_level: float) -> None:
|
||||
await self.call_service(
|
||||
"media_player/volume_set",
|
||||
entity_id=self.entity.name,
|
||||
volume_level=volume_level,
|
||||
)
|
||||
|
||||
@action
|
||||
async def volume_up(self) -> None:
|
||||
await self.prepare_volume_change()
|
||||
await self.volume_change(StepperDir.UP)
|
||||
|
||||
@action
|
||||
async def volume_down(self) -> None:
|
||||
await self.prepare_volume_change()
|
||||
await self.volume_change(StepperDir.DOWN)
|
||||
|
||||
@action
|
||||
async def volume_mute(self) -> None:
|
||||
await self.call_service("media_player/volume_mute", entity_id=self.entity.name)
|
||||
|
||||
@action
|
||||
async def tts(
|
||||
self,
|
||||
message: str,
|
||||
service: str = "google_translate_say",
|
||||
cache: bool | None = None,
|
||||
language: str | None = None,
|
||||
options: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
args: dict[str, Any] = {"entity_id": self.entity.name, "message": message}
|
||||
if cache is not None:
|
||||
args["cache"] = cache
|
||||
if language is not None:
|
||||
args["language"] = language
|
||||
if options is not None:
|
||||
args["options"] = options
|
||||
await self.call_service(f"tts.{service}", **args)
|
||||
|
||||
@action
|
||||
async def volume_from_controller_angle(
|
||||
self, extra: EventData | None = None
|
||||
) -> None:
|
||||
if extra is None:
|
||||
self.log("No event data present", level="WARNING")
|
||||
return
|
||||
if isinstance(self.integration, Z2MIntegration):
|
||||
if "action_rotation_angle" not in extra:
|
||||
self.log(
|
||||
"`action_rotation_angle` is not present in the MQTT payload",
|
||||
level="WARNING",
|
||||
)
|
||||
return
|
||||
angle = extra["action_rotation_angle"]
|
||||
direction = StepperDir.UP if angle > 0 else StepperDir.DOWN
|
||||
await self._hold(direction)
|
||||
|
||||
@action
|
||||
async def hold(self, direction: str) -> None: # type: ignore[override]
|
||||
await self._hold(direction)
|
||||
|
||||
async def _hold(self, direction: str) -> None:
|
||||
await self.prepare_volume_change()
|
||||
await super().hold(direction)
|
||||
|
||||
async def prepare_volume_change(self) -> None:
|
||||
volume_level = await self.get_entity_state(attribute="volume_level")
|
||||
if volume_level is not None:
|
||||
self.volume_level = volume_level
|
||||
|
||||
async def volume_change(self, direction: str) -> bool:
|
||||
if await self.feature_support.is_supported(MediaPlayerSupport.VOLUME_SET):
|
||||
stepper_output = self.volume_stepper.step(self.volume_level, direction)
|
||||
self.volume_level = stepper_output.next_value
|
||||
await self.volume_set(self.volume_level)
|
||||
return stepper_output.exceeded
|
||||
else:
|
||||
if direction == StepperDir.UP:
|
||||
await self.call_service(
|
||||
"media_player/volume_up", entity_id=self.entity.name
|
||||
)
|
||||
else:
|
||||
await self.call_service(
|
||||
"media_player/volume_down", entity_id=self.entity.name
|
||||
)
|
||||
return False
|
||||
|
||||
async def hold_loop(self, direction: str) -> bool:
|
||||
return await self.volume_change(direction)
|
||||
|
||||
def default_delay(self) -> int:
|
||||
return 500
|
||||
@@ -1,48 +0,0 @@
|
||||
from cx_const import PredefinedActionsMapping, Switch
|
||||
from cx_core.controller import action
|
||||
from cx_core.type_controller import Entity, TypeController
|
||||
|
||||
|
||||
class SwitchController(TypeController[Entity]):
|
||||
"""
|
||||
This is the main class that controls the switches for different devices.
|
||||
Type of actions:
|
||||
- On/Off/Toggle
|
||||
Parameters taken:
|
||||
- controller (required): Inherited from Controller
|
||||
- switch (required): Switch entity name
|
||||
"""
|
||||
|
||||
domains = [
|
||||
"switch",
|
||||
"alert",
|
||||
"automation",
|
||||
"cover",
|
||||
"input_boolean",
|
||||
"light",
|
||||
"media_player",
|
||||
"script",
|
||||
]
|
||||
entity_arg = "switch"
|
||||
|
||||
def get_predefined_actions_mapping(self) -> PredefinedActionsMapping:
|
||||
return {
|
||||
Switch.ON: self.on,
|
||||
Switch.OFF: self.off,
|
||||
Switch.TOGGLE: self.toggle,
|
||||
}
|
||||
|
||||
def _get_entity_type(self) -> type[Entity]:
|
||||
return Entity
|
||||
|
||||
@action
|
||||
async def on(self) -> None:
|
||||
await self.call_service("homeassistant/turn_on", entity_id=self.entity.name)
|
||||
|
||||
@action
|
||||
async def off(self) -> None:
|
||||
await self.call_service("homeassistant/turn_off", entity_id=self.entity.name)
|
||||
|
||||
@action
|
||||
async def toggle(self) -> None:
|
||||
await self.call_service("homeassistant/toggle", entity_id=self.entity.name)
|
||||
@@ -1,429 +0,0 @@
|
||||
import json
|
||||
from collections.abc import Awaitable, Callable
|
||||
from functools import lru_cache
|
||||
from typing import Annotated, Any, Literal
|
||||
|
||||
from cx_const import PredefinedActionsMapping, StepperDir, Z2MLight
|
||||
from cx_core.controller import Controller, action
|
||||
from cx_core.integration import EventData
|
||||
from cx_core.integration.z2m import Z2MIntegration
|
||||
from cx_core.stepper import InvertStepper, MinMax
|
||||
from cx_core.type_controller import Entity, TypeController
|
||||
|
||||
DEFAULT_CLICK_STEPS = 70
|
||||
DEFAULT_HOLD_STEPS = 70
|
||||
DEFAULT_TRANSITION = 0.5
|
||||
DEFAULT_MODE = "ha"
|
||||
DEFAULT_TOPIC_PREFIX = "zigbee2mqtt"
|
||||
|
||||
Mode = Annotated[str, Literal["ha", "mqtt"]]
|
||||
|
||||
|
||||
class Z2MLightEntity(Entity):
|
||||
mode: Mode
|
||||
topic_prefix: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
entities: list[str] | None = None,
|
||||
mode: Mode = DEFAULT_MODE,
|
||||
topic_prefix: str = DEFAULT_TOPIC_PREFIX,
|
||||
) -> None:
|
||||
super().__init__(name, entities)
|
||||
mode = Controller.get_option(mode, ["ha", "mqtt"])
|
||||
self.mode = mode
|
||||
self.topic_prefix = topic_prefix
|
||||
|
||||
|
||||
class Z2MLightController(TypeController[Z2MLightEntity]):
|
||||
"""
|
||||
This is the main class that controls the Zigbee2MQTT lights for different devices.
|
||||
Type of actions:
|
||||
- On/Off/Toggle
|
||||
- Brightness click and hold
|
||||
- Color temp click and hold
|
||||
"""
|
||||
|
||||
ATTRIBUTE_BRIGHTNESS = "brightness"
|
||||
ATTRIBUTE_COLOR_TEMP = "color_temp"
|
||||
|
||||
ATTRIBUTES_LIST = [
|
||||
ATTRIBUTE_BRIGHTNESS,
|
||||
ATTRIBUTE_COLOR_TEMP,
|
||||
]
|
||||
|
||||
MIN_MAX_ATTR = {
|
||||
ATTRIBUTE_BRIGHTNESS: MinMax(min=1, max=254),
|
||||
ATTRIBUTE_COLOR_TEMP: MinMax(min=250, max=454),
|
||||
}
|
||||
|
||||
entity_arg = "light"
|
||||
|
||||
click_steps: float
|
||||
hold_steps: float
|
||||
transition: float
|
||||
use_onoff: bool
|
||||
|
||||
hold_attribute: str | None
|
||||
|
||||
_mqtt_fn: dict[Mode, Callable[[str, str], Awaitable[None]]]
|
||||
|
||||
async def init(self) -> None:
|
||||
self.click_steps = self.args.get("click_steps", DEFAULT_CLICK_STEPS)
|
||||
self.hold_steps = self.args.get("hold_steps", DEFAULT_HOLD_STEPS)
|
||||
self.transition = self.args.get("transition", DEFAULT_TRANSITION)
|
||||
self.use_onoff = self.args.get("use_onoff", False)
|
||||
|
||||
self._mqtt_fn = {
|
||||
"ha": self._ha_mqtt_call,
|
||||
"mqtt": self._mqtt_plugin_call,
|
||||
}
|
||||
self.hold_attribute = None
|
||||
|
||||
await super().init()
|
||||
|
||||
def _get_entity_type(self) -> type[Z2MLightEntity]:
|
||||
return Z2MLightEntity
|
||||
|
||||
def get_predefined_actions_mapping(self) -> PredefinedActionsMapping:
|
||||
return {
|
||||
Z2MLight.ON: self.on,
|
||||
Z2MLight.OFF: self.off,
|
||||
Z2MLight.TOGGLE: self.toggle,
|
||||
Z2MLight.RELEASE: self.release,
|
||||
Z2MLight.ON_FULL_BRIGHTNESS: (
|
||||
self.on_full,
|
||||
(Z2MLightController.ATTRIBUTE_BRIGHTNESS,),
|
||||
),
|
||||
Z2MLight.ON_FULL_COLOR_TEMP: (
|
||||
self.on_full,
|
||||
(Z2MLightController.ATTRIBUTE_COLOR_TEMP,),
|
||||
),
|
||||
Z2MLight.ON_MIN_BRIGHTNESS: (
|
||||
self.on_min,
|
||||
(Z2MLightController.ATTRIBUTE_BRIGHTNESS,),
|
||||
),
|
||||
Z2MLight.ON_MIN_COLOR_TEMP: (
|
||||
self.on_min,
|
||||
(Z2MLightController.ATTRIBUTE_COLOR_TEMP,),
|
||||
),
|
||||
Z2MLight.SET_HALF_BRIGHTNESS: (
|
||||
self.set_value,
|
||||
(
|
||||
Z2MLightController.ATTRIBUTE_BRIGHTNESS,
|
||||
0.5,
|
||||
),
|
||||
),
|
||||
Z2MLight.SET_HALF_COLOR_TEMP: (
|
||||
self.set_value,
|
||||
(
|
||||
Z2MLightController.ATTRIBUTE_COLOR_TEMP,
|
||||
0.5,
|
||||
),
|
||||
),
|
||||
Z2MLight.CLICK: self.click,
|
||||
Z2MLight.CLICK_BRIGHTNESS_UP: (
|
||||
self.click,
|
||||
(
|
||||
Z2MLightController.ATTRIBUTE_BRIGHTNESS,
|
||||
StepperDir.UP,
|
||||
),
|
||||
),
|
||||
Z2MLight.CLICK_COLOR_TEMP_UP: (
|
||||
self.click,
|
||||
(
|
||||
Z2MLightController.ATTRIBUTE_COLOR_TEMP,
|
||||
StepperDir.UP,
|
||||
),
|
||||
),
|
||||
Z2MLight.CLICK_BRIGHTNESS_DOWN: (
|
||||
self.click,
|
||||
(
|
||||
Z2MLightController.ATTRIBUTE_BRIGHTNESS,
|
||||
StepperDir.DOWN,
|
||||
),
|
||||
),
|
||||
Z2MLight.CLICK_COLOR_TEMP_DOWN: (
|
||||
self.click,
|
||||
(
|
||||
Z2MLightController.ATTRIBUTE_COLOR_TEMP,
|
||||
StepperDir.DOWN,
|
||||
),
|
||||
),
|
||||
Z2MLight.HOLD: self.hold,
|
||||
Z2MLight.HOLD_BRIGHTNESS_UP: (
|
||||
self.hold,
|
||||
(
|
||||
Z2MLightController.ATTRIBUTE_BRIGHTNESS,
|
||||
StepperDir.UP,
|
||||
),
|
||||
),
|
||||
Z2MLight.HOLD_COLOR_TEMP_UP: (
|
||||
self.hold,
|
||||
(
|
||||
Z2MLightController.ATTRIBUTE_COLOR_TEMP,
|
||||
StepperDir.UP,
|
||||
),
|
||||
),
|
||||
Z2MLight.HOLD_BRIGHTNESS_DOWN: (
|
||||
self.hold,
|
||||
(
|
||||
Z2MLightController.ATTRIBUTE_BRIGHTNESS,
|
||||
StepperDir.DOWN,
|
||||
),
|
||||
),
|
||||
Z2MLight.HOLD_BRIGHTNESS_TOGGLE: (
|
||||
self.hold,
|
||||
(
|
||||
Z2MLightController.ATTRIBUTE_BRIGHTNESS,
|
||||
StepperDir.TOGGLE,
|
||||
),
|
||||
),
|
||||
Z2MLight.HOLD_COLOR_TEMP_DOWN: (
|
||||
self.hold,
|
||||
(
|
||||
Z2MLightController.ATTRIBUTE_COLOR_TEMP,
|
||||
StepperDir.DOWN,
|
||||
),
|
||||
),
|
||||
Z2MLight.HOLD_COLOR_TEMP_TOGGLE: (
|
||||
self.hold,
|
||||
(
|
||||
Z2MLightController.ATTRIBUTE_COLOR_TEMP,
|
||||
StepperDir.TOGGLE,
|
||||
),
|
||||
),
|
||||
Z2MLight.XYCOLOR_FROM_CONTROLLER: self.xycolor_from_controller,
|
||||
Z2MLight.COLORTEMP_FROM_CONTROLLER: self.colortemp_from_controller,
|
||||
Z2MLight.BRIGHTNESS_FROM_CONTROLLER_LEVEL: self.brightness_from_controller_level,
|
||||
Z2MLight.BRIGHTNESS_FROM_CONTROLLER_ANGLE: self.brightness_from_controller_angle,
|
||||
}
|
||||
|
||||
async def before_action(self, action: str, *args: Any, **kwargs: Any) -> bool:
|
||||
to_return = not (action == "hold" and self.hold_attribute is not None)
|
||||
return await super().before_action(action, *args, **kwargs) and to_return
|
||||
|
||||
async def _ha_mqtt_call(self, topic: str, payload: str) -> None:
|
||||
await self.call_service("mqtt.publish", topic=topic, payload=payload)
|
||||
|
||||
async def _mqtt_plugin_call(self, topic: str, payload: str) -> None:
|
||||
await self.call_service(
|
||||
"mqtt.publish", topic=topic, payload=payload, namespace="mqtt"
|
||||
)
|
||||
|
||||
async def _mqtt_call(self, payload: dict[str, Any]) -> None:
|
||||
await self._mqtt_fn[self.entity.mode](
|
||||
f"{self.entity.topic_prefix}/{self.entity.name}/set", json.dumps(payload)
|
||||
)
|
||||
|
||||
async def _on(self, **attributes: Any) -> None:
|
||||
await self._mqtt_call({"state": "ON", **attributes})
|
||||
|
||||
@action
|
||||
async def on(self, attributes: dict[str, float] | None = None) -> None:
|
||||
attributes = attributes or {}
|
||||
await self._on(**attributes)
|
||||
|
||||
async def _off(self) -> None:
|
||||
await self._mqtt_call({"state": "OFF"})
|
||||
|
||||
@action
|
||||
async def off(self) -> None:
|
||||
await self._off()
|
||||
|
||||
async def _toggle(self, **attributes: Any) -> None:
|
||||
await self._mqtt_call({"state": "TOGGLE", **attributes})
|
||||
|
||||
@action
|
||||
async def toggle(self, attributes: dict[str, float] | None = None) -> None:
|
||||
attributes = attributes or {}
|
||||
await self._toggle(**attributes)
|
||||
|
||||
async def _set_value(self, attribute: str, fraction: float) -> None:
|
||||
fraction = max(0, min(fraction, 1))
|
||||
min_ = self.MIN_MAX_ATTR[attribute].min
|
||||
max_ = self.MIN_MAX_ATTR[attribute].max
|
||||
value = (max_ - min_) * fraction + min_
|
||||
await self._on(**{attribute: value})
|
||||
|
||||
@action
|
||||
async def set_value(self, attribute: str, fraction: float) -> None:
|
||||
await self._set_value(attribute, fraction)
|
||||
|
||||
async def _on_full(self, attribute: str) -> None:
|
||||
await self._set_value(attribute, 1)
|
||||
|
||||
@action
|
||||
async def on_full(self, attribute: str) -> None:
|
||||
await self._on_full(attribute)
|
||||
|
||||
async def _on_min(self, attribute: str) -> None:
|
||||
await self._set_value(attribute, 0)
|
||||
|
||||
@action
|
||||
async def on_min(self, attribute: str) -> None:
|
||||
await self._on_min(attribute)
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def get_stepper(self, attribute: str, steps: float, *, tag: str) -> InvertStepper:
|
||||
previous_direction = StepperDir.DOWN
|
||||
return InvertStepper(self.MIN_MAX_ATTR[attribute], steps, previous_direction)
|
||||
|
||||
async def _change_light_state(
|
||||
self,
|
||||
*,
|
||||
attribute: str,
|
||||
direction: str,
|
||||
stepper: InvertStepper,
|
||||
transition: float | None,
|
||||
use_onoff: bool,
|
||||
mode: str,
|
||||
) -> None:
|
||||
onoff_cmd = (
|
||||
"_onoff" if use_onoff and attribute == self.ATTRIBUTE_BRIGHTNESS else ""
|
||||
)
|
||||
stepper_output = stepper.step(stepper.steps, direction)
|
||||
kwargs = {}
|
||||
if transition is not None:
|
||||
kwargs["transition"] = transition
|
||||
await self._mqtt_call(
|
||||
{
|
||||
f"{attribute}_{mode}{onoff_cmd}": stepper_output.next_value,
|
||||
**kwargs,
|
||||
}
|
||||
)
|
||||
|
||||
@action
|
||||
async def click(
|
||||
self,
|
||||
attribute: str,
|
||||
direction: str,
|
||||
steps: float | None = None,
|
||||
transition: float | None = None,
|
||||
use_onoff: bool | None = None,
|
||||
) -> None:
|
||||
attribute = self.get_option(attribute, self.ATTRIBUTES_LIST, "`click` action")
|
||||
direction = self.get_option(
|
||||
direction, [StepperDir.UP, StepperDir.DOWN], "`click` action"
|
||||
)
|
||||
steps = steps if steps is not None else self.click_steps
|
||||
stepper = self.get_stepper(attribute, steps, tag="click")
|
||||
await self._change_light_state(
|
||||
attribute=attribute,
|
||||
direction=direction,
|
||||
stepper=stepper,
|
||||
transition=transition if transition is not None else self.transition,
|
||||
use_onoff=use_onoff if use_onoff is not None else self.use_onoff,
|
||||
mode="step",
|
||||
)
|
||||
|
||||
async def _hold(
|
||||
self,
|
||||
attribute: str,
|
||||
direction: str,
|
||||
steps: float | None = None,
|
||||
use_onoff: bool | None = None,
|
||||
) -> None:
|
||||
attribute = self.get_option(attribute, self.ATTRIBUTES_LIST, "`hold` action")
|
||||
direction = self.get_option(
|
||||
direction,
|
||||
[StepperDir.UP, StepperDir.DOWN, StepperDir.TOGGLE],
|
||||
"`hold` action",
|
||||
)
|
||||
steps = steps if steps is not None else self.hold_steps
|
||||
stepper = self.get_stepper(attribute, steps, tag="hold")
|
||||
direction = stepper.get_direction(steps, direction)
|
||||
self.hold_attribute = attribute
|
||||
await self._change_light_state(
|
||||
attribute=attribute,
|
||||
direction=direction,
|
||||
stepper=stepper,
|
||||
transition=None,
|
||||
use_onoff=use_onoff if use_onoff is not None else self.use_onoff,
|
||||
mode="move",
|
||||
)
|
||||
|
||||
@action
|
||||
async def hold(
|
||||
self,
|
||||
attribute: str,
|
||||
direction: str,
|
||||
steps: float | None = None,
|
||||
use_onoff: bool | None = None,
|
||||
) -> None:
|
||||
await self._hold(attribute, direction, steps, use_onoff)
|
||||
|
||||
@action
|
||||
async def release(self) -> None:
|
||||
if self.hold_attribute is None:
|
||||
return
|
||||
await self._mqtt_call({f"{self.hold_attribute}_move": "stop"})
|
||||
self.hold_attribute = None
|
||||
|
||||
@action
|
||||
async def xycolor_from_controller(self, extra: EventData | None = None) -> None:
|
||||
if extra is None:
|
||||
self.log("No event data present", level="WARNING")
|
||||
return
|
||||
if isinstance(self.integration, Z2MIntegration):
|
||||
if "action_color" not in extra:
|
||||
self.log(
|
||||
"`action_color` is not present in the MQTT payload", level="WARNING"
|
||||
)
|
||||
return
|
||||
xy_color = extra["action_color"]
|
||||
await self._on(color={"x": xy_color["x"], "y": xy_color["y"]})
|
||||
|
||||
@action
|
||||
async def colortemp_from_controller(self, extra: EventData | None = None) -> None:
|
||||
if extra is None:
|
||||
self.log("No event data present", level="WARNING")
|
||||
return
|
||||
if isinstance(self.integration, Z2MIntegration):
|
||||
if "action_color_temperature" not in extra:
|
||||
self.log(
|
||||
"`action_color_temperature` is not present in the MQTT payload",
|
||||
level="WARNING",
|
||||
)
|
||||
return
|
||||
await self._on(color_temp=extra["action_color_temperature"])
|
||||
|
||||
@action
|
||||
async def brightness_from_controller_level(
|
||||
self, extra: EventData | None = None
|
||||
) -> None:
|
||||
if extra is None:
|
||||
self.log("No event data present", level="WARNING")
|
||||
return
|
||||
if isinstance(self.integration, Z2MIntegration):
|
||||
if "action_level" not in extra:
|
||||
self.log(
|
||||
"`action_level` is not present in the MQTT payload",
|
||||
level="WARNING",
|
||||
)
|
||||
return
|
||||
await self._on(brightness=extra["action_level"])
|
||||
|
||||
@action
|
||||
async def brightness_from_controller_angle(
|
||||
self,
|
||||
steps: float | None = None,
|
||||
use_onoff: bool | None = None,
|
||||
extra: EventData | None = None,
|
||||
) -> None:
|
||||
if extra is None:
|
||||
self.log("No event data present", level="WARNING")
|
||||
return
|
||||
if isinstance(self.integration, Z2MIntegration):
|
||||
if "action_rotation_angle" not in extra:
|
||||
self.log(
|
||||
"`action_rotation_angle` is not present in the MQTT payload",
|
||||
level="WARNING",
|
||||
)
|
||||
return
|
||||
angle = extra["action_rotation_angle"]
|
||||
direction = StepperDir.UP if angle > 0 else StepperDir.DOWN
|
||||
await self._hold(
|
||||
self.ATTRIBUTE_BRIGHTNESS, direction, steps=steps, use_onoff=use_onoff
|
||||
)
|
||||
@@ -1,133 +0,0 @@
|
||||
import abc
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
from cx_core.controller import Controller
|
||||
from cx_core.feature_support import FeatureSupport
|
||||
|
||||
EntityVar = TypeVar("EntityVar", bound="Entity")
|
||||
|
||||
|
||||
class Entity:
|
||||
name: str
|
||||
entities: list[str]
|
||||
|
||||
def __init__(
|
||||
self, name: str, entities: list[str] | None = None, **kwargs: Any
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.set_entities(entities)
|
||||
|
||||
@property
|
||||
def main(self) -> str:
|
||||
return self.entities[0]
|
||||
|
||||
@property
|
||||
def is_group(self) -> bool:
|
||||
return self.entities[0] != self.name
|
||||
|
||||
def set_entities(self, value: list[str] | None = None) -> None:
|
||||
self.entities = value if value is not None else [self.name]
|
||||
|
||||
@classmethod
|
||||
def instantiate(
|
||||
cls: type[EntityVar],
|
||||
name: str,
|
||||
entities: list[str] | None = None,
|
||||
**params: Any,
|
||||
) -> EntityVar:
|
||||
return cls(name=name, entities=entities, **params)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name if not self.is_group else f"{self.name}({self.entities})"
|
||||
|
||||
|
||||
class TypeController(Controller, abc.ABC, Generic[EntityVar]):
|
||||
domains: list[str] = []
|
||||
entity_arg: str
|
||||
entity: EntityVar
|
||||
update_supported_features: bool
|
||||
feature_support: FeatureSupport
|
||||
|
||||
async def init(self) -> None:
|
||||
if self.entity_arg not in self.args:
|
||||
raise ValueError(
|
||||
f"{self.__class__.__name__} class needs the `{self.entity_arg}` attribute"
|
||||
)
|
||||
self.entity = await self._get_entity(self.args[self.entity_arg])
|
||||
self._check_domain(self.entity)
|
||||
self.update_supported_features = self.args.get(
|
||||
"update_supported_features", False
|
||||
)
|
||||
supported_features: int | None = self.args.get("supported_features")
|
||||
self.feature_support = FeatureSupport(
|
||||
self, supported_features, self.update_supported_features
|
||||
)
|
||||
await super().init()
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_entity_type(self) -> type[EntityVar]:
|
||||
raise NotImplementedError
|
||||
|
||||
async def _get_entities(self, entity_name: str) -> list[str] | None:
|
||||
entities: str | list[str] | None = await self.get_state(
|
||||
entity_name, attribute="entity_id"
|
||||
)
|
||||
self.log(
|
||||
f"Entities from `{entity_name}` (entity_id attribute): `{entities}`",
|
||||
level="DEBUG",
|
||||
)
|
||||
# If the entity groups other entities, this attribute will be a list
|
||||
if not isinstance(entities, (list, tuple)):
|
||||
return None
|
||||
if entities is not None and len(entities) == 0:
|
||||
raise ValueError(f"`{entity_name}` does not have any entities registered.")
|
||||
return entities
|
||||
|
||||
async def _get_entity(self, entity: str | dict[str, Any]) -> EntityVar:
|
||||
entity_args: dict[str, Any]
|
||||
entity_name: str
|
||||
if isinstance(entity, str):
|
||||
entity_name = entity
|
||||
entity_args = {}
|
||||
elif isinstance(entity, dict):
|
||||
entity_name = entity["name"]
|
||||
entity_args = {key: value for key, value in entity.items() if key != "name"}
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Type {type(entity)} is not supported for `{self.entity_arg}` attribute"
|
||||
)
|
||||
entities = await self._get_entities(entity_name) if self.domains else None
|
||||
return self._get_entity_type().instantiate(
|
||||
name=entity_name, entities=entities, **entity_args
|
||||
)
|
||||
|
||||
def _check_domain(self, entity: Entity) -> None:
|
||||
if not self.domains:
|
||||
return
|
||||
if self.contains_templating(entity.name):
|
||||
return
|
||||
same_domain = all(
|
||||
any(elem.startswith(domain + ".") for domain in self.domains)
|
||||
for elem in entity.entities
|
||||
)
|
||||
if not same_domain:
|
||||
if entity.is_group:
|
||||
error_msg = (
|
||||
f"All the subentities from {entity} must be from one "
|
||||
f"of the following domains {self.domains} (e.g. {self.domains[0]}.bedroom)"
|
||||
)
|
||||
else:
|
||||
error_msg = (
|
||||
f"'{entity}' must be from one "
|
||||
f"of the following domains {self.domains} (e.g. {self.domains[0]}.bedroom)"
|
||||
)
|
||||
raise ValueError(error_msg)
|
||||
|
||||
async def get_entity_state(self, attribute: str | None = None) -> Any:
|
||||
entity = self.entity.main
|
||||
if self.update_supported_features:
|
||||
entities = await self._get_entities(self.entity.name)
|
||||
self.entity.set_entities(entities)
|
||||
entity = self.entity.entities[0]
|
||||
out = await self.get_state(entity, attribute=attribute)
|
||||
return out
|
||||
@@ -1,46 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, Z2MLight
|
||||
from cx_core import LightController, Z2MLightController
|
||||
|
||||
|
||||
class AdeoHRC99CZC045LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON,
|
||||
"off": Light.OFF,
|
||||
"brightness_step_up": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_step_down": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Light.RELEASE,
|
||||
"color_hue_step_up": Light.HOLD_COLOR_UP,
|
||||
"color_hue_step_down": Light.HOLD_COLOR_DOWN,
|
||||
"color_saturation_step_up": Light.HOLD_COLOR_UP,
|
||||
"color_saturation_step_down": Light.HOLD_COLOR_DOWN,
|
||||
"color_temperature_step_up": Light.HOLD_COLOR_TEMP_UP,
|
||||
"color_temperature_step_down": Light.HOLD_COLOR_TEMP_DOWN,
|
||||
"color_stop": Light.RELEASE,
|
||||
"scene_1": None,
|
||||
"scene_2": None,
|
||||
"scene_3": None,
|
||||
"scene_4": None,
|
||||
}
|
||||
|
||||
|
||||
class AdeoHRC99CZC045Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Z2MLight.ON,
|
||||
"off": Z2MLight.OFF,
|
||||
"brightness_step_up": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_step_down": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Z2MLight.RELEASE,
|
||||
"color_hue_step_up": Z2MLight.HOLD_COLOR_TEMP_UP,
|
||||
"color_hue_step_down": Z2MLight.HOLD_COLOR_TEMP_DOWN,
|
||||
"color_saturation_step_up": Z2MLight.HOLD_COLOR_TEMP_UP,
|
||||
"color_saturation_step_down": Z2MLight.HOLD_COLOR_TEMP_DOWN,
|
||||
"color_temperature_step_up": Z2MLight.HOLD_COLOR_TEMP_UP,
|
||||
"color_temperature_step_down": Z2MLight.HOLD_COLOR_TEMP_DOWN,
|
||||
"color_stop": Z2MLight.RELEASE,
|
||||
"scene_1": None,
|
||||
"scene_2": None,
|
||||
"scene_3": None,
|
||||
"scene_4": None,
|
||||
}
|
||||
@@ -1,878 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, MediaPlayer, Switch, Z2MLight
|
||||
from cx_core import (
|
||||
LightController,
|
||||
MediaPlayerController,
|
||||
SwitchController,
|
||||
Z2MLightController,
|
||||
)
|
||||
from cx_core.integration import EventData
|
||||
|
||||
|
||||
class WXKG02LMLightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single_both": Light.TOGGLE,
|
||||
"double_both": Light.CLICK_BRIGHTNESS_UP,
|
||||
"hold_both": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"single_left": Light.TOGGLE,
|
||||
"double_left": Light.CLICK_BRIGHTNESS_UP,
|
||||
"hold_left": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"single_right": Light.TOGGLE,
|
||||
"double_right": Light.CLICK_BRIGHTNESS_UP,
|
||||
"hold_right": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.TOGGLE, # single left
|
||||
1001: Light.CLICK_BRIGHTNESS_DOWN, # long left
|
||||
1004: Light.CLICK_BRIGHTNESS_UP, # double left
|
||||
2002: Light.TOGGLE, # single right
|
||||
2001: Light.CLICK_BRIGHTNESS_DOWN, # long right
|
||||
2004: Light.CLICK_BRIGHTNESS_UP, # double right
|
||||
3002: Light.TOGGLE, # single both
|
||||
3001: Light.CLICK_BRIGHTNESS_DOWN, # long both
|
||||
3004: Light.CLICK_BRIGHTNESS_UP, # double both
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"both_single": Light.TOGGLE,
|
||||
"both_double": Light.CLICK_BRIGHTNESS_UP,
|
||||
"both_long press": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"left_single": Light.TOGGLE,
|
||||
"left_double": Light.CLICK_BRIGHTNESS_UP,
|
||||
"left_long press": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"right_single": Light.TOGGLE,
|
||||
"right_double": Light.CLICK_BRIGHTNESS_UP,
|
||||
"right_long press": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class WXKG02LMZ2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single_both": Z2MLight.TOGGLE,
|
||||
"double_both": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"hold_both": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"single_left": Z2MLight.TOGGLE,
|
||||
"double_left": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"hold_left": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"single_right": Z2MLight.TOGGLE,
|
||||
"double_right": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"hold_right": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
}
|
||||
|
||||
|
||||
class WXKG02LMSwitchController(SwitchController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single_both": Switch.TOGGLE,
|
||||
"single_left": Switch.TOGGLE,
|
||||
"single_right": Switch.TOGGLE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Switch.TOGGLE,
|
||||
2002: Switch.TOGGLE,
|
||||
3002: Switch.TOGGLE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"both_single": Switch.TOGGLE,
|
||||
"left_single": Switch.TOGGLE,
|
||||
"right_single": Switch.TOGGLE,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class WXKG01LMLightController(LightController):
|
||||
"""
|
||||
Different states reported from the controller:
|
||||
single, double, triple, quadruple,
|
||||
many, hold, release
|
||||
"""
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": Light.TOGGLE,
|
||||
"double": Light.ON_FULL_BRIGHTNESS,
|
||||
"triple": Light.ON_MIN_BRIGHTNESS,
|
||||
"quadruple": Light.SET_HALF_BRIGHTNESS,
|
||||
"many": None,
|
||||
"hold": Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.TOGGLE, # single
|
||||
1004: Light.ON_FULL_BRIGHTNESS, # double
|
||||
1005: Light.ON_MIN_BRIGHTNESS, # triple
|
||||
1006: Light.SET_HALF_BRIGHTNESS, # quadruple
|
||||
1010: None, # many
|
||||
1001: Light.HOLD_BRIGHTNESS_TOGGLE, # hold the button
|
||||
1003: Light.RELEASE, # release the button
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": Light.TOGGLE,
|
||||
"double": Light.ON_FULL_BRIGHTNESS,
|
||||
"triple": Light.ON_MIN_BRIGHTNESS,
|
||||
"quadruple": Light.SET_HALF_BRIGHTNESS,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
args = data["args"]
|
||||
if "click_type" in args:
|
||||
return args["click_type"]
|
||||
return data["command"]
|
||||
|
||||
|
||||
class WXKG01LMZ2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": Z2MLight.TOGGLE,
|
||||
"double": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"triple": Z2MLight.ON_MIN_BRIGHTNESS,
|
||||
"quadruple": Z2MLight.SET_HALF_BRIGHTNESS,
|
||||
"many": None,
|
||||
"hold": Z2MLight.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"release": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class WXKG11LMRemoteLightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": Light.TOGGLE,
|
||||
"double": Light.ON_FULL_BRIGHTNESS,
|
||||
"hold": Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.TOGGLE,
|
||||
1004: Light.ON_FULL_BRIGHTNESS,
|
||||
1001: Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
1003: Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": Light.TOGGLE,
|
||||
"double": Light.ON_FULL_BRIGHTNESS,
|
||||
"hold": Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class WXKG11LMRemoteZ2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": Z2MLight.TOGGLE,
|
||||
"double": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"hold": Z2MLight.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"release": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class WXKG11LMSensorSwitchLightController(LightController):
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.TOGGLE,
|
||||
1004: Light.ON_FULL_BRIGHTNESS,
|
||||
1005: Light.ON_MIN_BRIGHTNESS,
|
||||
1006: Light.SET_HALF_BRIGHTNESS,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": Light.TOGGLE,
|
||||
"double": Light.ON_FULL_BRIGHTNESS,
|
||||
"triple": Light.ON_MIN_BRIGHTNESS,
|
||||
"quadruple": Light.SET_HALF_BRIGHTNESS,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
mapping = {
|
||||
1: "single",
|
||||
2: "double",
|
||||
3: "triple",
|
||||
4: "quadruple",
|
||||
}
|
||||
clicks = data["args"]["value"]
|
||||
return mapping.get(clicks, "")
|
||||
|
||||
|
||||
class WXKG12LMLightController(LightController):
|
||||
"""
|
||||
Different states reported from the controller:
|
||||
single, double, shake, hold, release
|
||||
"""
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": Light.TOGGLE,
|
||||
"double": Light.ON_FULL_BRIGHTNESS,
|
||||
"shake": Light.ON_MIN_BRIGHTNESS,
|
||||
"hold": Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.TOGGLE, # button_1_press
|
||||
1004: Light.ON_FULL_BRIGHTNESS, # button_1_double_press
|
||||
1007: Light.ON_MIN_BRIGHTNESS, # button_1_shake
|
||||
1001: Light.HOLD_BRIGHTNESS_TOGGLE, # button_1_hold
|
||||
1003: Light.RELEASE, # button_1_release_after_hold
|
||||
}
|
||||
|
||||
|
||||
class WXKG12LMZ2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": Z2MLight.TOGGLE,
|
||||
"double": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"shake": Z2MLight.ON_MIN_BRIGHTNESS,
|
||||
"hold": Z2MLight.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"release": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class MFKZQ01LMLightController(LightController):
|
||||
"""
|
||||
This controller allows movement actions for Xiaomi Aqara Smart Cube as
|
||||
shake, wakeup, fall, slide, flip180 or 90 and rotate_left or right.
|
||||
Then from the apps.yaml the needed actions can be included and create
|
||||
different instances for different lights.
|
||||
"""
|
||||
|
||||
# Different states reported from the controller:
|
||||
# shake, wakeup, fall, tap, slide, flip180
|
||||
# flip90, rotate_left and rotate_right
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"shake": Light.ON_MIN_BRIGHTNESS,
|
||||
"tap": Light.TOGGLE,
|
||||
"slide": Light.ON_FULL_BRIGHTNESS,
|
||||
"flip180": Light.CLICK_COLOR_UP,
|
||||
"flip90": Light.CLICK_COLOR_DOWN,
|
||||
"rotate_left": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"rotate_right": Light.CLICK_BRIGHTNESS_UP,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1: Light.ON_MIN_BRIGHTNESS,
|
||||
6: Light.TOGGLE,
|
||||
5: Light.ON_FULL_BRIGHTNESS,
|
||||
4: Light.CLICK_COLOR_UP,
|
||||
3: Light.CLICK_COLOR_DOWN,
|
||||
8: Light.CLICK_BRIGHTNESS_DOWN,
|
||||
7: Light.CLICK_BRIGHTNESS_UP,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"shake": Light.ON_MIN_BRIGHTNESS,
|
||||
"knock": Light.TOGGLE,
|
||||
"slide": Light.ON_FULL_BRIGHTNESS,
|
||||
"flip180": Light.CLICK_COLOR_UP,
|
||||
"flip90": Light.CLICK_COLOR_DOWN,
|
||||
"rotate_left": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"rotate_right": Light.CLICK_BRIGHTNESS_UP,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
action = command
|
||||
args = data.get("args", {})
|
||||
if command == "flip":
|
||||
action = command + str(args["flip_degrees"])
|
||||
return action
|
||||
|
||||
|
||||
class WXCJKG11LMLightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_1_single": Light.OFF,
|
||||
"button_1_double": Light.ON_MIN_BRIGHTNESS,
|
||||
"button_1_hold": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"button_1_release": Light.RELEASE,
|
||||
"button_2_single": Light.ON,
|
||||
"button_2_double": Light.ON_FULL_BRIGHTNESS,
|
||||
"button_2_hold": Light.HOLD_BRIGHTNESS_UP,
|
||||
"button_2_release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.OFF,
|
||||
1001: Light.HOLD_BRIGHTNESS_DOWN,
|
||||
1003: Light.RELEASE,
|
||||
2002: Light.ON,
|
||||
2001: Light.HOLD_BRIGHTNESS_UP,
|
||||
2003: Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class WXCJKG11LMZ2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_1_single": Z2MLight.OFF,
|
||||
"button_1_double": Z2MLight.ON_MIN_BRIGHTNESS,
|
||||
"button_1_hold": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"button_1_release": Z2MLight.RELEASE,
|
||||
"button_2_single": Z2MLight.ON,
|
||||
"button_2_double": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"button_2_hold": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"button_2_release": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class WXCJKG12LMLightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_1_single": Light.OFF,
|
||||
"button_1_double": Light.ON_MIN_COLOR_TEMP,
|
||||
"button_1_triple": None,
|
||||
"button_1_hold": Light.HOLD_COLOR_DOWN,
|
||||
"button_1_release": Light.RELEASE,
|
||||
"button_2_single": Light.ON,
|
||||
"button_2_double": Light.ON_FULL_COLOR_TEMP,
|
||||
"button_2_triple": None,
|
||||
"button_2_hold": Light.HOLD_COLOR_UP,
|
||||
"button_2_release": Light.RELEASE,
|
||||
"button_3_single": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"button_3_double": Light.ON_MIN_BRIGHTNESS,
|
||||
"button_3_triple": None,
|
||||
"button_3_hold": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"button_3_release": Light.RELEASE,
|
||||
"button_4_single": Light.CLICK_BRIGHTNESS_UP,
|
||||
"button_4_double": Light.ON_FULL_BRIGHTNESS,
|
||||
"button_4_triple": None,
|
||||
"button_4_hold": Light.HOLD_BRIGHTNESS_UP,
|
||||
"button_4_release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1_single": Light.OFF,
|
||||
"1_double": Light.ON_MIN_COLOR_TEMP,
|
||||
"1_triple": None,
|
||||
"1_long press": Light.HOLD_COLOR_DOWN,
|
||||
"1_release": Light.RELEASE,
|
||||
"2_single": Light.ON,
|
||||
"2_double": Light.ON_FULL_COLOR_TEMP,
|
||||
"2_triple": None,
|
||||
"2_long press": Light.HOLD_COLOR_UP,
|
||||
"2_release": Light.RELEASE,
|
||||
"3_single": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"3_double": Light.ON_MIN_BRIGHTNESS,
|
||||
"3_triple": None,
|
||||
"3_long press": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"3_release": Light.RELEASE,
|
||||
"4_single": Light.CLICK_BRIGHTNESS_UP,
|
||||
"4_double": Light.ON_FULL_BRIGHTNESS,
|
||||
"4_triple": None,
|
||||
"4_long press": Light.HOLD_BRIGHTNESS_UP,
|
||||
"4_release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class WXCJKG12LMZ2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_1_single": Z2MLight.OFF,
|
||||
"button_1_double": Z2MLight.ON_MIN_COLOR_TEMP,
|
||||
"button_1_triple": None,
|
||||
"button_1_hold": Z2MLight.HOLD_COLOR_TEMP_DOWN,
|
||||
"button_1_release": Z2MLight.RELEASE,
|
||||
"button_2_single": Z2MLight.ON,
|
||||
"button_2_double": Z2MLight.ON_FULL_COLOR_TEMP,
|
||||
"button_2_triple": None,
|
||||
"button_2_hold": Z2MLight.HOLD_COLOR_TEMP_UP,
|
||||
"button_2_release": Z2MLight.RELEASE,
|
||||
"button_3_single": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"button_3_double": Z2MLight.ON_MIN_BRIGHTNESS,
|
||||
"button_3_triple": None,
|
||||
"button_3_hold": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"button_3_release": Z2MLight.RELEASE,
|
||||
"button_4_single": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"button_4_double": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"button_4_triple": None,
|
||||
"button_4_hold": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"button_4_release": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class WXCJKG13LMLightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_1_single": Light.OFF,
|
||||
"button_1_double": Light.SYNC,
|
||||
"button_1_triple": None,
|
||||
"button_1_hold": None,
|
||||
"button_1_release": None,
|
||||
"button_2_single": Light.ON,
|
||||
"button_2_double": Light.SYNC,
|
||||
"button_2_triple": None,
|
||||
"button_2_hold": None,
|
||||
"button_2_release": None,
|
||||
"button_3_single": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"button_3_double": Light.ON_MIN_BRIGHTNESS,
|
||||
"button_3_triple": None,
|
||||
"button_3_hold": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"button_3_release": Light.RELEASE,
|
||||
"button_4_single": Light.CLICK_BRIGHTNESS_UP,
|
||||
"button_4_double": Light.ON_FULL_BRIGHTNESS,
|
||||
"button_4_triple": None,
|
||||
"button_4_hold": Light.HOLD_BRIGHTNESS_UP,
|
||||
"button_4_release": Light.RELEASE,
|
||||
"button_5_single": Light.CLICK_COLOR_DOWN,
|
||||
"button_5_double": Light.ON_MIN_COLOR_TEMP,
|
||||
"button_5_triple": None,
|
||||
"button_5_hold": Light.HOLD_COLOR_DOWN,
|
||||
"button_5_release": Light.RELEASE,
|
||||
"button_6_single": Light.CLICK_COLOR_UP,
|
||||
"button_6_double": Light.ON_FULL_COLOR_TEMP,
|
||||
"button_6_triple": None,
|
||||
"button_6_hold": Light.HOLD_COLOR_UP,
|
||||
"button_6_release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.OFF,
|
||||
1004: Light.SYNC,
|
||||
1005: None,
|
||||
1001: None,
|
||||
1003: None,
|
||||
2002: Light.ON,
|
||||
2004: Light.SYNC,
|
||||
2005: None,
|
||||
2001: None,
|
||||
2003: None,
|
||||
3002: Light.CLICK_BRIGHTNESS_DOWN,
|
||||
3004: Light.ON_MIN_BRIGHTNESS,
|
||||
3005: None,
|
||||
3001: Light.HOLD_BRIGHTNESS_DOWN,
|
||||
3003: Light.RELEASE,
|
||||
4002: Light.CLICK_BRIGHTNESS_UP,
|
||||
4004: Light.ON_FULL_BRIGHTNESS,
|
||||
4005: None,
|
||||
4001: Light.HOLD_BRIGHTNESS_UP,
|
||||
4003: Light.RELEASE,
|
||||
5002: Light.CLICK_COLOR_DOWN,
|
||||
5004: Light.ON_MIN_COLOR_TEMP,
|
||||
5005: None,
|
||||
5001: Light.HOLD_COLOR_DOWN,
|
||||
5003: Light.RELEASE,
|
||||
6002: Light.CLICK_COLOR_UP,
|
||||
6004: Light.ON_FULL_COLOR_TEMP,
|
||||
6005: None,
|
||||
6001: Light.HOLD_COLOR_UP,
|
||||
6003: Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1_single": Light.OFF,
|
||||
"1_double": Light.SYNC,
|
||||
"1_triple": None,
|
||||
"1_long": None,
|
||||
"1_release": None,
|
||||
"2_single": Light.ON,
|
||||
"2_double": Light.SYNC,
|
||||
"2_triple": None,
|
||||
"2_long": None,
|
||||
"2_release": None,
|
||||
"3_single": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"3_double": Light.ON_MIN_BRIGHTNESS,
|
||||
"3_triple": None,
|
||||
"3_long": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"3_release": Light.RELEASE,
|
||||
"4_single": Light.CLICK_BRIGHTNESS_UP,
|
||||
"4_double": Light.ON_FULL_BRIGHTNESS,
|
||||
"4_triple": None,
|
||||
"4_long": Light.HOLD_BRIGHTNESS_UP,
|
||||
"4_release": Light.RELEASE,
|
||||
"5_single": Light.CLICK_COLOR_DOWN,
|
||||
"5_double": Light.ON_MIN_COLOR_TEMP,
|
||||
"5_triple": None,
|
||||
"5_long": Light.HOLD_COLOR_DOWN,
|
||||
"5_release": Light.RELEASE,
|
||||
"6_single": Light.CLICK_COLOR_UP,
|
||||
"6_double": Light.ON_FULL_COLOR_TEMP,
|
||||
"6_triple": None,
|
||||
"6_long": Light.HOLD_COLOR_UP,
|
||||
"6_release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class WXCJKG13LMZ2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_1_single": Z2MLight.OFF,
|
||||
"button_1_double": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"button_1_triple": None,
|
||||
"button_1_hold": None,
|
||||
"button_1_release": None,
|
||||
"button_2_single": Z2MLight.ON,
|
||||
"button_2_double": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"button_2_triple": None,
|
||||
"button_2_hold": None,
|
||||
"button_2_release": None,
|
||||
"button_3_single": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"button_3_double": Z2MLight.ON_MIN_BRIGHTNESS,
|
||||
"button_3_triple": None,
|
||||
"button_3_hold": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"button_3_release": Z2MLight.RELEASE,
|
||||
"button_4_single": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"button_4_double": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"button_4_triple": None,
|
||||
"button_4_hold": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"button_4_release": Z2MLight.RELEASE,
|
||||
"button_5_single": Z2MLight.CLICK_COLOR_TEMP_DOWN,
|
||||
"button_5_double": Z2MLight.ON_MIN_COLOR_TEMP,
|
||||
"button_5_triple": None,
|
||||
"button_5_hold": Z2MLight.HOLD_COLOR_TEMP_DOWN,
|
||||
"button_5_release": Z2MLight.RELEASE,
|
||||
"button_6_single": Z2MLight.CLICK_COLOR_TEMP_UP,
|
||||
"button_6_double": Z2MLight.ON_FULL_COLOR_TEMP,
|
||||
"button_6_triple": None,
|
||||
"button_6_hold": Z2MLight.HOLD_COLOR_TEMP_UP,
|
||||
"button_6_release": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class WXCJKG13LMMediaPlayerController(MediaPlayerController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_1_single": MediaPlayer.CLICK_VOLUME_DOWN,
|
||||
"button_1_double": MediaPlayer.MUTE,
|
||||
"button_1_triple": None,
|
||||
"button_1_hold": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"button_1_release": MediaPlayer.RELEASE,
|
||||
"button_2_single": MediaPlayer.CLICK_VOLUME_UP,
|
||||
"button_2_double": MediaPlayer.PLAY_PAUSE,
|
||||
"button_2_triple": None,
|
||||
"button_2_hold": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"button_2_release": MediaPlayer.RELEASE,
|
||||
"button_3_single": MediaPlayer.PREVIOUS_TRACK,
|
||||
"button_3_double": None,
|
||||
"button_3_triple": None,
|
||||
"button_3_hold": None,
|
||||
"button_3_release": None,
|
||||
"button_4_single": MediaPlayer.NEXT_TRACK,
|
||||
"button_4_double": None,
|
||||
"button_4_triple": None,
|
||||
"button_4_hold": None,
|
||||
"button_4_release": None,
|
||||
"button_5_single": MediaPlayer.PREVIOUS_SOURCE,
|
||||
"button_5_double": None,
|
||||
"button_5_triple": None,
|
||||
"button_5_hold": None,
|
||||
"button_5_release": None,
|
||||
"button_6_single": MediaPlayer.NEXT_SOURCE,
|
||||
"button_6_double": None,
|
||||
"button_6_triple": None,
|
||||
"button_6_hold": None,
|
||||
"button_6_release": None,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
#######################
|
||||
# Button layout
|
||||
#######################
|
||||
# 1000: top-left
|
||||
# 2000: top-right
|
||||
# 3000: middle-left
|
||||
# 4000: middle-right
|
||||
# 5000: bottom-left
|
||||
# 6000: bottom-right
|
||||
#######################
|
||||
# Actions
|
||||
#######################
|
||||
# 0001: hold
|
||||
# 0002: click
|
||||
# 0003: release
|
||||
# 0004: double-click
|
||||
# 0005: triple-click
|
||||
#######################
|
||||
return {
|
||||
1002: MediaPlayer.CLICK_VOLUME_DOWN,
|
||||
1001: MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
1003: MediaPlayer.RELEASE,
|
||||
1004: MediaPlayer.MUTE,
|
||||
1005: None,
|
||||
2002: MediaPlayer.CLICK_VOLUME_UP,
|
||||
2001: MediaPlayer.HOLD_VOLUME_UP,
|
||||
2003: MediaPlayer.RELEASE,
|
||||
2004: MediaPlayer.PLAY_PAUSE,
|
||||
2005: None,
|
||||
3002: MediaPlayer.PREVIOUS_TRACK,
|
||||
3001: None,
|
||||
3003: None,
|
||||
3004: None,
|
||||
3005: None,
|
||||
4002: MediaPlayer.NEXT_TRACK,
|
||||
4001: None,
|
||||
4003: None,
|
||||
4004: None,
|
||||
4005: None,
|
||||
5002: MediaPlayer.PREVIOUS_SOURCE,
|
||||
5001: None,
|
||||
5003: None,
|
||||
5004: None,
|
||||
5005: None,
|
||||
6002: MediaPlayer.NEXT_SOURCE,
|
||||
6001: None,
|
||||
6003: None,
|
||||
6004: None,
|
||||
6005: None,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1_single": MediaPlayer.CLICK_VOLUME_DOWN,
|
||||
"1_double": MediaPlayer.MUTE,
|
||||
"1_triple": None,
|
||||
"1_long": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"1_release": MediaPlayer.RELEASE,
|
||||
"2_single": MediaPlayer.CLICK_VOLUME_UP,
|
||||
"2_double": MediaPlayer.PLAY_PAUSE,
|
||||
"2_triple": None,
|
||||
"2_long": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"2_release": MediaPlayer.RELEASE,
|
||||
"3_single": MediaPlayer.PREVIOUS_TRACK,
|
||||
"3_double": None,
|
||||
"3_triple": None,
|
||||
"3_long": None,
|
||||
"3_release": None,
|
||||
"4_single": MediaPlayer.NEXT_TRACK,
|
||||
"4_double": None,
|
||||
"4_triple": None,
|
||||
"4_long": None,
|
||||
"4_release": None,
|
||||
"5_single": MediaPlayer.PREVIOUS_SOURCE,
|
||||
"5_double": None,
|
||||
"5_triple": None,
|
||||
"5_long": None,
|
||||
"5_release": Light.RELEASE,
|
||||
"6_single": MediaPlayer.NEXT_SOURCE,
|
||||
"6_double": None,
|
||||
"6_triple": None,
|
||||
"6_long": None,
|
||||
"6_release": None,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class WXKG06LMLightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": Light.TOGGLE,
|
||||
"double": Light.CLICK_BRIGHTNESS_UP,
|
||||
"hold": Light.CLICK_BRIGHTNESS_UP,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.TOGGLE,
|
||||
1001: Light.CLICK_BRIGHTNESS_DOWN,
|
||||
1004: Light.CLICK_BRIGHTNESS_UP,
|
||||
}
|
||||
|
||||
|
||||
class WXKG06LMSwitchController(SwitchController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {"single": Light.TOGGLE}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {1002: Light.TOGGLE}
|
||||
|
||||
|
||||
class WXKG07LMLightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single_both": Light.TOGGLE,
|
||||
"double_both": Light.CLICK_BRIGHTNESS_UP,
|
||||
"hold_both": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"single_left": Light.TOGGLE,
|
||||
"double_left": Light.CLICK_BRIGHTNESS_UP,
|
||||
"hold_left": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"single_right": Light.TOGGLE,
|
||||
"double_right": Light.CLICK_BRIGHTNESS_UP,
|
||||
"hold_right": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1001: Light.CLICK_BRIGHTNESS_DOWN, # long left
|
||||
1002: Light.TOGGLE, # single left
|
||||
1004: Light.CLICK_BRIGHTNESS_UP, # double left
|
||||
2001: Light.CLICK_BRIGHTNESS_DOWN, # long right
|
||||
2002: Light.TOGGLE, # single right
|
||||
2004: Light.CLICK_BRIGHTNESS_UP, # double right
|
||||
3001: Light.CLICK_BRIGHTNESS_DOWN, # long both
|
||||
3002: Light.TOGGLE, # single both
|
||||
3004: Light.CLICK_BRIGHTNESS_UP, # double both
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"both_single": Light.TOGGLE,
|
||||
"both_double": Light.CLICK_BRIGHTNESS_UP,
|
||||
"both_long press": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"left_single": Light.TOGGLE,
|
||||
"left_double": Light.CLICK_BRIGHTNESS_UP,
|
||||
"left_long press": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"right_single": Light.TOGGLE,
|
||||
"right_double": Light.CLICK_BRIGHTNESS_UP,
|
||||
"right_long press": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class WXKG07LMZ2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single_both": Z2MLight.TOGGLE,
|
||||
"double_both": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"hold_both": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"single_left": Z2MLight.TOGGLE,
|
||||
"double_left": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"hold_left": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"single_right": Z2MLight.TOGGLE,
|
||||
"double_right": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"hold_right": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
}
|
||||
|
||||
|
||||
class WXKG07LMSwitchController(SwitchController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single_both": Switch.TOGGLE,
|
||||
"single_left": Switch.TOGGLE,
|
||||
"single_right": Switch.TOGGLE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Switch.TOGGLE,
|
||||
2002: Switch.TOGGLE,
|
||||
3002: Switch.TOGGLE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"both_single": Switch.TOGGLE,
|
||||
"left_single": Switch.TOGGLE,
|
||||
"right_single": Switch.TOGGLE,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class ZNXNKG02LMLightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": Light.TOGGLE,
|
||||
"double": Light.ON_FULL_BRIGHTNESS,
|
||||
"hold": Light.HOLD_COLOR_TOGGLE,
|
||||
"release": Light.RELEASE,
|
||||
"start_rotating": Light.BRIGHTNESS_FROM_CONTROLLER_ANGLE,
|
||||
"stop_rotating": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class ZNXNKG02LMMediaPlayerController(MediaPlayerController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": MediaPlayer.PLAY_PAUSE,
|
||||
"double": MediaPlayer.NEXT_TRACK,
|
||||
"hold": MediaPlayer.PREVIOUS_TRACK,
|
||||
"release": MediaPlayer.RELEASE,
|
||||
"start_rotating": MediaPlayer.VOLUME_FROM_CONTROLLER_ANGLE,
|
||||
"stop_rotating": MediaPlayer.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class WXKG15LMLightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single_both": Light.TOGGLE,
|
||||
"double_both": Light.CLICK_BRIGHTNESS_UP,
|
||||
"triple_both": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"hold_both": Light.ON_FULL_BRIGHTNESS,
|
||||
"single_left": Light.TOGGLE,
|
||||
"double_left": Light.CLICK_BRIGHTNESS_UP,
|
||||
"triple_left": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"hold_left": Light.ON_FULL_BRIGHTNESS,
|
||||
"single_right": Light.TOGGLE,
|
||||
"double_right": Light.CLICK_BRIGHTNESS_UP,
|
||||
"triple_right": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"hold_right": Light.ON_FULL_BRIGHTNESS,
|
||||
}
|
||||
|
||||
|
||||
class WXKG15LMZ2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single_both": Z2MLight.TOGGLE,
|
||||
"double_both": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"triple_both": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"hold_both": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"single_left": Z2MLight.TOGGLE,
|
||||
"double_left": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"triple_left": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"hold_left": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"single_right": Z2MLight.TOGGLE,
|
||||
"double_right": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"triple_right": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"hold_right": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
}
|
||||
|
||||
|
||||
class WXKG15LMSwitchController(SwitchController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single_both": Switch.TOGGLE,
|
||||
"single_left": Switch.TOGGLE,
|
||||
"single_right": Switch.TOGGLE,
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light
|
||||
from cx_core import LightController
|
||||
from cx_core.integration import EventData
|
||||
|
||||
|
||||
class AUA1ZBR2GWLightController(LightController):
|
||||
# Different states reported from the controller:
|
||||
# on, off, brightness_step_up, brightness_step_down,
|
||||
# color_temperature_step_up, color_temperature_step_down
|
||||
# There is one copy of these actions per endpoint
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1_toggle": Light.TOGGLE,
|
||||
"1_step_up": Light.CLICK_BRIGHTNESS_UP,
|
||||
"1_step_down": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"1_step_color_temp_up": Light.CLICK_COLOR_TEMP_UP,
|
||||
"1_step_color_temp_down": Light.CLICK_COLOR_TEMP_DOWN,
|
||||
"2_toggle": Light.TOGGLE,
|
||||
"2_step_up": Light.CLICK_BRIGHTNESS_UP,
|
||||
"2_step_down": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"2_step_color_temp_up": Light.CLICK_COLOR_TEMP_UP,
|
||||
"2_step_color_temp_down": Light.CLICK_COLOR_TEMP_DOWN,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
endpoint_id = data.get("endpoint_id", 1)
|
||||
command: str = data["command"]
|
||||
action = command
|
||||
args = data.get("args", [])
|
||||
if command == "step" or command == "step_color_temp":
|
||||
args_mapping = {0: "up", 1: "down", 3: "up"}
|
||||
action = "_".join((action, args_mapping[args[0]]))
|
||||
if command == "on" or command == "off":
|
||||
action = "toggle"
|
||||
action = f"{endpoint_id}_{action}"
|
||||
return action
|
||||
@@ -1,78 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light
|
||||
from cx_core import LightController
|
||||
|
||||
|
||||
class HMPB2WM552LightController(LightController):
|
||||
def get_homematic_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"PRESS_SHORT_1": Light.OFF,
|
||||
"PRESS_LONG_1": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"PRESS_CONT_1": None,
|
||||
"PRESS_LONG_RELEASE_1": Light.RELEASE,
|
||||
"PRESS_SHORT_2": Light.ON,
|
||||
"PRESS_LONG_2": Light.HOLD_BRIGHTNESS_UP,
|
||||
"PRESS_CONT_2": None,
|
||||
"PRESS_LONG_RELEASE_2": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class HMPBI4FMLightController(LightController):
|
||||
def get_homematic_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"PRESS_SHORT_1": Light.OFF,
|
||||
"PRESS_CONT_1": None,
|
||||
"PRESS_LONG_1": Light.CLICK_COLOR_DOWN,
|
||||
"PRESS_SHORT_2": Light.ON,
|
||||
"PRESS_CONT_2": None,
|
||||
"PRESS_LONG_2": Light.CLICK_COLOR_UP,
|
||||
"PRESS_SHORT_3": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"PRESS_CONT_3": None,
|
||||
"PRESS_LONG_3": Light.ON_MIN_BRIGHTNESS,
|
||||
"PRESS_SHORT_4": Light.CLICK_BRIGHTNESS_UP,
|
||||
"PRESS_CONT_4": None,
|
||||
"PRESS_LONG_4": Light.ON_FULL_BRIGHTNESS,
|
||||
}
|
||||
|
||||
|
||||
class HMPB6WM55LightController(LightController):
|
||||
def get_homematic_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"PRESS_SHORT_1": Light.OFF,
|
||||
"PRESS_CONT_1": None,
|
||||
"PRESS_LONG_1": Light.SYNC,
|
||||
"PRESS_LONG_RELEASE_1": Light.RELEASE,
|
||||
"PRESS_SHORT_2": Light.ON,
|
||||
"PRESS_CONT_2": None,
|
||||
"PRESS_LONG_2": Light.SYNC,
|
||||
"PRESS_LONG_RELEASE_2": Light.RELEASE,
|
||||
"PRESS_SHORT_3": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"PRESS_CONT_3": None,
|
||||
"PRESS_LONG_3": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"PRESS_LONG_RELEASE_3": Light.RELEASE,
|
||||
"PRESS_SHORT_4": Light.CLICK_BRIGHTNESS_UP,
|
||||
"PRESS_CONT_4": None,
|
||||
"PRESS_LONG_4": Light.HOLD_BRIGHTNESS_UP,
|
||||
"PRESS_LONG_RELEASE_4": Light.RELEASE,
|
||||
"PRESS_SHORT_5": Light.CLICK_COLOR_DOWN,
|
||||
"PRESS_CONT_5": None,
|
||||
"PRESS_LONG_5": Light.HOLD_COLOR_DOWN,
|
||||
"PRESS_LONG_RELEASE_5": Light.RELEASE,
|
||||
"PRESS_SHORT_6": Light.CLICK_COLOR_UP,
|
||||
"PRESS_CONT_6": None,
|
||||
"PRESS_LONG_6": Light.HOLD_COLOR_UP,
|
||||
"PRESS_LONG_RELEASE_6": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class HMSenMDIRWM55LightController(LightController):
|
||||
def get_homematic_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"PRESS_SHORT_1": Light.OFF,
|
||||
"PRESS_LONG_1": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"PRESS_CONT_1": None,
|
||||
"PRESS_LONG_RELEASE_1": Light.RELEASE,
|
||||
"PRESS_SHORT_2": Light.ON,
|
||||
"PRESS_LONG_2": Light.HOLD_BRIGHTNESS_UP,
|
||||
"PRESS_CONT_2": None,
|
||||
"PRESS_LONG_RELEASE_2": Light.RELEASE,
|
||||
}
|
||||
@@ -1,844 +0,0 @@
|
||||
from cx_const import (
|
||||
Cover,
|
||||
DefaultActionsMapping,
|
||||
Light,
|
||||
MediaPlayer,
|
||||
PredefinedActionsMapping,
|
||||
Switch,
|
||||
Z2MLight,
|
||||
)
|
||||
from cx_core import (
|
||||
CoverController,
|
||||
LightController,
|
||||
MediaPlayerController,
|
||||
SwitchController,
|
||||
Z2MLightController,
|
||||
action,
|
||||
)
|
||||
from cx_core.integration import EventData
|
||||
|
||||
|
||||
class E1810Controller(LightController):
|
||||
# Different states reported from the controller:
|
||||
# toggle, brightness_up_click, brightness_down_click
|
||||
# arrow_left_click, arrow_right_click, brightness_up_hold
|
||||
# brightness_up_release, brightness_down_hold, brightness_down_release,
|
||||
# arrow_left_hold, arrow_left_release, arrow_right_hold
|
||||
# arrow_right_release
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"toggle": Light.TOGGLE,
|
||||
"toggle_hold": Light.SYNC,
|
||||
"brightness_up_click": Light.CLICK_BRIGHTNESS_UP,
|
||||
"brightness_down_click": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"arrow_left_click": Light.CLICK_COLOR_DOWN,
|
||||
"arrow_right_click": Light.CLICK_COLOR_UP,
|
||||
"brightness_up_hold": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_up_release": Light.RELEASE,
|
||||
"brightness_down_hold": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_down_release": Light.RELEASE,
|
||||
"arrow_left_hold": Light.HOLD_COLOR_DOWN,
|
||||
"arrow_left_release": Light.RELEASE,
|
||||
"arrow_right_hold": Light.HOLD_COLOR_UP,
|
||||
"arrow_right_release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.TOGGLE,
|
||||
1001: Light.SYNC,
|
||||
2002: Light.CLICK_BRIGHTNESS_UP,
|
||||
3002: Light.CLICK_BRIGHTNESS_DOWN,
|
||||
4002: Light.CLICK_COLOR_DOWN,
|
||||
5002: Light.CLICK_COLOR_UP,
|
||||
2001: Light.HOLD_BRIGHTNESS_UP,
|
||||
2003: Light.RELEASE,
|
||||
3001: Light.HOLD_BRIGHTNESS_DOWN,
|
||||
3003: Light.RELEASE,
|
||||
4001: Light.HOLD_COLOR_DOWN,
|
||||
4003: Light.RELEASE,
|
||||
5001: Light.HOLD_COLOR_UP,
|
||||
5003: Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"toggle": Light.TOGGLE,
|
||||
"press_2_0_0": Light.SYNC,
|
||||
"step_with_on_off_0_43_5": Light.CLICK_BRIGHTNESS_UP,
|
||||
"step_1_43_5_0_0": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"press_257_13_0": Light.CLICK_COLOR_DOWN,
|
||||
"press_256_13_0": Light.CLICK_COLOR_UP,
|
||||
"move_with_on_off_0_83": Light.HOLD_BRIGHTNESS_UP,
|
||||
"move_with_on_off_0_84": Light.HOLD_BRIGHTNESS_UP, # ZigBee 3.0 firmware
|
||||
"move_1_83_0_0": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"move_1_84_0_0": Light.HOLD_BRIGHTNESS_DOWN, # ZigBee 3.0 firmware
|
||||
"hold_3329_0": Light.HOLD_COLOR_DOWN,
|
||||
"hold_3328_0": Light.HOLD_COLOR_UP,
|
||||
"stop": Light.RELEASE,
|
||||
"stop_with_on_off": Light.RELEASE,
|
||||
"release": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class E1810Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"toggle": Z2MLight.TOGGLE,
|
||||
"toggle_hold": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"brightness_up_click": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"brightness_down_click": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"arrow_left_click": Z2MLight.CLICK_COLOR_TEMP_DOWN,
|
||||
"arrow_right_click": Z2MLight.CLICK_COLOR_TEMP_UP,
|
||||
"brightness_up_hold": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_up_release": Z2MLight.RELEASE,
|
||||
"brightness_down_hold": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_down_release": Z2MLight.RELEASE,
|
||||
"arrow_left_hold": Z2MLight.HOLD_COLOR_TEMP_DOWN,
|
||||
"arrow_left_release": Z2MLight.RELEASE,
|
||||
"arrow_right_hold": Z2MLight.HOLD_COLOR_TEMP_UP,
|
||||
"arrow_right_release": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class E1810MediaPlayerController(MediaPlayerController):
|
||||
# Different states reported from the controller:
|
||||
# toggle, brightness_up_click, brightness_down_click
|
||||
# arrow_left_click, arrow_right_click, brightness_up_hold
|
||||
# brightness_up_release, brightness_down_hold, brightness_down_release,
|
||||
# arrow_left_hold, arrow_left_release, arrow_right_hold
|
||||
# arrow_right_release
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"toggle": MediaPlayer.PLAY_PAUSE,
|
||||
"brightness_up_click": MediaPlayer.CLICK_VOLUME_UP,
|
||||
"brightness_down_click": MediaPlayer.CLICK_VOLUME_DOWN,
|
||||
"arrow_left_click": MediaPlayer.PREVIOUS_TRACK,
|
||||
"arrow_right_click": MediaPlayer.NEXT_TRACK,
|
||||
"arrow_left_hold": MediaPlayer.PREVIOUS_SOURCE,
|
||||
"arrow_right_hold": MediaPlayer.NEXT_SOURCE,
|
||||
"brightness_up_hold": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"brightness_up_release": MediaPlayer.RELEASE,
|
||||
"brightness_down_hold": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"brightness_down_release": MediaPlayer.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: MediaPlayer.PLAY_PAUSE,
|
||||
2002: MediaPlayer.CLICK_VOLUME_UP,
|
||||
3002: MediaPlayer.CLICK_VOLUME_DOWN,
|
||||
4002: MediaPlayer.PREVIOUS_TRACK,
|
||||
5002: MediaPlayer.NEXT_TRACK,
|
||||
2001: MediaPlayer.HOLD_VOLUME_UP,
|
||||
2003: MediaPlayer.RELEASE,
|
||||
3001: MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
3003: MediaPlayer.RELEASE,
|
||||
4001: MediaPlayer.PREVIOUS_SOURCE,
|
||||
5001: MediaPlayer.NEXT_SOURCE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"toggle": MediaPlayer.PLAY_PAUSE,
|
||||
"step_with_on_off_0_43_5": MediaPlayer.CLICK_VOLUME_UP,
|
||||
"step_1_43_5_0_0": MediaPlayer.CLICK_VOLUME_DOWN,
|
||||
"press_257_13_0": MediaPlayer.PREVIOUS_TRACK,
|
||||
"press_256_13_0": MediaPlayer.NEXT_TRACK,
|
||||
"move_with_on_off_0_83": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"move_with_on_off_0_84": MediaPlayer.HOLD_VOLUME_UP, # ZigBee 3.0 firmware
|
||||
"stop": MediaPlayer.RELEASE,
|
||||
"stop_with_on_off": MediaPlayer.RELEASE,
|
||||
"move_1_83_0_0": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"move_1_84_0_0": MediaPlayer.HOLD_VOLUME_DOWN, # ZigBee 3.0 firmware
|
||||
"hold_3329_0": MediaPlayer.PREVIOUS_SOURCE,
|
||||
"hold_3328_0": MediaPlayer.NEXT_SOURCE,
|
||||
"release": MediaPlayer.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class E1743Controller(LightController):
|
||||
# Different states reported from the controller:
|
||||
# on, off, brightness_up, brightness_down, brightness_stop
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON,
|
||||
"off": Light.OFF,
|
||||
"brightness_move_up": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.ON,
|
||||
2002: Light.OFF,
|
||||
1001: Light.HOLD_BRIGHTNESS_UP,
|
||||
2001: Light.HOLD_BRIGHTNESS_DOWN,
|
||||
1003: Light.RELEASE,
|
||||
2003: Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON,
|
||||
"off": Light.OFF,
|
||||
"move_with_on_off_0_83": Light.HOLD_BRIGHTNESS_UP,
|
||||
"move_1_83_0_0": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"stop": Light.RELEASE,
|
||||
"stop_with_on_off": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class E1743Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Z2MLight.ON,
|
||||
"off": Z2MLight.OFF,
|
||||
"brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class E1743MediaPlayerController(MediaPlayerController):
|
||||
# Different states reported from the controller:
|
||||
# on, off, brightness_up, brightness_down, brightness_stop
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": MediaPlayer.PLAY_PAUSE,
|
||||
"off": MediaPlayer.NEXT_TRACK,
|
||||
"brightness_move_up": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"brightness_move_down": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"brightness_stop": MediaPlayer.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: MediaPlayer.PLAY_PAUSE,
|
||||
2002: MediaPlayer.NEXT_TRACK,
|
||||
1001: MediaPlayer.HOLD_VOLUME_UP,
|
||||
2001: MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
1003: MediaPlayer.RELEASE,
|
||||
2003: MediaPlayer.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": MediaPlayer.PLAY_PAUSE,
|
||||
"off": MediaPlayer.NEXT_TRACK,
|
||||
"move_with_on_off_0_83": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"move_1_83_0_0": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"stop": MediaPlayer.RELEASE,
|
||||
"stop_with_on_off": MediaPlayer.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class E1743SwitchController(SwitchController):
|
||||
# Different states reported from the controller:
|
||||
# on, off
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {"on": Switch.ON, "off": Switch.OFF}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {1002: Switch.ON, 2002: Switch.OFF}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {"on": Switch.ON, "off": Switch.OFF}
|
||||
|
||||
|
||||
class E1743CoverController(CoverController):
|
||||
# Different states reported from the controller:
|
||||
# on, off
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Cover.TOGGLE_OPEN,
|
||||
"off": Cover.TOGGLE_CLOSE,
|
||||
"brightness_move_up": Cover.OPEN,
|
||||
"brightness_move_down": Cover.CLOSE,
|
||||
"brightness_stop": Cover.STOP,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Cover.TOGGLE_OPEN,
|
||||
2002: Cover.TOGGLE_CLOSE,
|
||||
1001: Cover.OPEN,
|
||||
2001: Cover.CLOSE,
|
||||
1003: Cover.STOP,
|
||||
2003: Cover.STOP,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Cover.TOGGLE_OPEN,
|
||||
"off": Cover.TOGGLE_CLOSE,
|
||||
"move_with_on_off_0_83": Cover.OPEN,
|
||||
"move_1_83_0_0": Cover.CLOSE,
|
||||
"stop": Cover.STOP,
|
||||
"stop_with_on_off": Cover.STOP,
|
||||
}
|
||||
|
||||
|
||||
class E2201LightController(E1743Controller):
|
||||
pass
|
||||
|
||||
|
||||
class E2201Z2MLightController(E1743Z2MLightController):
|
||||
pass
|
||||
|
||||
|
||||
class E2201MediaPlayerController(E1743MediaPlayerController):
|
||||
pass
|
||||
|
||||
|
||||
class E2201SwitchController(E1743SwitchController):
|
||||
pass
|
||||
|
||||
|
||||
class E2201CoverController(E1743CoverController):
|
||||
pass
|
||||
|
||||
|
||||
class ICTCG1Controller(LightController):
|
||||
# Different states reported from the controller:
|
||||
# rotate_left, rotate_left_quick
|
||||
# rotate_right, rotate_right_quick
|
||||
# rotate_stop
|
||||
|
||||
@action
|
||||
async def rotate_left_quick(self) -> None:
|
||||
await self.release()
|
||||
await self.off()
|
||||
|
||||
@action
|
||||
async def rotate_right_quick(self) -> None:
|
||||
await self.release()
|
||||
await self.on_full(LightController.ATTRIBUTE_BRIGHTNESS)
|
||||
|
||||
def get_predefined_actions_mapping(self) -> PredefinedActionsMapping:
|
||||
parent_mapping = super().get_predefined_actions_mapping()
|
||||
mapping: PredefinedActionsMapping = {
|
||||
"rotate_left_quick": self.rotate_left_quick,
|
||||
"rotate_right_quick": self.rotate_right_quick,
|
||||
}
|
||||
mapping.update(parent_mapping)
|
||||
return mapping
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"brightness_move_up": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Light.RELEASE,
|
||||
"brightness_move_to_level": Light.BRIGHTNESS_FROM_CONTROLLER_LEVEL,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: "rotate_right_quick",
|
||||
2002: Light.CLICK_BRIGHTNESS_UP,
|
||||
3002: Light.CLICK_BRIGHTNESS_DOWN,
|
||||
4002: "rotate_left_quick",
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"move_1_70": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"move_1_195": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"move_to_level_with_on_off_0_1": "rotate_left_quick",
|
||||
"move_with_on_off_0_70": Light.HOLD_BRIGHTNESS_UP,
|
||||
"move_with_on_off_0_195": Light.ON,
|
||||
"move_to_level_with_on_off_255_1": "rotate_right_quick",
|
||||
"stop": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class ICTCG1Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Z2MLight.RELEASE,
|
||||
"brightness_move_to_level": Z2MLight.BRIGHTNESS_FROM_CONTROLLER_LEVEL,
|
||||
}
|
||||
|
||||
|
||||
class ICTCG1MediaPlayerController(MediaPlayerController):
|
||||
# Different states reported from the controller:
|
||||
# rotate_left, rotate_left_quick
|
||||
# rotate_right, rotate_right_quick
|
||||
# rotate_stop
|
||||
|
||||
@action
|
||||
async def rotate_left_quick(self) -> None:
|
||||
await self.release()
|
||||
await self.pause()
|
||||
|
||||
@action
|
||||
async def rotate_right_quick(self) -> None:
|
||||
await self.release()
|
||||
await self.play()
|
||||
|
||||
def get_predefined_actions_mapping(self) -> PredefinedActionsMapping:
|
||||
parent_mapping = super().get_predefined_actions_mapping()
|
||||
mapping: PredefinedActionsMapping = {
|
||||
"rotate_left_quick": self.rotate_left_quick,
|
||||
"rotate_right_quick": self.rotate_right_quick,
|
||||
}
|
||||
mapping.update(parent_mapping)
|
||||
return mapping
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"brightness_move_up": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"brightness_move_down": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"brightness_stop": MediaPlayer.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: "rotate_right_quick",
|
||||
2002: MediaPlayer.CLICK_VOLUME_UP,
|
||||
3002: MediaPlayer.CLICK_VOLUME_DOWN,
|
||||
4002: "rotate_left_quick",
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"move_1_70": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"move_1_195": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"move_to_level_with_on_off_0_1": "rotate_left_quick",
|
||||
"move_with_on_off_0_70": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"move_with_on_off_0_195": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"move_to_level_with_on_off_255_1": "rotate_right_quick",
|
||||
"stop": MediaPlayer.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class E1744LightController(LightController):
|
||||
# Different states reported from the controller:
|
||||
# brightness_move_down, brightness_move_up, brightness_stop,
|
||||
# toggle, brightness_step_up, brightness_step_down
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"brightness_move_down": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_move_up": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_stop": Light.RELEASE,
|
||||
"toggle": Light.TOGGLE,
|
||||
"brightness_step_up": Light.ON_FULL_BRIGHTNESS,
|
||||
"brightness_step_down": Light.ON_MIN_BRIGHTNESS,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
3001: Light.HOLD_BRIGHTNESS_DOWN,
|
||||
2001: Light.HOLD_BRIGHTNESS_UP,
|
||||
2003: Light.RELEASE,
|
||||
3003: Light.RELEASE,
|
||||
1002: Light.TOGGLE,
|
||||
1004: Light.ON_FULL_BRIGHTNESS,
|
||||
1005: Light.ON_MIN_BRIGHTNESS,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"move_1_195_0_0": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"move_0_195_0_0": Light.HOLD_BRIGHTNESS_UP,
|
||||
"stop": Light.RELEASE,
|
||||
"toggle": Light.TOGGLE,
|
||||
"step_0_1_0_0_0": Light.ON_FULL_BRIGHTNESS,
|
||||
"step_1_1_0_0_0": Light.ON_MIN_BRIGHTNESS,
|
||||
}
|
||||
|
||||
def default_delay(self) -> int:
|
||||
return 500
|
||||
|
||||
|
||||
class E1744Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_stop": Z2MLight.RELEASE,
|
||||
"toggle": Z2MLight.TOGGLE,
|
||||
"brightness_step_up": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"brightness_step_down": Z2MLight.ON_MIN_BRIGHTNESS,
|
||||
}
|
||||
|
||||
|
||||
class E1744MediaPlayerController(MediaPlayerController):
|
||||
# Different states reported from the controller:
|
||||
# brightness_move_down, brightness_move_up, brightness_stop,
|
||||
# toggle, brightness_step_up, brightness_step_down
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"brightness_move_down": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"brightness_move_up": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"brightness_stop": MediaPlayer.RELEASE,
|
||||
"toggle": MediaPlayer.PLAY_PAUSE,
|
||||
"brightness_step_up": MediaPlayer.NEXT_TRACK,
|
||||
"brightness_step_down": MediaPlayer.PREVIOUS_TRACK,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
2001: MediaPlayer.HOLD_VOLUME_UP,
|
||||
3001: MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
2003: MediaPlayer.RELEASE,
|
||||
3003: MediaPlayer.RELEASE,
|
||||
1002: MediaPlayer.PLAY_PAUSE,
|
||||
1004: MediaPlayer.NEXT_TRACK,
|
||||
1005: MediaPlayer.PREVIOUS_TRACK,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"move_1_195_0_0": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"move_0_195_0_0": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"stop": MediaPlayer.RELEASE,
|
||||
"toggle": MediaPlayer.PLAY_PAUSE,
|
||||
"step_0_1_0_0_0": MediaPlayer.NEXT_TRACK,
|
||||
"step_1_1_0_0_0": MediaPlayer.PREVIOUS_TRACK,
|
||||
}
|
||||
|
||||
def default_delay(self) -> int:
|
||||
return 500
|
||||
|
||||
|
||||
class E1766LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {"open": Light.ON, "close": Light.OFF}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.ON,
|
||||
1003: Light.ON_FULL_BRIGHTNESS,
|
||||
2002: Light.OFF,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"up_open": Light.ON,
|
||||
"down_close": Light.OFF,
|
||||
}
|
||||
|
||||
|
||||
class E1766Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {"open": Z2MLight.ON, "close": Z2MLight.OFF}
|
||||
|
||||
|
||||
class E1766SwitchController(SwitchController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {"open": Switch.ON, "close": Switch.OFF}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {1002: Switch.ON, 2002: Switch.OFF}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {"up_open": Switch.ON, "down_close": Switch.OFF}
|
||||
|
||||
|
||||
class E1766CoverController(CoverController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"open": Cover.TOGGLE_OPEN,
|
||||
"close": Cover.TOGGLE_CLOSE,
|
||||
"stop": Cover.STOP,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Cover.TOGGLE_OPEN,
|
||||
1003: Cover.STOP,
|
||||
2002: Cover.TOGGLE_CLOSE,
|
||||
2003: Cover.STOP,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"up_open": Cover.TOGGLE_OPEN,
|
||||
"down_close": Cover.TOGGLE_CLOSE,
|
||||
"stop": Cover.STOP,
|
||||
}
|
||||
|
||||
|
||||
class E1812LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.TOGGLE,
|
||||
"brightness_move_up": Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"brightness_stop": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.TOGGLE,
|
||||
1001: Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
1003: Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.TOGGLE,
|
||||
"move_with_on_off": Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"stop": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class E1812Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Z2MLight.TOGGLE,
|
||||
"brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"brightness_stop": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class E1812SwitchController(SwitchController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {"on": Switch.TOGGLE}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {1002: Switch.TOGGLE}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {"on": Light.TOGGLE}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class E2002LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON,
|
||||
"off": Light.OFF,
|
||||
"arrow_left_click": Light.CLICK_COLOR_DOWN,
|
||||
"arrow_right_click": Light.CLICK_COLOR_UP,
|
||||
"brightness_move_up": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_stop": Light.RELEASE,
|
||||
"brightness_move_down": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"arrow_left_hold": Light.HOLD_COLOR_DOWN,
|
||||
"arrow_left_release": Light.RELEASE,
|
||||
"arrow_right_hold": Light.HOLD_COLOR_UP,
|
||||
"arrow_right_release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.ON,
|
||||
2002: Light.OFF,
|
||||
3002: Light.CLICK_COLOR_DOWN,
|
||||
4002: Light.CLICK_COLOR_UP,
|
||||
1001: Light.HOLD_BRIGHTNESS_UP,
|
||||
1003: Light.RELEASE,
|
||||
2001: Light.HOLD_BRIGHTNESS_DOWN,
|
||||
2003: Light.RELEASE,
|
||||
3001: Light.HOLD_COLOR_DOWN,
|
||||
3003: Light.RELEASE,
|
||||
4001: Light.HOLD_COLOR_UP,
|
||||
4003: Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON,
|
||||
"off": Light.OFF,
|
||||
"press_257_13_0": Light.CLICK_COLOR_DOWN,
|
||||
"press_256_13_0": Light.CLICK_COLOR_UP,
|
||||
"move_with_on_off_0_83": Light.HOLD_BRIGHTNESS_UP,
|
||||
"move_1_83_0_0": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"hold_3329_0": Light.HOLD_COLOR_DOWN,
|
||||
"hold_3328_0": Light.HOLD_COLOR_UP,
|
||||
"stop": Light.RELEASE,
|
||||
"stop_with_on_off": Light.RELEASE,
|
||||
"release": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class W2049LightController(E2002LightController):
|
||||
async def initialize(self) -> None:
|
||||
await super().initialize()
|
||||
self.log(
|
||||
"⚠️ `W2049LightController` is deprecated and will be removed. Use `E2002LightController` instead.",
|
||||
level="WARNING",
|
||||
ascii_encode=False,
|
||||
)
|
||||
|
||||
|
||||
class E2002Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Z2MLight.ON,
|
||||
"off": Z2MLight.OFF,
|
||||
"arrow_left_click": Z2MLight.CLICK_COLOR_TEMP_DOWN,
|
||||
"arrow_right_click": Z2MLight.CLICK_COLOR_TEMP_UP,
|
||||
"brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_stop": Z2MLight.RELEASE,
|
||||
"brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"arrow_left_hold": Z2MLight.HOLD_COLOR_TEMP_DOWN,
|
||||
"arrow_left_release": Z2MLight.RELEASE,
|
||||
"arrow_right_hold": Z2MLight.HOLD_COLOR_TEMP_UP,
|
||||
"arrow_right_release": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class E2002MediaPlayerController(MediaPlayerController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": MediaPlayer.PLAY_PAUSE,
|
||||
"off": MediaPlayer.CLICK_VOLUME_DOWN,
|
||||
"arrow_left_click": MediaPlayer.PREVIOUS_TRACK,
|
||||
"arrow_right_click": MediaPlayer.NEXT_TRACK,
|
||||
"arrow_left_hold": MediaPlayer.PREVIOUS_SOURCE,
|
||||
"arrow_left_release": MediaPlayer.RELEASE,
|
||||
"arrow_right_hold": MediaPlayer.NEXT_SOURCE,
|
||||
"arrow_right_release": MediaPlayer.RELEASE,
|
||||
"brightness_move_up": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"brightness_move_down": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"brightness_stop": MediaPlayer.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: MediaPlayer.PLAY_PAUSE,
|
||||
2002: MediaPlayer.CLICK_VOLUME_DOWN,
|
||||
3002: MediaPlayer.PREVIOUS_TRACK,
|
||||
4002: MediaPlayer.NEXT_TRACK,
|
||||
1001: MediaPlayer.HOLD_VOLUME_UP,
|
||||
1003: MediaPlayer.RELEASE,
|
||||
2001: MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
2003: MediaPlayer.RELEASE,
|
||||
3001: MediaPlayer.PREVIOUS_SOURCE,
|
||||
3003: MediaPlayer.RELEASE,
|
||||
4001: MediaPlayer.NEXT_SOURCE,
|
||||
4003: MediaPlayer.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": MediaPlayer.PLAY_PAUSE,
|
||||
"off": MediaPlayer.CLICK_VOLUME_DOWN,
|
||||
"press_257_13_0": MediaPlayer.PREVIOUS_TRACK,
|
||||
"press_256_13_0": MediaPlayer.NEXT_TRACK,
|
||||
"move_with_on_off_0_83": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"move_1_83_0_0": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"hold_3329_0": MediaPlayer.PREVIOUS_SOURCE,
|
||||
"hold_3328_0": MediaPlayer.NEXT_SOURCE,
|
||||
"stop": MediaPlayer.RELEASE,
|
||||
"stop_with_on_off": MediaPlayer.RELEASE,
|
||||
"release": MediaPlayer.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class W2049MediaPlayerController(E2002MediaPlayerController):
|
||||
async def initialize(self) -> None:
|
||||
await super().initialize()
|
||||
self.log(
|
||||
"⚠️ `W2049MediaPlayerController` is deprecated and will be removed. "
|
||||
"Use `E2002MediaPlayerController` instead.",
|
||||
level="WARNING",
|
||||
ascii_encode=False,
|
||||
)
|
||||
|
||||
|
||||
class E2213LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1_long_press": Light.HOLD_BRIGHTNESS_UP,
|
||||
"2_long_press": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"1_short_release": Light.ON,
|
||||
"2_short_release": Light.OFF,
|
||||
"1_long_release": Light.RELEASE,
|
||||
"2_long_release": Light.RELEASE,
|
||||
"1_double_press": Light.HOLD_COLOR_UP,
|
||||
"2_double_press": Light.HOLD_COLOR_DOWN,
|
||||
}
|
||||
|
||||
|
||||
class E2213Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1_long_press": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"2_long_press": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"1_short_release": Z2MLight.ON,
|
||||
"2_short_release": Z2MLight.OFF,
|
||||
"1_long_release": Z2MLight.RELEASE,
|
||||
"2_long_release": Z2MLight.RELEASE,
|
||||
"1_double_press": Z2MLight.HOLD_COLOR_TEMP_UP,
|
||||
"2_double_press": Z2MLight.HOLD_COLOR_TEMP_DOWN,
|
||||
}
|
||||
|
||||
|
||||
class E2123MediaPlayerController(MediaPlayerController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"toggle": MediaPlayer.PLAY_PAUSE, # click Play button
|
||||
"play_pause": MediaPlayer.PLAY_PAUSE, # click Play button
|
||||
"track_next": MediaPlayer.NEXT_TRACK, # click Next Track
|
||||
"track_previous": MediaPlayer.PREVIOUS_TRACK, # click Previous Track
|
||||
"volume_up": MediaPlayer.CLICK_VOLUME_UP, # click + (Volume up)
|
||||
"volume_up_hold": MediaPlayer.CLICK_VOLUME_UP, # hold + (Volume up)
|
||||
"volume_down": MediaPlayer.CLICK_VOLUME_DOWN, # click - (Volume down)
|
||||
"volume_down_hold": MediaPlayer.CLICK_VOLUME_DOWN, # hold - (Volume down)
|
||||
"dots_1_initial_press": MediaPlayer.PREVIOUS_SOURCE, # click . (Previous Source)
|
||||
"dots_2_initial_press": MediaPlayer.NEXT_SOURCE, # click .. (Next Source)
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: MediaPlayer.PLAY, # Play; Press/Release or Hold
|
||||
2001: MediaPlayer.HOLD_VOLUME_UP, # +; Hold (every 0.2s)
|
||||
2002: MediaPlayer.RELEASE, # +; Press/Release
|
||||
3001: MediaPlayer.HOLD_VOLUME_DOWN, # +; Hold (every 0.2s)
|
||||
3002: MediaPlayer.RELEASE, # -; Press/Release
|
||||
4002: MediaPlayer.PREVIOUS_TRACK, # Previous; Press/Release or Hold
|
||||
5002: MediaPlayer.NEXT_TRACK, # Next; Press/Release or Hold
|
||||
6000: MediaPlayer.PREVIOUS_SOURCE, # .; Initial Press
|
||||
7000: MediaPlayer.NEXT_SOURCE, # ..; Initial Press
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"toggle": MediaPlayer.PLAY_PAUSE, # click Play button
|
||||
"play_pause": MediaPlayer.PLAY_PAUSE, # click Play button
|
||||
"step_0_1_0_0_0": MediaPlayer.NEXT_TRACK, # click Next Track
|
||||
"step_1_1_0_0_0": MediaPlayer.PREVIOUS_TRACK, # click Previous Track
|
||||
"move_with_on_off_0_255": MediaPlayer.CLICK_VOLUME_UP, # click + (Volume up)
|
||||
"move_0_255_0_0": MediaPlayer.HOLD_VOLUME_UP, # hold + (Volume up)
|
||||
"move_with_on_off_1_255": MediaPlayer.CLICK_VOLUME_DOWN, # click - (Volume down)
|
||||
"move_1_255_0_0": MediaPlayer.HOLD_VOLUME_DOWN, # hold - (Volume down)
|
||||
"2_initial_press_0": MediaPlayer.PREVIOUS_SOURCE, # click . (Previous Source)
|
||||
"3_initial_press_0": MediaPlayer.NEXT_SOURCE, # click .. (Next Source)
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command = data["command"]
|
||||
args = data["args"]
|
||||
if isinstance(args, dict):
|
||||
args = args["args"]
|
||||
args = list(map(str, args))
|
||||
action = command
|
||||
if not (command == "stop" or command == "release"):
|
||||
if len(args) > 0:
|
||||
action += "_" + "_".join(args)
|
||||
endpoint = data["endpoint_id"]
|
||||
# We only need to add the endpoint ID for the dot buttons, which use 2 and 3
|
||||
if endpoint != 1:
|
||||
return f"{data['endpoint_id']}_{action}"
|
||||
else:
|
||||
return action
|
||||
@@ -1,100 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
from cx_const import DefaultActionsMapping, Light, Z2MLight
|
||||
from cx_core import LightController, Z2MLightController
|
||||
from cx_core.integration import EventData
|
||||
|
||||
|
||||
def get_zha_action_LegrandWallController(data: dict[str, Any]) -> str:
|
||||
endpoint_id = data.get("endpoint_id", 1)
|
||||
command: str = data["command"]
|
||||
action = command
|
||||
args = data.get("args", {})
|
||||
args_mapping = {0: "up", 1: "down"}
|
||||
if command == "move":
|
||||
action = "_".join((action, args_mapping[args[0]]))
|
||||
action = "_".join((str(endpoint_id), action))
|
||||
return action
|
||||
|
||||
|
||||
class Legrand600083LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON,
|
||||
"off": Light.OFF,
|
||||
"brightness_move_up": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1_on": Light.ON,
|
||||
"1_off": Light.OFF,
|
||||
"1_move_up": Light.HOLD_BRIGHTNESS_UP,
|
||||
"1_move_down": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"1_stop": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
return get_zha_action_LegrandWallController(data)
|
||||
|
||||
|
||||
class Legrand600083Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Z2MLight.ON,
|
||||
"off": Z2MLight.OFF,
|
||||
"brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class Legrand600088LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on_left": Light.ON,
|
||||
"off_left": Light.OFF,
|
||||
"brightness_move_up_left": Light.HOLD_COLOR_UP,
|
||||
"brightness_move_down_left": Light.HOLD_COLOR_DOWN,
|
||||
"brightness_stop_left": Light.RELEASE,
|
||||
"on_right": Light.ON_FULL_BRIGHTNESS,
|
||||
"off_right": Light.ON_MIN_BRIGHTNESS,
|
||||
"brightness_move_up_right": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_right": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_right": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1_on": Light.ON,
|
||||
"1_off": Light.OFF,
|
||||
"1_move_up": Light.HOLD_COLOR_UP,
|
||||
"1_move_down": Light.HOLD_COLOR_DOWN,
|
||||
"1_stop": Light.RELEASE,
|
||||
"2_on": Light.ON_FULL_BRIGHTNESS,
|
||||
"2_off": Light.ON_MIN_BRIGHTNESS,
|
||||
"2_move_up": Light.HOLD_BRIGHTNESS_UP,
|
||||
"2_move_down": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"2_stop": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
return get_zha_action_LegrandWallController(data)
|
||||
|
||||
|
||||
class Legrand600088Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on_left": Z2MLight.ON,
|
||||
"off_left": Z2MLight.OFF,
|
||||
"brightness_move_up_left": Z2MLight.HOLD_COLOR_TEMP_UP,
|
||||
"brightness_move_down_left": Z2MLight.HOLD_COLOR_TEMP_DOWN,
|
||||
"brightness_stop_left": Z2MLight.RELEASE,
|
||||
"on_right": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"off_right": Z2MLight.ON_MIN_BRIGHTNESS,
|
||||
"brightness_move_up_right": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_right": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_right": Z2MLight.RELEASE,
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, Z2MLight
|
||||
from cx_core import LightController, Z2MLightController
|
||||
|
||||
|
||||
class ZS23000278LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON,
|
||||
"off": Light.OFF,
|
||||
"brightness_step_up": Light.CLICK_BRIGHTNESS_UP,
|
||||
"brightness_step_down": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"brightness_move_to_level": Light.BRIGHTNESS_FROM_CONTROLLER_LEVEL,
|
||||
"brightness_move_up": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Light.RELEASE,
|
||||
"color_temperature_move": Light.COLORTEMP_FROM_CONTROLLER,
|
||||
"color_temperature_move_up": Light.CLICK_COLOR_TEMP_UP,
|
||||
"color_temperature_move_down": Light.CLICK_COLOR_TEMP_DOWN,
|
||||
"color_move": Light.XYCOLOR_FROM_CONTROLLER,
|
||||
}
|
||||
|
||||
|
||||
class ZS23000278Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Z2MLight.ON,
|
||||
"off": Z2MLight.OFF,
|
||||
"brightness_step_up": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"brightness_step_down": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"brightness_move_to_level": Z2MLight.BRIGHTNESS_FROM_CONTROLLER_LEVEL,
|
||||
"brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Z2MLight.RELEASE,
|
||||
"color_temperature_move": Z2MLight.COLORTEMP_FROM_CONTROLLER,
|
||||
"color_temperature_move_up": Z2MLight.CLICK_COLOR_TEMP_UP,
|
||||
"color_temperature_move_down": Z2MLight.CLICK_COLOR_TEMP_DOWN,
|
||||
"color_move": Z2MLight.XYCOLOR_FROM_CONTROLLER,
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, Z2MLight
|
||||
from cx_core import LightController, Z2MLightController
|
||||
|
||||
|
||||
class HG06323LightController(LightController):
|
||||
# Different states reported from the controller:
|
||||
# on, off, brightness_step_up, brightness_move_up,
|
||||
# brightness_step_down, brightness_move_down, brightness_stop
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON,
|
||||
"brightness_step_up": Light.CLICK_BRIGHTNESS_UP,
|
||||
"brightness_move_up": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_stop": Light.RELEASE,
|
||||
"brightness_step_down": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"brightness_move_down": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"off": Light.OFF,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON,
|
||||
"step_0_51_10": Light.CLICK_BRIGHTNESS_UP,
|
||||
"move_0_51": Light.HOLD_BRIGHTNESS_UP,
|
||||
"stop": Light.RELEASE,
|
||||
"step_1_51_10": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"move_1_51": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"off": Light.OFF,
|
||||
}
|
||||
|
||||
|
||||
class HG06323Z2MLightController(Z2MLightController):
|
||||
# Different states reported from the controller:
|
||||
# on, off, brightness_step_up, brightness_move_up,
|
||||
# brightness_step_down, brightness_move_down, brightness_stop
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Z2MLight.ON,
|
||||
"brightness_step_up": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_stop": Z2MLight.RELEASE,
|
||||
"brightness_step_down": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"off": Z2MLight.OFF,
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, MediaPlayer, Z2MLight
|
||||
from cx_core import LightController, MediaPlayerController
|
||||
from cx_core.type.z2m_light_controller import Z2MLightController
|
||||
|
||||
|
||||
class LZL4BWHL01LightController(LightController):
|
||||
# Each button press fires an event but no separate
|
||||
# hold event. Press of up or down generates a stop event
|
||||
# when released.
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.ON_FULL_BRIGHTNESS,
|
||||
2001: Light.HOLD_BRIGHTNESS_UP,
|
||||
2003: Light.RELEASE,
|
||||
3001: Light.HOLD_BRIGHTNESS_DOWN,
|
||||
3003: Light.RELEASE,
|
||||
4002: Light.OFF,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"move_to_level_with_on_off_254_4": Light.ON_FULL_BRIGHTNESS,
|
||||
"step_with_on_off_0_30_6": Light.HOLD_BRIGHTNESS_UP,
|
||||
"step_1_30_6": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"move_to_level_with_on_off_0_4": Light.OFF,
|
||||
"stop": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class Z31BRLLightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"brightness_move_to_level": Light.BRIGHTNESS_FROM_CONTROLLER_LEVEL,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.TOGGLE,
|
||||
2002: Light.CLICK_BRIGHTNESS_UP,
|
||||
3002: Light.CLICK_BRIGHTNESS_DOWN,
|
||||
}
|
||||
|
||||
|
||||
class Z31BRLZ2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"brightness_move_to_level": Z2MLight.BRIGHTNESS_FROM_CONTROLLER_LEVEL,
|
||||
}
|
||||
|
||||
|
||||
class LutronPJ22BLightController(LightController):
|
||||
def get_state_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1": Light.ON_FULL_BRIGHTNESS,
|
||||
"4": Light.OFF,
|
||||
}
|
||||
|
||||
def get_lutron_caseta_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_2_press": Light.ON,
|
||||
"button_4_press": Light.OFF,
|
||||
}
|
||||
|
||||
|
||||
class LutronPJ22BMediaPlayerController(MediaPlayerController):
|
||||
def get_state_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1": MediaPlayer.PLAY_PAUSE,
|
||||
"4": MediaPlayer.NEXT_TRACK,
|
||||
}
|
||||
|
||||
def get_lutron_caseta_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_2_press": MediaPlayer.PLAY_PAUSE,
|
||||
"button_4_press": MediaPlayer.NEXT_TRACK,
|
||||
}
|
||||
|
||||
|
||||
class LutronPJ22BRLLightController(LightController):
|
||||
def get_state_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1": Light.ON_FULL_BRIGHTNESS,
|
||||
"8": Light.HOLD_BRIGHTNESS_UP,
|
||||
"16": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"4": Light.OFF,
|
||||
"0": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class LutronPJ22BRLMediaPlayerController(MediaPlayerController):
|
||||
def get_state_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1": MediaPlayer.PLAY_PAUSE,
|
||||
"8": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"16": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"4": MediaPlayer.NEXT_TRACK,
|
||||
"0": MediaPlayer.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class LutronPJ23BRLLightController(LightController):
|
||||
def get_state_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1": Light.ON_FULL_BRIGHTNESS,
|
||||
"8": Light.HOLD_BRIGHTNESS_UP,
|
||||
"2": Light.SET_HALF_BRIGHTNESS,
|
||||
"16": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"4": Light.OFF,
|
||||
"0": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_lutron_caseta_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_2_press": Light.ON_FULL_BRIGHTNESS,
|
||||
"button_4_press": Light.OFF,
|
||||
"button_3_press": Light.SET_HALF_BRIGHTNESS,
|
||||
"button_5_press": Light.HOLD_BRIGHTNESS_UP,
|
||||
"button_5_release": Light.RELEASE,
|
||||
"button_6_press": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"button_6_release": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class LutronPJ23BRLMediaPlayerController(MediaPlayerController):
|
||||
def get_state_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1": MediaPlayer.PLAY_PAUSE,
|
||||
"8": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"2": MediaPlayer.NEXT_SOURCE,
|
||||
"16": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"4": MediaPlayer.NEXT_TRACK,
|
||||
"0": MediaPlayer.RELEASE,
|
||||
}
|
||||
|
||||
def get_lutron_caseta_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_2_press": MediaPlayer.PLAY_PAUSE,
|
||||
"button_4_press": MediaPlayer.NEXT_TRACK,
|
||||
"button_3_press": MediaPlayer.NEXT_SOURCE,
|
||||
"button_5_press": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"button_5_release": MediaPlayer.RELEASE,
|
||||
"button_6_press": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"button_6_release": MediaPlayer.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class LutronPJ24BLightController(LightController):
|
||||
def get_state_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1": Light.ON_FULL_BRIGHTNESS,
|
||||
"2": Light.HOLD_BRIGHTNESS_UP,
|
||||
"4": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"8": Light.OFF,
|
||||
"0": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class LutronPJ24BMediaPlayerController(MediaPlayerController):
|
||||
def get_state_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1": MediaPlayer.PLAY_PAUSE,
|
||||
"2": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"4": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"8": MediaPlayer.NEXT_TRACK,
|
||||
"0": MediaPlayer.RELEASE,
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, Z2MLight
|
||||
from cx_core import LightController, Z2MLightController
|
||||
from cx_core.controller import Controller
|
||||
from cx_core.integration import EventData
|
||||
|
||||
|
||||
class MLI404011LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.TOGGLE,
|
||||
"off": Light.TOGGLE,
|
||||
"brightness_down_click": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"brightness_down_hold": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_down_release": Light.RELEASE,
|
||||
"brightness_up_click": Light.CLICK_BRIGHTNESS_UP,
|
||||
"brightness_up_hold": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_up_release": Light.RELEASE,
|
||||
"color_wheel": Light.XYCOLOR_FROM_CONTROLLER, # Color ring press
|
||||
"color_temp": Light.COLORTEMP_FROM_CONTROLLER, # warm or cold
|
||||
"scene_1": None, # sunset button
|
||||
"scene_2": None, # party button
|
||||
"scene_3": None, # reading button
|
||||
"scene_4": None, # fire button
|
||||
"scene_5": None, # heart button
|
||||
"scene_6": None, # night button
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.TOGGLE,
|
||||
2001: Light.HOLD_BRIGHTNESS_UP,
|
||||
2002: Light.CLICK_BRIGHTNESS_UP,
|
||||
2003: Light.RELEASE,
|
||||
3001: Light.HOLD_BRIGHTNESS_DOWN,
|
||||
3002: Light.CLICK_BRIGHTNESS_DOWN,
|
||||
3003: Light.RELEASE,
|
||||
4002: Light.CLICK_COLOR_UP,
|
||||
5002: Light.CLICK_COLOR_DOWN,
|
||||
6002: Light.XYCOLOR_FROM_CONTROLLER, # Color ring press
|
||||
7002: None, # reading button
|
||||
8002: None, # sunset button
|
||||
9002: None, # party button
|
||||
10002: None, # night button
|
||||
11002: None, # fire button
|
||||
12002: None, # heart button
|
||||
}
|
||||
|
||||
|
||||
class MLI404011Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Z2MLight.TOGGLE,
|
||||
"off": Z2MLight.TOGGLE,
|
||||
"brightness_down_click": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"brightness_down_hold": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_down_release": Z2MLight.RELEASE,
|
||||
"brightness_up_click": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"brightness_up_hold": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_up_release": Z2MLight.RELEASE,
|
||||
"color_wheel": Z2MLight.XYCOLOR_FROM_CONTROLLER, # Color ring press
|
||||
"color_temp": Z2MLight.COLORTEMP_FROM_CONTROLLER, # warm or cold
|
||||
"scene_1": None, # sunset button
|
||||
"scene_2": None, # party button
|
||||
"scene_3": None, # reading button
|
||||
"scene_4": None, # fire button
|
||||
"scene_5": None, # heart button
|
||||
"scene_6": None, # night button
|
||||
}
|
||||
|
||||
|
||||
class MLI404002Controller(Controller):
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
if command not in ("move", "step"):
|
||||
return command
|
||||
args = data["args"]
|
||||
direction_mapping = {0: "up", 1: "down"}
|
||||
return f"{command}_{direction_mapping[args[0]]}"
|
||||
|
||||
|
||||
class MLI404002LightController(MLI404002Controller, LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.TOGGLE,
|
||||
"off": Light.TOGGLE,
|
||||
"brightness_step_up": Light.CLICK_BRIGHTNESS_UP,
|
||||
"brightness_step_down": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"brightness_move_up": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Light.RELEASE,
|
||||
"recall_1": Light.ON_FULL_BRIGHTNESS,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.TOGGLE,
|
||||
"off": Light.TOGGLE,
|
||||
"move_up": Light.HOLD_BRIGHTNESS_UP,
|
||||
"move_down": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"stop": Light.RELEASE,
|
||||
"step_up": Light.CLICK_BRIGHTNESS_UP,
|
||||
"step_down": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"recall": Light.ON_FULL_BRIGHTNESS,
|
||||
}
|
||||
|
||||
|
||||
class MLI404002Z2MLightController(MLI404002Controller, Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Z2MLight.TOGGLE,
|
||||
"off": Z2MLight.TOGGLE,
|
||||
"brightness_step_up": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"brightness_step_down": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Z2MLight.RELEASE,
|
||||
"recall_1": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, Z2MLight
|
||||
from cx_core import LightController, Z2MLightController
|
||||
|
||||
|
||||
class Namron4512773LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on_l1": Light.ON,
|
||||
"off_l1": Light.OFF,
|
||||
"brightness_move_up_l1": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_l1": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_l1": Light.RELEASE,
|
||||
"on_l2": Light.ON,
|
||||
"off_l2": Light.OFF,
|
||||
"brightness_move_up_l2": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_l2": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_l2": Light.RELEASE,
|
||||
"on_l3": Light.ON,
|
||||
"off_l3": Light.OFF,
|
||||
"brightness_move_up_l3": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_l3": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_l3": Light.RELEASE,
|
||||
"on_l4": Light.ON,
|
||||
"off_l4": Light.OFF,
|
||||
"brightness_move_up_l4": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_l4": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_l4": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class Namron4512773Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on_l1": Z2MLight.ON,
|
||||
"off_l1": Z2MLight.OFF,
|
||||
"brightness_move_up_l1": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_l1": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_l1": Z2MLight.RELEASE,
|
||||
"on_l2": Z2MLight.ON,
|
||||
"off_l2": Z2MLight.OFF,
|
||||
"brightness_move_up_l2": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_l2": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_l2": Z2MLight.RELEASE,
|
||||
"on_l3": Z2MLight.ON,
|
||||
"off_l3": Z2MLight.OFF,
|
||||
"brightness_move_up_l3": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_l3": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_l3": Z2MLight.RELEASE,
|
||||
"on_l4": Z2MLight.ON,
|
||||
"off_l4": Z2MLight.OFF,
|
||||
"brightness_move_up_l4": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_l4": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_l4": Z2MLight.RELEASE,
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light
|
||||
from cx_core import LightController
|
||||
from cx_core.integration import EventData
|
||||
|
||||
|
||||
class OsramAC025XX00NJLightController(LightController):
|
||||
# This mapping works for: AC0251100NJ / AC0251400NJ / AC0251600NJ / AC0251700NJ
|
||||
# (different models are just different colours)
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.ON, # Click Arrow up
|
||||
1001: Light.HOLD_BRIGHTNESS_UP, # Hold Arrow Up
|
||||
1003: Light.RELEASE, # Release Arrow Up
|
||||
2002: Light.OFF, # Click Arrow down
|
||||
2001: Light.HOLD_BRIGHTNESS_DOWN, # Hold Arrow down
|
||||
2003: Light.RELEASE, # Release Arrow down
|
||||
3002: Light.SYNC, # Click Circle button
|
||||
3001: Light.HOLD_COLOR_UP, # Hold Circle button
|
||||
3003: Light.RELEASE, # Release Circle button
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1_on": Light.ON,
|
||||
"1_move_with_on_off": Light.HOLD_BRIGHTNESS_UP,
|
||||
"1_stop": Light.RELEASE,
|
||||
"3_move_to_level_with_on_off": None,
|
||||
"3_move_to_color_temp": Light.SYNC,
|
||||
"3_move_to_saturation": Light.HOLD_COLOR_UP,
|
||||
"3_move_hue": Light.RELEASE,
|
||||
"2_off": Light.OFF,
|
||||
"2_move": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"2_stop": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
return f"{data['endpoint_id']}_{data['command']}"
|
||||
@@ -1,326 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, Z2MLight
|
||||
from cx_core import LightController, Z2MLightController
|
||||
from cx_core.integration import EventData
|
||||
|
||||
|
||||
class HueDimmerController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on_press_release": Light.ON,
|
||||
"on_hold": Light.HOLD_COLOR_UP,
|
||||
"on_hold_release": Light.RELEASE,
|
||||
"up_press_release": Light.CLICK_BRIGHTNESS_UP,
|
||||
"up_hold": Light.HOLD_BRIGHTNESS_UP,
|
||||
"up_hold_release": Light.RELEASE,
|
||||
"down_press_release": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"down_hold": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"down_hold_release": Light.RELEASE,
|
||||
"off_press_release": Light.OFF,
|
||||
"off_hold": Light.HOLD_COLOR_DOWN,
|
||||
"off_hold_release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.ON,
|
||||
1001: Light.HOLD_COLOR_UP,
|
||||
1003: Light.RELEASE,
|
||||
2002: Light.CLICK_BRIGHTNESS_UP,
|
||||
2001: Light.HOLD_BRIGHTNESS_UP,
|
||||
2003: Light.RELEASE,
|
||||
3002: Light.CLICK_BRIGHTNESS_DOWN,
|
||||
3001: Light.HOLD_BRIGHTNESS_DOWN,
|
||||
3003: Light.RELEASE,
|
||||
4002: Light.OFF,
|
||||
4001: Light.HOLD_COLOR_DOWN,
|
||||
4003: Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"off_long_release": Light.RELEASE,
|
||||
"off_hold": Light.HOLD_COLOR_DOWN,
|
||||
"off_short_release": Light.OFF,
|
||||
"down_long_release": Light.RELEASE,
|
||||
"down_hold": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"down_short_release": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"up_long_release": Light.RELEASE,
|
||||
"up_hold": Light.HOLD_BRIGHTNESS_UP,
|
||||
"up_short_release": Light.CLICK_BRIGHTNESS_UP,
|
||||
"on_long_release": Light.RELEASE,
|
||||
"on_hold": Light.HOLD_COLOR_UP,
|
||||
"on_short_release": Light.ON,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class HueDimmerZ2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on_press_release": Z2MLight.ON,
|
||||
"on_hold": Z2MLight.HOLD_COLOR_TEMP_UP,
|
||||
"on_hold_release": Z2MLight.RELEASE,
|
||||
"up_press_release": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"up_hold": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"up_hold_release": Z2MLight.RELEASE,
|
||||
"down_press_release": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"down_hold": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"down_hold_release": Z2MLight.RELEASE,
|
||||
"off_press_release": Z2MLight.OFF,
|
||||
"off_hold": Z2MLight.HOLD_COLOR_TEMP_DOWN,
|
||||
"off_hold_release": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class Philips929002398602LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on_press_release": Light.TOGGLE,
|
||||
"on_hold": Light.TOGGLE,
|
||||
"on_hold_release": Light.RELEASE,
|
||||
"up_press_release": Light.CLICK_BRIGHTNESS_UP,
|
||||
"up_hold": Light.HOLD_BRIGHTNESS_UP,
|
||||
"up_hold_release": Light.RELEASE,
|
||||
"down_press_release": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"down_hold": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"down_hold_release": Light.RELEASE,
|
||||
"off_press_release": Light.CLICK_COLOR_UP,
|
||||
"off_hold": Light.HOLD_COLOR_DOWN,
|
||||
"off_hold_release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.TOGGLE,
|
||||
1001: Light.TOGGLE,
|
||||
1003: Light.RELEASE,
|
||||
2002: Light.CLICK_BRIGHTNESS_UP,
|
||||
2001: Light.HOLD_BRIGHTNESS_UP,
|
||||
2003: Light.RELEASE,
|
||||
3002: Light.CLICK_BRIGHTNESS_DOWN,
|
||||
3001: Light.HOLD_BRIGHTNESS_DOWN,
|
||||
3003: Light.RELEASE,
|
||||
4002: Light.CLICK_COLOR_UP,
|
||||
4001: Light.HOLD_COLOR_DOWN,
|
||||
4003: Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"off_long_release": Light.RELEASE,
|
||||
"off_hold": Light.HOLD_COLOR_DOWN,
|
||||
"off_short_release": Light.CLICK_COLOR_UP,
|
||||
"down_long_release": Light.RELEASE,
|
||||
"down_hold": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"down_short_release": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"up_long_release": Light.RELEASE,
|
||||
"up_hold": Light.HOLD_BRIGHTNESS_UP,
|
||||
"up_short_release": Light.CLICK_BRIGHTNESS_UP,
|
||||
"on_long_release": Light.RELEASE,
|
||||
"on_hold": Light.TOGGLE,
|
||||
"on_short_release": Light.TOGGLE,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class Philips929002398602Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on_press_release": Z2MLight.ON,
|
||||
"on_hold": Z2MLight.HOLD_COLOR_TEMP_UP,
|
||||
"on_hold_release": Z2MLight.RELEASE,
|
||||
"up_press_release": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"up_hold": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"up_hold_release": Z2MLight.RELEASE,
|
||||
"down_press_release": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"down_hold": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"down_hold_release": Z2MLight.RELEASE,
|
||||
"off_press_release": Z2MLight.OFF,
|
||||
"off_hold": Z2MLight.HOLD_COLOR_TEMP_DOWN,
|
||||
"off_hold_release": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class PTM215XLightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"press_1": Light.ON,
|
||||
"release_1": Light.RELEASE,
|
||||
"press_2": Light.OFF,
|
||||
"release_2": Light.RELEASE,
|
||||
"press_3": Light.ON_FULL_BRIGHTNESS,
|
||||
"release_3": Light.RELEASE,
|
||||
"press_4": Light.ON_MIN_BRIGHTNESS,
|
||||
"release_4": Light.RELEASE,
|
||||
"press_1_and_3": Light.ON_FULL_COLOR_TEMP,
|
||||
"release_1_and_3": Light.RELEASE,
|
||||
"press_2_and_4": Light.ON_MIN_COLOR_TEMP,
|
||||
"release_2_and_4": Light.RELEASE,
|
||||
"press_energy_bar": Light.SYNC,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.ON, # button_1_release
|
||||
1001: Light.SYNC, # button_1_hold
|
||||
1000: None, # button_1_press
|
||||
1003: None, # button_1_release_after_hold
|
||||
2002: Light.OFF, # button_2_release
|
||||
2001: Light.SYNC, # button_2_hold
|
||||
2000: None, # button_2_press
|
||||
2003: None, # button_2_release_after_hold
|
||||
3002: Light.ON_FULL_BRIGHTNESS, # button_3_release
|
||||
3001: Light.HOLD_BRIGHTNESS_UP, # button_3_hold
|
||||
3000: None, # button_3_press
|
||||
3003: Light.RELEASE, # button_3_release_after_hold
|
||||
4002: Light.ON_MIN_BRIGHTNESS, # button_4_release
|
||||
4001: Light.HOLD_BRIGHTNESS_DOWN, # button_4_hold
|
||||
4000: None, # button_4_press
|
||||
4003: Light.RELEASE, # button_4_release_after_hold
|
||||
5002: Light.ON_FULL_COLOR_TEMP, # button_1_3_release
|
||||
5001: Light.HOLD_COLOR_UP, # button_1_3_hold
|
||||
5000: None, # button_1_3_press
|
||||
5003: Light.RELEASE, # button_1_3_release_after_hold
|
||||
6002: Light.ON_MIN_COLOR_TEMP, # button_2_4_release
|
||||
6001: Light.HOLD_COLOR_DOWN, # button_2_4_hold
|
||||
6000: None, # button_2_4_press
|
||||
6003: Light.RELEASE, # button_2_4_release_after_hold
|
||||
}
|
||||
|
||||
|
||||
class HueSmartButtonLightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.TOGGLE,
|
||||
"off": Light.TOGGLE,
|
||||
"hold": Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1000: None, # Initial press
|
||||
1001: Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
1002: Light.TOGGLE,
|
||||
1003: Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class HueSmartButtonZ2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Z2MLight.TOGGLE,
|
||||
"off": Z2MLight.TOGGLE,
|
||||
"hold": Z2MLight.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"release": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class Philips929003017102LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
# "left_press": Light.TOGGLE,
|
||||
"left_press_release": Light.TOGGLE,
|
||||
"left_hold": Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"left_hold_release": Light.RELEASE,
|
||||
# "right_press": Light.TOGGLE,
|
||||
"right_press_release": Light.TOGGLE,
|
||||
"right_hold": Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"right_hold_release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1000: None, # Initial press
|
||||
1001: Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
1002: Light.TOGGLE,
|
||||
1003: Light.RELEASE,
|
||||
2000: None, # Initial press
|
||||
2001: Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
2002: Light.TOGGLE,
|
||||
2003: Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
# "left_press": Light.TOGGLE,
|
||||
"left_press_release": Light.TOGGLE,
|
||||
"left_hold": Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"left_hold_release": Light.RELEASE,
|
||||
# "right_press": Light.TOGGLE,
|
||||
"right_short_release": Light.TOGGLE,
|
||||
"right_hold": Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"right_long_release": Light.RELEASE,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class Philips929003017102Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
# "left_press": Z2MLight.TOGGLE,
|
||||
"left_press_release": Z2MLight.TOGGLE,
|
||||
"left_hold": Z2MLight.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"left_hold_release": Z2MLight.RELEASE,
|
||||
# "right_press": Z2MLight.TOGGLE,
|
||||
"right_press_release": Z2MLight.TOGGLE,
|
||||
"right_hold": Z2MLight.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"right_hold_release": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class PhilipsRDM002LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_1_press_release": Light.OFF,
|
||||
"button_1_hold": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"button_1_hold_release": Light.RELEASE,
|
||||
"button_2_press_release": Light.ON,
|
||||
"button_2_hold": Light.HOLD_BRIGHTNESS_UP,
|
||||
"button_2_hold_release": Light.RELEASE,
|
||||
"button_3_press_release": Light.CLICK_COLOR_DOWN,
|
||||
"button_3_hold": Light.HOLD_COLOR_DOWN,
|
||||
"button_3_hold_release": Light.RELEASE,
|
||||
"button_4_press_release": Light.CLICK_COLOR_UP,
|
||||
"button_4_hold": Light.HOLD_COLOR_UP,
|
||||
"button_4_hold_release": Light.RELEASE,
|
||||
"dial_rotate_left_step": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"dial_rotate_left_slow": Light.ON_MIN_COLOR_TEMP,
|
||||
"dial_rotate_left_fast": Light.ON_MIN_BRIGHTNESS,
|
||||
"dial_rotate_right_step": Light.CLICK_BRIGHTNESS_UP,
|
||||
"dial_rotate_right_slow": Light.ON_FULL_COLOR_TEMP,
|
||||
"dial_rotate_right_fast": Light.ON_FULL_BRIGHTNESS,
|
||||
}
|
||||
|
||||
|
||||
class PhilipsRDM002Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_1_press_release": Z2MLight.OFF,
|
||||
"button_1_hold": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"button_1_hold_release": Z2MLight.RELEASE,
|
||||
"button_2_press_release": Z2MLight.ON,
|
||||
"button_2_hold": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"button_2_hold_release": Z2MLight.RELEASE,
|
||||
"button_3_press_release": Z2MLight.CLICK_COLOR_TEMP_DOWN,
|
||||
"button_3_hold": Z2MLight.HOLD_COLOR_TEMP_DOWN,
|
||||
"button_3_hold_release": Z2MLight.RELEASE,
|
||||
"button_4_press_release": Z2MLight.CLICK_COLOR_TEMP_UP,
|
||||
"button_4_hold": Z2MLight.HOLD_COLOR_TEMP_UP,
|
||||
"button_4_hold_release": Z2MLight.RELEASE,
|
||||
"dial_rotate_left_step": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"dial_rotate_left_slow": Z2MLight.ON_MIN_COLOR_TEMP,
|
||||
"dial_rotate_left_fast": Z2MLight.ON_MIN_BRIGHTNESS,
|
||||
"dial_rotate_right_step": Light.CLICK_BRIGHTNESS_UP,
|
||||
"dial_rotate_right_slow": Z2MLight.ON_FULL_COLOR_TEMP,
|
||||
"dial_rotate_right_fast": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, Z2MLight
|
||||
from cx_core import LightController, Z2MLightController
|
||||
|
||||
|
||||
class Prolight5412748727388LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON,
|
||||
"off": Light.OFF,
|
||||
"brightness_move_up": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Light.RELEASE,
|
||||
"brightness_move_to_level": Light.BRIGHTNESS_FROM_CONTROLLER_LEVEL,
|
||||
"color_temperature_move": Light.COLORTEMP_FROM_CONTROLLER,
|
||||
"color_temperature_move_up": Light.CLICK_COLOR_TEMP_UP,
|
||||
"color_temperature_move_down": Light.CLICK_COLOR_TEMP_DOWN,
|
||||
"color_move": Light.XYCOLOR_FROM_CONTROLLER,
|
||||
}
|
||||
|
||||
|
||||
class Prolight5412748727388Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Z2MLight.ON,
|
||||
"off": Z2MLight.OFF,
|
||||
"brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Z2MLight.RELEASE,
|
||||
"brightness_move_to_level": Z2MLight.BRIGHTNESS_FROM_CONTROLLER_LEVEL,
|
||||
"color_temperature_move": Z2MLight.COLORTEMP_FROM_CONTROLLER,
|
||||
"color_temperature_move_up": Z2MLight.CLICK_COLOR_TEMP_UP,
|
||||
"color_temperature_move_down": Z2MLight.CLICK_COLOR_TEMP_DOWN,
|
||||
"color_move": Z2MLight.XYCOLOR_FROM_CONTROLLER,
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, Z2MLight
|
||||
from cx_core import LightController, Z2MLightController
|
||||
from cx_core.controller import action
|
||||
from cx_core.integration import EventData
|
||||
from cx_core.integration.zha import ZHAIntegration
|
||||
|
||||
|
||||
class ZB5121LightController(LightController):
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON,
|
||||
"off": Light.OFF,
|
||||
"step_with_on_off_0_32_0": Light.CLICK_BRIGHTNESS_UP, # Click brightness up
|
||||
"move_with_on_off_0_50": Light.HOLD_BRIGHTNESS_UP, # Hold brightness up
|
||||
"step_with_on_off_1_32_0": Light.CLICK_BRIGHTNESS_DOWN, # Click brightness down
|
||||
"move_with_on_off_1_50": Light.HOLD_BRIGHTNESS_DOWN, # Hold brightness down
|
||||
"recall_0_1": None, # Click clapperboard
|
||||
"stop": Light.RELEASE, # long release
|
||||
}
|
||||
|
||||
|
||||
class ZB5122LightController(LightController):
|
||||
@action
|
||||
async def colortemp_from_controller(self, extra: EventData) -> None:
|
||||
if isinstance(self.integration, ZHAIntegration):
|
||||
await self._on(color_temp=extra["args"][0])
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON, # Click light on
|
||||
"off": Light.OFF, # Click light off
|
||||
"hold_brightness_up": Light.HOLD_BRIGHTNESS_UP, # Hold light on
|
||||
"hold_brightness_down": Light.HOLD_BRIGHTNESS_DOWN, # Hold light off
|
||||
"stop": Light.RELEASE, # long release
|
||||
"move_to_color": Light.CLICK_XY_COLOR_UP, # click RGB
|
||||
"move_hue": Light.HOLD_XY_COLOR_UP, # hold RGB
|
||||
"stop_move_hue": Light.RELEASE, # release RGB
|
||||
"move_to_color_temp": Light.COLORTEMP_FROM_CONTROLLER, # click CW
|
||||
"move_color_temp": Light.HOLD_COLOR_TEMP_TOGGLE, # hold CW
|
||||
"stop_move_step": Light.RELEASE, # release CW
|
||||
"recall_0_1": None, # Click clapperboard
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
if command == "move_with_on_off":
|
||||
return (
|
||||
"hold_brightness_up" if data["args"][0] == 0 else "hold_brightness_down"
|
||||
)
|
||||
elif command == "move_hue":
|
||||
return "stop_move_hue" if tuple(data["args"]) == (0, 0) else "move_hue"
|
||||
return command
|
||||
|
||||
|
||||
class ZB3009LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.TOGGLE,
|
||||
"off": Light.TOGGLE,
|
||||
"brightness_move_up": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Light.RELEASE,
|
||||
"color_temperature_move_down": Light.CLICK_COLOR_TEMP_DOWN,
|
||||
"color_temperature_move_up": Light.CLICK_COLOR_TEMP_UP,
|
||||
"color_temperature_move": Light.COLORTEMP_FROM_CONTROLLER,
|
||||
"color_move": Light.XYCOLOR_FROM_CONTROLLER,
|
||||
"hue_move": None, # Play/pause button
|
||||
"recall_1": None, # Scene 1
|
||||
"recall_3": None, # Scene 2
|
||||
"recall_2": None, # Scene 3
|
||||
}
|
||||
|
||||
|
||||
class ZB3009Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Z2MLight.TOGGLE,
|
||||
"off": Z2MLight.TOGGLE,
|
||||
"brightness_move_up": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop": Z2MLight.RELEASE,
|
||||
"color_temperature_move_down": Z2MLight.CLICK_COLOR_TEMP_DOWN,
|
||||
"color_temperature_move_up": Z2MLight.CLICK_COLOR_TEMP_UP,
|
||||
"color_temperature_move": Z2MLight.COLORTEMP_FROM_CONTROLLER,
|
||||
"color_move": Z2MLight.XYCOLOR_FROM_CONTROLLER,
|
||||
"hue_move": None, # Play/pause button
|
||||
"recall_1": None, # Scene 1
|
||||
"recall_3": None, # Scene 2
|
||||
"recall_2": None, # Scene 3
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, Z2MLight
|
||||
from cx_core import LightController, Z2MLightController
|
||||
|
||||
|
||||
class ROB2000070LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on_1": Light.ON,
|
||||
"off_1": Light.OFF,
|
||||
"brightness_move_up_1": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_1": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_1": Light.RELEASE,
|
||||
"on_2": Light.ON,
|
||||
"off_2": Light.OFF,
|
||||
"brightness_move_up_2": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_2": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_2": Light.RELEASE,
|
||||
"on_3": Light.ON,
|
||||
"off_3": Light.OFF,
|
||||
"brightness_move_up_3": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_3": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_3": Light.RELEASE,
|
||||
"on_4": Light.ON,
|
||||
"off_4": Light.OFF,
|
||||
"brightness_move_up_4": Light.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_4": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_4": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class ROB2000070Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on_1": Z2MLight.ON,
|
||||
"off_1": Z2MLight.OFF,
|
||||
"brightness_move_up_1": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_1": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_1": Z2MLight.RELEASE,
|
||||
"on_2": Z2MLight.ON,
|
||||
"off_2": Z2MLight.OFF,
|
||||
"brightness_move_up_2": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_2": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_2": Z2MLight.RELEASE,
|
||||
"on_3": Z2MLight.ON,
|
||||
"off_3": Z2MLight.OFF,
|
||||
"brightness_move_up_3": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_3": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_3": Z2MLight.RELEASE,
|
||||
"on_4": Z2MLight.ON,
|
||||
"off_4": Z2MLight.OFF,
|
||||
"brightness_move_up_4": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"brightness_move_down_4": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"brightness_stop_4": Z2MLight.RELEASE,
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, Z2MLight
|
||||
from cx_core import LightController, Z2MLightController
|
||||
|
||||
|
||||
class E1EG7FLightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON,
|
||||
"on_long": Light.CLICK_COLOR_UP,
|
||||
"on_double": Light.ON_FULL_COLOR_TEMP,
|
||||
"up": Light.CLICK_BRIGHTNESS_UP,
|
||||
"up_long": Light.ON_FULL_BRIGHTNESS,
|
||||
"down": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"down_long": Light.ON_MIN_BRIGHTNESS,
|
||||
"off": Light.OFF,
|
||||
"off_long": Light.CLICK_COLOR_DOWN,
|
||||
"off_double": Light.ON_MIN_COLOR_TEMP,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON,
|
||||
"on_long": Light.CLICK_COLOR_UP,
|
||||
"on_double": Light.ON_FULL_COLOR_TEMP,
|
||||
"step_0_1_0": Light.CLICK_BRIGHTNESS_UP,
|
||||
"step_0_2_0": Light.ON_FULL_BRIGHTNESS,
|
||||
"step_1_1_0": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"step_1_2_0": Light.ON_MIN_BRIGHTNESS,
|
||||
"off": Light.OFF,
|
||||
"off_long": Light.CLICK_COLOR_DOWN,
|
||||
"off_double": Light.ON_MIN_COLOR_TEMP,
|
||||
}
|
||||
|
||||
|
||||
class E1EG7FZ2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Z2MLight.ON,
|
||||
"on_long": Z2MLight.CLICK_COLOR_TEMP_UP,
|
||||
"on_double": Z2MLight.ON_FULL_COLOR_TEMP,
|
||||
"up": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"up_long": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"down": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"down_long": Z2MLight.ON_MIN_BRIGHTNESS,
|
||||
"off": Z2MLight.OFF,
|
||||
"off_long": Z2MLight.CLICK_COLOR_TEMP_DOWN,
|
||||
"off_double": Z2MLight.ON_MIN_COLOR_TEMP,
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light
|
||||
from cx_core import LightController
|
||||
|
||||
|
||||
class ShellyI3LightController(LightController):
|
||||
def get_shellyforhass_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": Light.CLICK_BRIGHTNESS_UP,
|
||||
"long": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"double": Light.ON_FULL_BRIGHTNESS,
|
||||
}
|
||||
|
||||
|
||||
class ShellyPlusI4LightController(LightController):
|
||||
def get_shelly_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single_push_1": Light.ON,
|
||||
"long_push_1": Light.HOLD_COLOR_UP,
|
||||
"btn_up_1": Light.RELEASE,
|
||||
"double_push_1": Light.ON_FULL_COLOR_TEMP,
|
||||
"single_push_2": Light.OFF,
|
||||
"long_push_2": Light.HOLD_COLOR_DOWN,
|
||||
"btn_up_2": Light.RELEASE,
|
||||
"double_push_2": Light.ON_MIN_COLOR_TEMP,
|
||||
"single_push_3": Light.CLICK_BRIGHTNESS_UP,
|
||||
"long_push_3": Light.HOLD_BRIGHTNESS_UP,
|
||||
"btn_up_3": Light.RELEASE,
|
||||
"double_push_3": Light.ON_FULL_BRIGHTNESS,
|
||||
"single_push_4": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"long_push_4": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"btn_up_4": Light.RELEASE,
|
||||
"double_push_4": Light.ON_MIN_BRIGHTNESS,
|
||||
}
|
||||
|
||||
|
||||
class Shelly25LightController(LightController):
|
||||
def get_shelly_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single_push_1": Light.ON,
|
||||
"long_push_1": Light.HOLD_BRIGHTNESS_UP,
|
||||
"btn_up_1": Light.RELEASE,
|
||||
"double_push_1": Light.ON_FULL_BRIGHTNESS,
|
||||
"single_push_2": Light.OFF,
|
||||
"long_push_2": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"btn_up_2": Light.RELEASE,
|
||||
"double_push_2": Light.ON_MIN_BRIGHTNESS,
|
||||
}
|
||||
|
||||
|
||||
class ShellyDimmer2LightController(LightController):
|
||||
def get_shelly_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single_1": Light.TOGGLE,
|
||||
"single_2": None,
|
||||
"long_1": Light.TOGGLE,
|
||||
"long_2": None,
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light
|
||||
from cx_core import LightController
|
||||
|
||||
|
||||
class SK5700002228949LightController(LightController):
|
||||
# This mapping works for: 5700002228949, 5700002228963, 5745000433087
|
||||
# The buttons are distributted like:
|
||||
# 1 3
|
||||
# 2 4
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.ON,
|
||||
1001: Light.HOLD_BRIGHTNESS_UP,
|
||||
1003: Light.RELEASE,
|
||||
3002: Light.OFF,
|
||||
3001: Light.HOLD_BRIGHTNESS_DOWN,
|
||||
3003: Light.RELEASE,
|
||||
2002: Light.CLICK_COLOR_UP,
|
||||
2001: Light.HOLD_COLOR_UP,
|
||||
2003: Light.RELEASE,
|
||||
4002: Light.CLICK_COLOR_DOWN,
|
||||
4001: Light.HOLD_COLOR_DOWN,
|
||||
4003: Light.RELEASE,
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, MediaPlayer
|
||||
from cx_core import LightController, MediaPlayerController
|
||||
|
||||
|
||||
class SmartThingsButtonLightController(LightController):
|
||||
"""
|
||||
This controller sends click, double click, and hold commands.
|
||||
No release command is sent.
|
||||
"""
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": Light.TOGGLE,
|
||||
"double": Light.ON_FULL_BRIGHTNESS,
|
||||
"hold": Light.SET_HALF_BRIGHTNESS,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: Light.TOGGLE,
|
||||
1004: Light.ON_FULL_BRIGHTNESS,
|
||||
1001: Light.SET_HALF_BRIGHTNESS,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_single_1_0_0_0": Light.TOGGLE,
|
||||
"button_double_2_0_0_0": Light.ON_FULL_BRIGHTNESS,
|
||||
"button_hold_3_0_0_0": Light.SET_HALF_BRIGHTNESS,
|
||||
}
|
||||
|
||||
|
||||
class SmartThingsButtonMediaPlayerController(MediaPlayerController):
|
||||
"""
|
||||
This controller sends click, double click, and hold commands.
|
||||
No release command is sent.
|
||||
"""
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single_click": MediaPlayer.PLAY_PAUSE,
|
||||
"double_click": MediaPlayer.NEXT_TRACK,
|
||||
"hold": MediaPlayer.PREVIOUS_TRACK,
|
||||
}
|
||||
|
||||
def get_deconz_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
1002: MediaPlayer.PLAY_PAUSE,
|
||||
1004: MediaPlayer.NEXT_TRACK,
|
||||
1001: MediaPlayer.PREVIOUS_TRACK,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_single_1_0_0_0": MediaPlayer.PLAY_PAUSE,
|
||||
"button_double_2_0_0_0": MediaPlayer.NEXT_TRACK,
|
||||
"button_hold_3_0_0_0": MediaPlayer.PREVIOUS_TRACK,
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light
|
||||
from cx_core import LightController
|
||||
from cx_core.integration import EventData
|
||||
|
||||
|
||||
class SNZB01LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"single": Light.TOGGLE, # single click
|
||||
"double": Light.ON_FULL_BRIGHTNESS, # double click
|
||||
"long": Light.ON_MIN_BRIGHTNESS, # hold
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"toggle": Light.TOGGLE, # single click
|
||||
"on": Light.ON_FULL_BRIGHTNESS, # double click
|
||||
"off": Light.ON_MIN_BRIGHTNESS, # hold
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
@@ -1,103 +0,0 @@
|
||||
from cx_const import Cover, DefaultActionsMapping, Light, Switch, Z2MLight
|
||||
from cx_core import (
|
||||
CoverController,
|
||||
LightController,
|
||||
SwitchController,
|
||||
Z2MLightController,
|
||||
)
|
||||
|
||||
|
||||
class TasmotaButtonZ2MLightController(Z2MLightController):
|
||||
def get_tasmota_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"TOGGLE": Z2MLight.TOGGLE,
|
||||
"SINGLE": Z2MLight.TOGGLE,
|
||||
"DOUBLE": Z2MLight.ON_MIN_BRIGHTNESS,
|
||||
"TRIPLE": Z2MLight.SET_HALF_BRIGHTNESS,
|
||||
"QUAD": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"PENTA": None,
|
||||
"HOLD": Z2MLight.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"CLEAR": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class TasmotaSwitchZ2MLightController(Z2MLightController):
|
||||
def get_tasmota_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"TOGGLE": Z2MLight.TOGGLE,
|
||||
"ON": Z2MLight.ON,
|
||||
"OFF": Z2MLight.OFF,
|
||||
"HOLD": Z2MLight.SET_HALF_BRIGHTNESS,
|
||||
}
|
||||
|
||||
|
||||
class TasmotaButtonLightController(LightController):
|
||||
def get_tasmota_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"TOGGLE": Light.TOGGLE,
|
||||
"SINGLE": Light.TOGGLE,
|
||||
"DOUBLE": Light.ON_MIN_BRIGHTNESS,
|
||||
"TRIPLE": Light.SET_HALF_BRIGHTNESS,
|
||||
"QUAD": Light.ON_FULL_BRIGHTNESS,
|
||||
"PENTA": None,
|
||||
"HOLD": Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"CLEAR": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class TasmotaSwitchLightController(LightController):
|
||||
def get_tasmota_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"TOGGLE": Light.TOGGLE,
|
||||
"ON": Light.ON,
|
||||
"OFF": Light.OFF,
|
||||
"HOLD": Light.ON_MIN_MAX_BRIGHTNESS,
|
||||
}
|
||||
|
||||
|
||||
class TasmotaButtonSwitchController(SwitchController):
|
||||
def get_tasmota_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"TOGGLE": Light.TOGGLE,
|
||||
"SINGLE": Switch.TOGGLE,
|
||||
"DOUBLE": Switch.ON,
|
||||
"TRIPLE": Switch.OFF,
|
||||
"QUAD": None,
|
||||
"PENTA": None,
|
||||
"HOLD": None,
|
||||
"CLEAR": None,
|
||||
}
|
||||
|
||||
|
||||
class TasmotaSwitchSwitchController(SwitchController):
|
||||
def get_tasmota_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"TOGGLE": Switch.TOGGLE,
|
||||
"ON": Switch.ON,
|
||||
"OFF": Switch.OFF,
|
||||
"HOLD": None,
|
||||
}
|
||||
|
||||
|
||||
class TasmotaButtonCoverController(CoverController):
|
||||
def get_tasmota_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"TOGGLE": Cover.TOGGLE_OPEN,
|
||||
"SINGLE": Cover.TOGGLE_OPEN,
|
||||
"DOUBLE": Cover.CLOSE,
|
||||
"TRIPLE": None,
|
||||
"QUAD": None,
|
||||
"PENTA": None,
|
||||
"HOLD": Cover.CLOSE,
|
||||
"CLEAR": None,
|
||||
}
|
||||
|
||||
|
||||
class TasmotaSwitchCoverController(CoverController):
|
||||
def get_tasmota_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"TOGGLE": Cover.TOGGLE_OPEN,
|
||||
"ON": Cover.OPEN,
|
||||
"OFF": Cover.CLOSE,
|
||||
"HOLD": Cover.TOGGLE_CLOSE,
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, MediaPlayer
|
||||
from cx_core import LightController, MediaPlayerController
|
||||
from cx_core.integration import EventData
|
||||
|
||||
|
||||
class TerncyPP01LightController(LightController):
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_single": Light.TOGGLE,
|
||||
"button_double": Light.ON_FULL_BRIGHTNESS,
|
||||
"button_triple": Light.ON_MIN_BRIGHTNESS,
|
||||
"button_quadruple": Light.SET_HALF_BRIGHTNESS,
|
||||
"button_quintuple": Light.SET_HALF_COLOR_TEMP,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class TerncySD01LightController(LightController):
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_single": Light.TOGGLE,
|
||||
"button_double": Light.ON_FULL_BRIGHTNESS,
|
||||
"button_triple": Light.ON_MIN_BRIGHTNESS,
|
||||
"button_quadruple": Light.SET_HALF_BRIGHTNESS,
|
||||
"button_quintuple": Light.SET_HALF_COLOR_TEMP,
|
||||
"rotate_left": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"rotate_right": Light.CLICK_BRIGHTNESS_UP,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
|
||||
|
||||
class TerncySD01MediaPlayerController(MediaPlayerController):
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"button_single": MediaPlayer.PLAY_PAUSE,
|
||||
"button_double": MediaPlayer.MUTE,
|
||||
"button_triple": MediaPlayer.NEXT_TRACK,
|
||||
"button_quadruple": MediaPlayer.PREVIOUS_TRACK,
|
||||
"button_quintuple": MediaPlayer.NEXT_SOURCE,
|
||||
"rotate_left": MediaPlayer.CLICK_VOLUME_DOWN,
|
||||
"rotate_right": MediaPlayer.CLICK_VOLUME_UP,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
return command
|
||||
@@ -1,39 +0,0 @@
|
||||
from cx_const import DefaultActionsMapping, Light, MediaPlayer, Z2MLight
|
||||
from cx_core import LightController, MediaPlayerController, Z2MLightController
|
||||
|
||||
|
||||
class ZYCT202LightController(LightController):
|
||||
"""
|
||||
This controller does not send hold action for on/off
|
||||
"""
|
||||
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.ON,
|
||||
"up-press": Light.HOLD_BRIGHTNESS_UP,
|
||||
"down-press": Light.HOLD_BRIGHTNESS_DOWN,
|
||||
"off": Light.OFF,
|
||||
"stop": Light.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class ZYCT202Z2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Z2MLight.ON,
|
||||
"up-press": Z2MLight.HOLD_BRIGHTNESS_UP,
|
||||
"down-press": Z2MLight.HOLD_BRIGHTNESS_DOWN,
|
||||
"off": Z2MLight.OFF,
|
||||
"stop": Z2MLight.RELEASE,
|
||||
}
|
||||
|
||||
|
||||
class ZYCT202MediaPlayerController(MediaPlayerController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": MediaPlayer.PLAY_PAUSE,
|
||||
"up-press": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"down-press": MediaPlayer.HOLD_VOLUME_DOWN,
|
||||
"off": MediaPlayer.PLAY_PAUSE,
|
||||
"stop": MediaPlayer.RELEASE,
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
from cx_const import Cover, DefaultActionsMapping, Light, MediaPlayer, Z2MLight
|
||||
from cx_core import (
|
||||
CoverController,
|
||||
LightController,
|
||||
MediaPlayerController,
|
||||
Z2MLightController,
|
||||
)
|
||||
from cx_core.integration import EventData
|
||||
|
||||
|
||||
class TS0044LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1_single": Light.TOGGLE,
|
||||
"1_double": Light.CLICK_BRIGHTNESS_UP,
|
||||
"1_hold": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"2_single": Light.TOGGLE,
|
||||
"2_double": Light.CLICK_BRIGHTNESS_UP,
|
||||
"2_hold": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"3_single": Light.TOGGLE,
|
||||
"3_double": Light.CLICK_BRIGHTNESS_UP,
|
||||
"3_hold": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"4_single": Light.TOGGLE,
|
||||
"4_double": Light.CLICK_BRIGHTNESS_UP,
|
||||
"4_hold": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
}
|
||||
|
||||
|
||||
class TS0044FLightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"on": Light.TOGGLE,
|
||||
"off": Light.TOGGLE,
|
||||
"brightness_step_up": Light.CLICK_BRIGHTNESS_UP,
|
||||
"brightness_step_down": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
}
|
||||
|
||||
|
||||
class TS0043LightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1_single": Light.TOGGLE,
|
||||
"1_double": Light.CLICK_BRIGHTNESS_UP,
|
||||
"1_hold": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"2_single": Light.TOGGLE,
|
||||
"2_double": Light.CLICK_BRIGHTNESS_UP,
|
||||
"2_hold": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"3_single": Light.TOGGLE,
|
||||
"3_double": Light.CLICK_BRIGHTNESS_UP,
|
||||
"3_hold": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1_remote_button_short_press": Light.TOGGLE,
|
||||
"1_remote_button_double_press": Light.CLICK_BRIGHTNESS_UP,
|
||||
"1_remote_button_long_press": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"2_remote_button_short_press": Light.TOGGLE,
|
||||
"2_remote_button_double_press": Light.CLICK_BRIGHTNESS_UP,
|
||||
"2_remote_button_long_press": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"3_remote_button_short_press": Light.TOGGLE,
|
||||
"3_remote_button_double_press": Light.CLICK_BRIGHTNESS_UP,
|
||||
"3_remote_button_long_press": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
args: str = ""
|
||||
# Command is {endpoint_id}_{command}
|
||||
if len(data["args"]) > 0:
|
||||
if isinstance(data["args"], list):
|
||||
args = "_" + "_".join([str(d) for d in data["args"]])
|
||||
else:
|
||||
args = args + f"{data['args']}"
|
||||
command: str = f"{data['endpoint_id']}_{data['command']}{args}"
|
||||
return command
|
||||
|
||||
|
||||
class TS0043CoverController(CoverController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1_single": Cover.OPEN,
|
||||
"2_single": Cover.STOP,
|
||||
"3_single": Cover.CLOSE,
|
||||
}
|
||||
|
||||
|
||||
class TuYaERS10TZBVKAALightController(LightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
# Command mode
|
||||
"brightness_step_up": Light.CLICK_BRIGHTNESS_UP,
|
||||
"brightness_step_down": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"toggle": Light.TOGGLE,
|
||||
"hue_move": Light.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"hue_stop": Light.RELEASE,
|
||||
"color_temperature_step_up": Light.CLICK_COLOR_UP,
|
||||
"color_temperature_step_down": Light.CLICK_COLOR_DOWN,
|
||||
# Event mode
|
||||
"rotate_left": Light.CLICK_BRIGHTNESS_DOWN,
|
||||
"rotate_right": Light.CLICK_BRIGHTNESS_UP,
|
||||
"single": Light.TOGGLE,
|
||||
"double": Light.ON_FULL_BRIGHTNESS,
|
||||
"hold": Light.ON_MIN_BRIGHTNESS,
|
||||
}
|
||||
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"toggle": Light.TOGGLE,
|
||||
"step_brightness": Light.BRIGHTNESS_FROM_CONTROLLER_STEP,
|
||||
"step_color_temp": Light.COLORTEMP_FROM_CONTROLLER_STEP,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
command: str = data["command"]
|
||||
if command == "step":
|
||||
return "step_brightness"
|
||||
return command
|
||||
|
||||
|
||||
class TuYaERS10TZBVKAAZ2MLightController(Z2MLightController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
# Command mode
|
||||
"brightness_step_up": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"brightness_step_down": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"toggle": Z2MLight.TOGGLE,
|
||||
"hue_move": Z2MLight.HOLD_BRIGHTNESS_TOGGLE,
|
||||
"hue_stop": Z2MLight.RELEASE,
|
||||
"color_temperature_step_up": Z2MLight.CLICK_COLOR_TEMP_UP,
|
||||
"color_temperature_step_down": Z2MLight.CLICK_COLOR_TEMP_DOWN,
|
||||
# Event mode
|
||||
"rotate_left": Z2MLight.CLICK_BRIGHTNESS_DOWN,
|
||||
"rotate_right": Z2MLight.CLICK_BRIGHTNESS_UP,
|
||||
"single": Z2MLight.TOGGLE,
|
||||
"double": Z2MLight.ON_FULL_BRIGHTNESS,
|
||||
"hold": Z2MLight.ON_MIN_BRIGHTNESS,
|
||||
}
|
||||
|
||||
|
||||
class TuYaERS10TZBVKAAMediaPlayerController(MediaPlayerController):
|
||||
def get_z2m_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
# Command mode
|
||||
"brightness_step_up": MediaPlayer.CLICK_VOLUME_UP,
|
||||
"brightness_step_down": MediaPlayer.CLICK_VOLUME_DOWN,
|
||||
"toggle": MediaPlayer.PLAY_PAUSE,
|
||||
"hue_move": MediaPlayer.HOLD_VOLUME_UP,
|
||||
"hue_stop": Z2MLight.RELEASE,
|
||||
"color_temperature_step_up": MediaPlayer.NEXT_TRACK,
|
||||
"color_temperature_step_down": MediaPlayer.PREVIOUS_TRACK,
|
||||
# Event mode
|
||||
"rotate_left": MediaPlayer.CLICK_VOLUME_DOWN,
|
||||
"rotate_right": MediaPlayer.CLICK_VOLUME_UP,
|
||||
"single": MediaPlayer.PLAY_PAUSE,
|
||||
"double": MediaPlayer.NEXT_TRACK,
|
||||
"hold": MediaPlayer.PREVIOUS_TRACK,
|
||||
}
|
||||
|
||||
|
||||
class TS0042LightController(LightController):
|
||||
def get_zha_actions_mapping(self) -> DefaultActionsMapping:
|
||||
return {
|
||||
"1_remote_button_short_press": Light.ON,
|
||||
"1_remote_button_double_press": Light.CLICK_BRIGHTNESS_UP,
|
||||
"1_remote_button_long_press": Light.ON_FULL_BRIGHTNESS,
|
||||
"2_remote_button_short_press": Light.OFF,
|
||||
"2_remote_button_double_press": Light.ON_MIN_BRIGHTNESS,
|
||||
}
|
||||
|
||||
def get_zha_action(self, data: EventData) -> str:
|
||||
args: str = ""
|
||||
# Command is {endpoint_id}_{command}
|
||||
if len(data["args"]) > 0:
|
||||
if isinstance(data["args"], list):
|
||||
args = "_" + "_".join([str(d) for d in data["args"]])
|
||||
else:
|
||||
args = args + f"{data['args']}"
|
||||
command: str = f"{data['endpoint_id']}_{data['command']}{args}"
|
||||
return command
|
||||
@@ -1,33 +0,0 @@
|
||||
import importlib
|
||||
import os
|
||||
import pkgutil
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _import_modules(file_dir: str, package: str) -> None:
|
||||
pkg_dir = os.path.dirname(file_dir)
|
||||
for _, name, ispkg in pkgutil.iter_modules([pkg_dir]):
|
||||
if ispkg:
|
||||
_import_modules(pkg_dir + "/" + name + "/__init__.py", package + "." + name)
|
||||
else:
|
||||
importlib.import_module("." + name, package)
|
||||
|
||||
|
||||
def _all_subclasses(cls: type[Any]) -> list[type[Any]]:
|
||||
return list(
|
||||
set(type.__subclasses__(cls)).union(
|
||||
[s for c in type.__subclasses__(cls) for s in _all_subclasses(c)]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def get_classes(file_: str, package_: str, class_: type[Any]) -> list[type[Any]]:
|
||||
_import_modules(file_, package_)
|
||||
subclasses = _all_subclasses(class_)
|
||||
subclasses = [cls_ for cls_ in subclasses if f"{package_}." in cls_.__module__]
|
||||
return subclasses
|
||||
|
||||
|
||||
def get_instances(file_: str, package_: str, class_: type[Any]) -> list[Any]:
|
||||
classes = get_classes(file_, package_, class_)
|
||||
return [cls_() for cls_ in classes]
|
||||
@@ -1 +0,0 @@
|
||||
__version__ = "v5.2.3"
|
||||
Reference in New Issue
Block a user