Skip to content

React to a Service Call

Intercept a Home Assistant service call and run custom logic in response. This recipe mirrors brightness and color temperature from a primary light to an accent light whenever someone turns the primary light on.

The code

from pydantic_settings import SettingsConfigDict

from hassette import App, AppConfig, P
from hassette.events import CallServiceEvent


class LightGroupConfig(AppConfig):
    model_config = SettingsConfigDict(env_prefix="light_group_")

    primary_light: str = "light.living_room_main"
    accent_light: str = "light.living_room_accent"


class LightGroupApp(App[LightGroupConfig]):
    """Mirror accent light whenever the primary light is turned on."""

    async def on_initialize(self) -> None:
        await self.bus.on_call_service(
            domain="light",
            service="turn_on",
            where=P.ServiceDataWhere({"entity_id": self.app_config.primary_light}),
            handler=self.on_primary_turned_on,
            name="primary_light_on",
        )

    async def on_primary_turned_on(self, event: CallServiceEvent) -> None:
        service_data = event.payload.data.service_data
        brightness = service_data.get("brightness")
        color_temp = service_data.get("color_temp")

        self.logger.info(
            "Primary light turned on (brightness=%s, color_temp=%s) — syncing accent",
            brightness,
            color_temp,
        )

        call_data: dict[str, object] = {"entity_id": self.app_config.accent_light}
        if brightness is not None:
            call_data["brightness"] = brightness
        if color_temp is not None:
            call_data["color_temp"] = color_temp

        await self.api.call_service("light", "turn_on", service_data=call_data)

How it works

  • on_call_service(domain="light", service="turn_on", ...) subscribes only to light.turn_on calls — no other service types reach the handler.
  • P.ServiceDataWhere({"entity_id": ...}) narrows the subscription further, so the handler only fires when the call targets the configured primary light.
  • The handler receives a CallServiceEvent. event.payload.data.service_data is the dict of arguments the caller passed — brightness, color temperature, transitions, and so on.
  • The handler forwards whichever parameters were present to light.turn_on on the accent light, leaving out keys that were not set in the original call.
  • Config fields (primary_light, accent_light) let you change entity IDs via environment variables (LIGHT_GROUP_PRIMARY_LIGHT, LIGHT_GROUP_ACCENT_LIGHT) without touching code.

Variations

Watch any entity in a group — replace the exact entity ID in ServiceDataWhere with a glob pattern:

where=P.ServiceDataWhere({"entity_id": "light.living_room_*"})

React to turn-off too — add a second subscription for service="turn_off" pointing to its own handler, and call light.turn_off on the accent light there.

See Also