Skip to content

Commit

Permalink
respect mqtt feedback for lights
Browse files Browse the repository at this point in the history
  • Loading branch information
siku2 committed Jul 5, 2024
1 parent f886c33 commit efaae2e
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 28 deletions.
9 changes: 2 additions & 7 deletions custom_components/dingz/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ class Input(
def __init__(self, shared: Shared, *, index: int) -> None:
super().__init__(shared)
self.__index = index
self.__is_on: bool | None = None

self._attr_unique_id = f"{self.coordinator.shared.mac_addr}-input-{index}"
self._attr_device_info = self.coordinator.shared.device_info
Expand Down Expand Up @@ -99,17 +98,13 @@ def handle_notification(self, notification: InternalNotification) -> None:
and notification.index == self.__index
):
return
self.__is_on = notification.on
self._attr_is_on = notification.on
self.async_write_ha_state()

@callback
def handle_state_update(self) -> None:
with contextlib.suppress(LookupError):
self.__is_on = self.coordinator.data["sensors"]["input_state"]

@property
def is_on(self) -> bool | None:
return self.__is_on
self._attr_is_on = self.coordinator.data["sensors"]["input_state"]


class Motion(CoordinatedNotificationStateEntity, BinarySensorEntity):
Expand Down
56 changes: 38 additions & 18 deletions custom_components/dingz/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,23 @@
LightEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from . import api
from .const import DOMAIN
from .helpers import DelayedCoordinatorRefreshMixin, UserAssignedNameMixin
from .shared import Shared, StateCoordinator
from .helpers import (
CoordinatedNotificationStateEntity,
DelayedCoordinatorRefreshMixin,
UserAssignedNameMixin,
)
from .shared import (
InternalNotification,
LightStateNotification,
Shared,
StateCoordinator,
)


async def async_setup_entry(
Expand All @@ -30,7 +39,7 @@ async def async_setup_entry(

for index, dingz_output in enumerate(shared.config.data.outputs):
if dingz_output.get("active", False) and dingz_output.get("type") == "light":
entities.append(Dimmer(shared.state, index))
entities.append(Dimmer(shared, index))

async_add_entities(entities)

Expand Down Expand Up @@ -124,19 +133,19 @@ async def async_turn_off(self, **kwargs: Any) -> None:


class Dimmer(
CoordinatorEntity[StateCoordinator],
CoordinatedNotificationStateEntity,
LightEntity,
UserAssignedNameMixin,
DelayedCoordinatorRefreshMixin,
):
_attr_supported_features = LightEntityFeature.TRANSITION

def __init__(self, coordinator: StateCoordinator, index: int) -> None:
super().__init__(coordinator)
def __init__(self, shared: Shared, index: int) -> None:
super().__init__(shared)
self.__index = index

self._attr_unique_id = f"{self.coordinator.shared.mac_addr}-dimmer-{index}"
self._attr_device_info = coordinator.shared.device_info
self._attr_device_info = shared.device_info
self._attr_translation_key = "dimmer"

@property
Expand Down Expand Up @@ -176,16 +185,27 @@ def color_mode(self) -> ColorMode | str | None:
def supported_color_modes(self) -> set[ColorMode] | set[str] | None:
return {ColorMode.BRIGHTNESS} if self.dingz_dimmable else {ColorMode.ONOFF}

@property
def is_on(self) -> bool | None:
return self.dingz_dimmer_state.get("on")

@property
def brightness(self) -> int | None:
output = self.dingz_dimmer_state.get("output")
if output is None:
return None
return 255 * output // 100
@callback
def handle_notification(self, notification: InternalNotification) -> None:
if not (
isinstance(notification, LightStateNotification)
and notification.index == self.__index
):
return
match notification.turn:
case "on":
self._attr_is_on = True
case "off":
self._attr_is_on = False

self._attr_brightness = 255 * notification.brightness // 100
self.async_write_ha_state()

@callback
def handle_state_update(self) -> None:
self._attr_is_on = self.dingz_dimmer_state.get("on")
if (output := self.dingz_dimmer_state.get("output")) is not None:
self._attr_brightness = 255 * output // 100

async def async_turn_on(self, **kwargs: Any) -> None:
try:
Expand Down
49 changes: 46 additions & 3 deletions custom_components/dingz/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ async def async_config_entry_first_refresh(self) -> None:
"topic": f"dingz/{dingz_id}/+/sensor/+",
"msg_callback": self._handle_mqtt_sensor,
},
"light": {
"topic": f"dingz/{dingz_id}/+/state/light/+",
"msg_callback": self._handle_mqtt_light,
},
},
)
await async_subscribe_topics(self.hass, self._sub_state)
Expand Down Expand Up @@ -185,6 +189,30 @@ async def _handle_mqtt_input(self, msg: mqtt.ReceiveMessage) -> None:
InputStateNotification(index=index, on=msg.payload == "1")
)

async def _handle_mqtt_light(self, msg: mqtt.ReceiveMessage) -> None:
(_, _, raw) = msg.topic.rpartition("/")
index = int(raw)

try:
payload: dict[str, Any] | Any = json.loads(msg.payload)
if not isinstance(payload, dict):
raise TypeError()
except (json.JSONDecodeError, TypeError):
_LOGGER.error(
"ignoring broken light notification (topic = %s): %s",
msg.topic,
msg.payload,
)
return
self._notifier.dispatch(
LightStateNotification(
index=index,
turn=payload["turn"],
brightness=payload["brightness"],
exception=payload["exception"],
)
)


class StateCoordinator(DataUpdateCoordinator[api.State]):
shared: Shared
Expand Down Expand Up @@ -248,9 +276,16 @@ class PirNotification(InternalNotification):
@dataclasses.dataclass(slots=True, kw_only=True)
class ButtonNotification(InternalNotification):
index: int
event_type: Literal["p"] | Literal["r"] | Literal["h"] | Literal["m1"] | Literal[
"m2"
] | Literal["m3"] | Literal["m4"] | Literal["m5"]
event_type: (
Literal["p"]
| Literal["r"]
| Literal["h"]
| Literal["m1"]
| Literal["m2"]
| Literal["m3"]
| Literal["m4"]
| Literal["m5"]
)


class MotorMotion(IntEnum):
Expand Down Expand Up @@ -303,6 +338,14 @@ class InputStateNotification(InternalNotification):
on: bool


@dataclasses.dataclass(slots=True, kw_only=True)
class LightStateNotification(InternalNotification):
index: int
turn: Literal["on"] | Literal["off"]
brightness: int
exception: int


_NotificationCallbackT = Callable[[InternalNotification], None]


Expand Down

0 comments on commit efaae2e

Please sign in to comment.