Example #1
0
    async def async_turn_on(self, **kwargs):
        """Turn the entity on."""
        supported_color_modes = self._supported_color_modes

        attributes = {}

        if ATTR_HS_COLOR in kwargs and COLOR_MODE_HS in supported_color_modes:
            hs_color = kwargs[ATTR_HS_COLOR]
            attributes["color_hs"] = [hs_color[0], hs_color[1]]

        if ATTR_WHITE in kwargs and COLOR_MODE_WHITE in supported_color_modes:
            attributes["white_value"] = scale_brightness(kwargs[ATTR_WHITE])

        if ATTR_TRANSITION in kwargs:
            attributes["transition"] = kwargs[ATTR_TRANSITION]

        if ATTR_BRIGHTNESS in kwargs and brightness_supported(
                supported_color_modes):
            attributes["brightness"] = scale_brightness(
                kwargs[ATTR_BRIGHTNESS])

        if ATTR_COLOR_TEMP in kwargs and COLOR_MODE_COLOR_TEMP in supported_color_modes:
            attributes["color_temp"] = int(kwargs[ATTR_COLOR_TEMP])

        if ATTR_EFFECT in kwargs:
            attributes["effect"] = kwargs[ATTR_EFFECT]

        self._tasmota_entity.set_state(True, attributes)
Example #2
0
    def __init__(self, *args):
        """Initialize a new Light accessory object."""
        super().__init__(*args, category=CATEGORY_LIGHTBULB)

        self.chars = []
        state = self.hass.states.get(self.entity_id)

        self._features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
        self._color_modes = state.attributes.get(ATTR_SUPPORTED_COLOR_MODES)

        if brightness_supported(self._color_modes):
            self.chars.append(CHAR_BRIGHTNESS)

        if color_supported(self._color_modes):
            self.chars.append(CHAR_HUE)
            self.chars.append(CHAR_SATURATION)
        elif color_temp_supported(self._color_modes):
            # ColorTemperature and Hue characteristic should not be
            # exposed both. Both states are tracked separately in HomeKit,
            # causing "source of truth" problems.
            self.chars.append(CHAR_COLOR_TEMPERATURE)

        serv_light = self.add_preload_service(SERV_LIGHTBULB, self.chars)

        self.char_on = serv_light.configure_char(CHAR_ON, value=0)

        if CHAR_BRIGHTNESS in self.chars:
            # Initial value is set to 100 because 0 is a special value (off). 100 is
            # an arbitrary non-zero value. It is updated immediately by async_update_state
            # to set to the correct initial value.
            self.char_brightness = serv_light.configure_char(CHAR_BRIGHTNESS,
                                                             value=100)

        if CHAR_COLOR_TEMPERATURE in self.chars:
            min_mireds = self.hass.states.get(self.entity_id).attributes.get(
                ATTR_MIN_MIREDS, 153)
            max_mireds = self.hass.states.get(self.entity_id).attributes.get(
                ATTR_MAX_MIREDS, 500)
            self.char_color_temperature = serv_light.configure_char(
                CHAR_COLOR_TEMPERATURE,
                value=min_mireds,
                properties={
                    PROP_MIN_VALUE: min_mireds,
                    PROP_MAX_VALUE: max_mireds
                },
            )

        if CHAR_HUE in self.chars:
            self.char_hue = serv_light.configure_char(CHAR_HUE, value=0)

        if CHAR_SATURATION in self.chars:
            self.char_saturation = serv_light.configure_char(CHAR_SATURATION,
                                                             value=75)

        self.async_update_state(state)

        serv_light.setter_callback = self._set_chars
Example #3
0
    def __init__(self, *args):
        """Initialize a new Light accessory object."""
        super().__init__(*args, category=CATEGORY_LIGHTBULB)

        self.chars = []
        self._event_timer = None
        self._pending_events = {}

        state = self.hass.states.get(self.entity_id)
        attributes = state.attributes
        self.color_modes = color_modes = (
            attributes.get(ATTR_SUPPORTED_COLOR_MODES) or [])
        self.color_supported = color_supported(color_modes)
        self.color_temp_supported = color_temp_supported(color_modes)
        self.brightness_supported = brightness_supported(color_modes)

        if self.brightness_supported:
            self.chars.append(CHAR_BRIGHTNESS)

        if self.color_supported:
            self.chars.extend([CHAR_HUE, CHAR_SATURATION])

        if self.color_temp_supported:
            self.chars.append(CHAR_COLOR_TEMPERATURE)

        serv_light = self.add_preload_service(SERV_LIGHTBULB, self.chars)
        self.char_on = serv_light.configure_char(CHAR_ON, value=0)

        if self.brightness_supported:
            # Initial value is set to 100 because 0 is a special value (off). 100 is
            # an arbitrary non-zero value. It is updated immediately by async_update_state
            # to set to the correct initial value.
            self.char_brightness = serv_light.configure_char(CHAR_BRIGHTNESS,
                                                             value=100)

        if self.color_temp_supported:
            min_mireds = math.floor(attributes.get(ATTR_MIN_MIREDS, 153))
            max_mireds = math.ceil(attributes.get(ATTR_MAX_MIREDS, 500))
            self.char_color_temp = serv_light.configure_char(
                CHAR_COLOR_TEMPERATURE,
                value=min_mireds,
                properties={
                    PROP_MIN_VALUE: min_mireds,
                    PROP_MAX_VALUE: max_mireds
                },
            )

        if self.color_supported:
            self.char_hue = serv_light.configure_char(CHAR_HUE, value=0)
            self.char_saturation = serv_light.configure_char(CHAR_SATURATION,
                                                             value=75)

        self.async_update_state(state)
        serv_light.setter_callback = self._set_chars
    def supported(self) -> bool:
        """Test if capability is supported."""
        features = self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)

        if self.state.domain == light.DOMAIN:
            if features & light.SUPPORT_BRIGHTNESS:
                return True

            if light.brightness_supported(self.state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES)):
                return True

        return False
Example #5
0
    def interfaces(self):
        """Yield the supported interfaces."""
        yield AlexaPowerController(self.entity)

        color_modes = self.entity.attributes.get(
            light.ATTR_SUPPORTED_COLOR_MODES)
        if light.brightness_supported(color_modes):
            yield AlexaBrightnessController(self.entity)
        if light.color_supported(color_modes):
            yield AlexaColorController(self.entity)
        if light.color_temp_supported(color_modes):
            yield AlexaColorTemperatureController(self.entity)

        yield AlexaEndpointHealth(self.hass, self.entity)
        yield Alexa(self.hass)
Example #6
0
File: light.py Project: jbouwh/core
 async def async_update(self) -> None:
     """Update entity attributes when the device status has changed."""
     # Brightness and transition
     if brightness_supported(self._attr_supported_color_modes):
         self._brightness = int(
             convert_scale(self._device.status.level, 100, 255, 0))
     # Color Temperature
     if ColorMode.COLOR_TEMP in self._attr_supported_color_modes:
         self._color_temp = color_util.color_temperature_kelvin_to_mired(
             self._device.status.color_temperature)
     # Color
     if ColorMode.HS in self._attr_supported_color_modes:
         self._hs_color = (
             convert_scale(self._device.status.hue, 100, 360),
             self._device.status.saturation,
         )
Example #7
0
    async def async_turn_on(self, **kwargs):
        """Turn the entity on."""
        supported_color_modes = self._supported_color_modes

        attributes = {}

        if ATTR_RGB_COLOR in kwargs and COLOR_MODE_RGB in supported_color_modes:
            rgb = kwargs[ATTR_RGB_COLOR]
            attributes["color"] = [rgb[0], rgb[1], rgb[2]]

        if ATTR_RGBW_COLOR in kwargs and COLOR_MODE_RGBW in supported_color_modes:
            rgbw = kwargs[ATTR_RGBW_COLOR]
            # Tasmota does not support direct RGBW control, the light must be set to
            # either white mode or color mode. Set the mode to white if white channel
            # is on, and to color otheruse
            if rgbw[3] == 0:
                attributes["color"] = [rgbw[0], rgbw[1], rgbw[2]]
            else:
                white_value_normalized = rgbw[3] / DEFAULT_BRIGHTNESS_MAX
                device_white_value = min(
                    round(white_value_normalized * TASMOTA_BRIGHTNESS_MAX),
                    TASMOTA_BRIGHTNESS_MAX,
                )
                attributes["white_value"] = device_white_value

        if ATTR_TRANSITION in kwargs:
            attributes["transition"] = kwargs[ATTR_TRANSITION]

        if ATTR_BRIGHTNESS in kwargs and brightness_supported(
                supported_color_modes):
            brightness_normalized = kwargs[
                ATTR_BRIGHTNESS] / DEFAULT_BRIGHTNESS_MAX
            device_brightness = min(
                round(brightness_normalized * TASMOTA_BRIGHTNESS_MAX),
                TASMOTA_BRIGHTNESS_MAX,
            )
            # Make sure the brightness is not rounded down to 0
            device_brightness = max(device_brightness, 1)
            attributes["brightness"] = device_brightness

        if ATTR_COLOR_TEMP in kwargs and COLOR_MODE_COLOR_TEMP in supported_color_modes:
            attributes["color_temp"] = int(kwargs[ATTR_COLOR_TEMP])

        if ATTR_EFFECT in kwargs:
            attributes["effect"] = kwargs[ATTR_EFFECT]

        self._tasmota_entity.set_state(True, attributes)
Example #8
0
    async def async_turn_on(self, **kwargs) -> None:
        """Turn on light."""
        if self.block.type == "relay":
            self.control_result = await self.set_state(turn="on")
            self.async_write_ha_state()
            return

        set_mode = None
        supported_color_modes = self._supported_color_modes
        params = {"turn": "on"}

        if ATTR_BRIGHTNESS in kwargs and brightness_supported(
                supported_color_modes):
            brightness_pct = int(100 * (kwargs[ATTR_BRIGHTNESS] + 1) / 255)
            if hasattr(self.block, "gain"):
                params["gain"] = brightness_pct
            if hasattr(self.block, "brightness"):
                params["brightness"] = brightness_pct

        if ATTR_COLOR_TEMP in kwargs and COLOR_MODE_COLOR_TEMP in supported_color_modes:
            color_temp = color_temperature_mired_to_kelvin(
                kwargs[ATTR_COLOR_TEMP])
            color_temp = min(self._max_kelvin, max(self._min_kelvin,
                                                   color_temp))
            # Color temperature change - used only in white mode, switch device mode to white
            set_mode = "white"
            params["temp"] = int(color_temp)

        if ATTR_RGB_COLOR in kwargs and COLOR_MODE_RGB in supported_color_modes:
            # Color channels change - used only in color mode, switch device mode to color
            set_mode = "color"
            (params["red"], params["green"],
             params["blue"]) = kwargs[ATTR_RGB_COLOR]

        if ATTR_RGBW_COLOR in kwargs and COLOR_MODE_RGBW in supported_color_modes:
            # Color channels change - used only in color mode, switch device mode to color
            set_mode = "color"
            (params["red"], params["green"], params["blue"],
             params["white"]) = kwargs[ATTR_RGBW_COLOR]

        if await self.set_light_mode(set_mode):
            self.control_result = await self.set_state(**params)

        self.async_write_ha_state()
Example #9
0
File: light.py Project: rikroe/core
    async def async_turn_off(self, **kwargs):
        """Turn the entity off."""
        duration = kwargs.get(light.ATTR_TRANSITION)
        supports_level = brightness_supported(self._attr_supported_color_modes)

        if duration and supports_level:
            result = await self._level_channel.move_to_level_with_on_off(
                0, duration * 10)
        else:
            result = await self._on_off_channel.off()
        self.debug("turned off: %s", result)
        if isinstance(result, Exception) or result[1] is not Status.SUCCESS:
            return
        self._state = False

        if duration and supports_level:
            # store current brightness so that the next turn_on uses it.
            self._off_brightness = self._brightness

        self.async_write_ha_state()
Example #10
0
    def _update(self):
        """Really update the state."""
        # Brightness handling
        if brightness_supported(self.supported_color_modes):
            self._attr_brightness = scaleto255(
                int(self.fibaro_device.properties.value))

        # Color handling
        if (color_supported(self.supported_color_modes)
                and "color" in self.fibaro_device.properties
                and "," in self.fibaro_device.properties.color):
            # Fibaro communicates the color as an 'R, G, B, W' string
            rgbw_s = self.fibaro_device.properties.color
            if rgbw_s == "0,0,0,0" and "lastColorSet" in self.fibaro_device.properties:
                rgbw_s = self.fibaro_device.properties.lastColorSet
            rgbw_list = [int(i) for i in rgbw_s.split(",")][:4]

            if self._attr_color_mode == ColorMode.RGB:
                self._attr_rgb_color = tuple(rgbw_list[:3])
            else:
                self._attr_rgbw_color = tuple(rgbw_list)
Example #11
0
File: light.py Project: jbouwh/core
    def update_dynamic_attributes(self):
        """Update dynamic attributes of the luminary."""
        self._is_on = self._luminary.on()
        self._available = self._luminary.reachable(
        ) and not self._luminary.deleted()
        if brightness_supported(self._attr_supported_color_modes):
            self._brightness = int(self._luminary.lum() * 2.55)

        if ColorMode.COLOR_TEMP in self._attr_supported_color_modes:
            self._color_temp = color_util.color_temperature_kelvin_to_mired(
                self._luminary.temp() or DEFAULT_KELVIN)

        if ColorMode.HS in self._attr_supported_color_modes:
            self._rgb_color = self._luminary.rgb()

        if len(self._attr_supported_color_modes > 1):
            # The light supports hs + color temp, determine which one it is
            if self._rgb_color == (0, 0, 0):
                self._attr_color_mode = ColorMode.COLOR_TEMP
            else:
                self._attr_color_mode = ColorMode.HS
Example #12
0
    async def async_turn_on(self, **kwargs: Any) -> None:
        """Turn on light."""
        if self.block.type == "relay":
            self.control_result = await self.set_state(turn="on")
            self.async_write_ha_state()
            return

        set_mode = None
        supported_color_modes = self._attr_supported_color_modes
        params: dict[str, Any] = {"turn": "on"}

        if ATTR_TRANSITION in kwargs:
            params["transition"] = min(int(kwargs[ATTR_TRANSITION] * 1000),
                                       MAX_TRANSITION_TIME)

        if ATTR_BRIGHTNESS in kwargs and brightness_supported(
                supported_color_modes):
            brightness_pct = int(100 * (kwargs[ATTR_BRIGHTNESS] + 1) / 255)
            if hasattr(self.block, "gain"):
                params["gain"] = brightness_pct
            if hasattr(self.block, "brightness"):
                params["brightness"] = brightness_pct

        if ATTR_COLOR_TEMP in kwargs and ColorMode.COLOR_TEMP in supported_color_modes:
            color_temp = color_temperature_mired_to_kelvin(
                kwargs[ATTR_COLOR_TEMP])
            color_temp = min(self._max_kelvin, max(self._min_kelvin,
                                                   color_temp))
            # Color temperature change - used only in white mode, switch device mode to white
            set_mode = "white"
            params["temp"] = int(color_temp)

        if ATTR_RGB_COLOR in kwargs and ColorMode.RGB in supported_color_modes:
            # Color channels change - used only in color mode, switch device mode to color
            set_mode = "color"
            (params["red"], params["green"],
             params["blue"]) = kwargs[ATTR_RGB_COLOR]

        if ATTR_RGBW_COLOR in kwargs and ColorMode.RGBW in supported_color_modes:
            # Color channels change - used only in color mode, switch device mode to color
            set_mode = "color"
            (params["red"], params["green"], params["blue"],
             params["white"]) = kwargs[ATTR_RGBW_COLOR]

        if ATTR_EFFECT in kwargs and ATTR_COLOR_TEMP not in kwargs:
            # Color effect change - used only in color mode, switch device mode to color
            set_mode = "color"
            if self.wrapper.model == "SHBLB-1":
                effect_dict = SHBLB_1_RGB_EFFECTS
            else:
                effect_dict = STANDARD_RGB_EFFECTS
            if kwargs[ATTR_EFFECT] in effect_dict.values():
                params["effect"] = [
                    k for k, v in effect_dict.items()
                    if v == kwargs[ATTR_EFFECT]
                ][0]
            else:
                LOGGER.error(
                    "Effect '%s' not supported by device %s",
                    kwargs[ATTR_EFFECT],
                    self.wrapper.model,
                )

        if (set_mode and set_mode != self.mode
                and self.wrapper.model in DUAL_MODE_LIGHT_MODELS):
            params["mode"] = set_mode

        self.control_result = await self.set_state(**params)
        self.async_write_ha_state()
Example #13
0
    async def put(self, request, username, entity_number):  # noqa: C901
        """Process a request to set the state of an individual light."""
        if not is_local(ip_address(request.remote)):
            return self.json_message("Only local IPs allowed", HTTP_UNAUTHORIZED)

        config = self.config
        hass = request.app["hass"]
        entity_id = config.number_to_entity_id(entity_number)

        if entity_id is None:
            _LOGGER.error("Unknown entity number: %s", entity_number)
            return self.json_message("Entity not found", HTTP_NOT_FOUND)

        entity = hass.states.get(entity_id)

        if entity is None:
            _LOGGER.error("Entity not found: %s", entity_id)
            return self.json_message("Entity not found", HTTP_NOT_FOUND)

        if not config.is_entity_exposed(entity):
            _LOGGER.error("Entity not exposed: %s", entity_id)
            return self.json_message("Entity not exposed", HTTP_UNAUTHORIZED)

        try:
            request_json = await request.json()
        except ValueError:
            _LOGGER.error("Received invalid json")
            return self.json_message("Invalid JSON", HTTP_BAD_REQUEST)

        # Get the entity's supported features
        entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
        if entity.domain == light.DOMAIN:
            color_modes = entity.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES, [])

        # Parse the request
        parsed = {
            STATE_ON: False,
            STATE_BRIGHTNESS: None,
            STATE_HUE: None,
            STATE_SATURATION: None,
            STATE_COLOR_TEMP: None,
            STATE_XY: None,
            STATE_TRANSITON: None,
        }

        if HUE_API_STATE_ON in request_json:
            if not isinstance(request_json[HUE_API_STATE_ON], bool):
                _LOGGER.error("Unable to parse data: %s", request_json)
                return self.json_message("Bad request", HTTP_BAD_REQUEST)
            parsed[STATE_ON] = request_json[HUE_API_STATE_ON]
        else:
            parsed[STATE_ON] = entity.state != STATE_OFF

        for (key, attr) in (
            (HUE_API_STATE_BRI, STATE_BRIGHTNESS),
            (HUE_API_STATE_HUE, STATE_HUE),
            (HUE_API_STATE_SAT, STATE_SATURATION),
            (HUE_API_STATE_CT, STATE_COLOR_TEMP),
            (HUE_API_STATE_TRANSITION, STATE_TRANSITON),
        ):
            if key in request_json:
                try:
                    parsed[attr] = int(request_json[key])
                except ValueError:
                    _LOGGER.error("Unable to parse data (2): %s", request_json)
                    return self.json_message("Bad request", HTTP_BAD_REQUEST)
        if HUE_API_STATE_XY in request_json:
            try:
                parsed[STATE_XY] = (
                    float(request_json[HUE_API_STATE_XY][0]),
                    float(request_json[HUE_API_STATE_XY][1]),
                )
            except ValueError:
                _LOGGER.error("Unable to parse data (2): %s", request_json)
                return self.json_message("Bad request", HTTP_BAD_REQUEST)

        if HUE_API_STATE_BRI in request_json:
            if entity.domain == light.DOMAIN:
                if light.brightness_supported(color_modes):
                    parsed[STATE_ON] = parsed[STATE_BRIGHTNESS] > 0
                else:
                    parsed[STATE_BRIGHTNESS] = None

            elif entity.domain == scene.DOMAIN:
                parsed[STATE_BRIGHTNESS] = None
                parsed[STATE_ON] = True

            elif entity.domain in [
                script.DOMAIN,
                media_player.DOMAIN,
                fan.DOMAIN,
                cover.DOMAIN,
                climate.DOMAIN,
                humidifier.DOMAIN,
            ]:
                # Convert 0-254 to 0-100
                level = (parsed[STATE_BRIGHTNESS] / HUE_API_STATE_BRI_MAX) * 100
                parsed[STATE_BRIGHTNESS] = round(level)
                parsed[STATE_ON] = True

        # Choose general HA domain
        domain = core.DOMAIN

        # Entity needs separate call to turn on
        turn_on_needed = False

        # Convert the resulting "on" status into the service we need to call
        service = SERVICE_TURN_ON if parsed[STATE_ON] else SERVICE_TURN_OFF

        # Construct what we need to send to the service
        data = {ATTR_ENTITY_ID: entity_id}

        # If the requested entity is a light, set the brightness, hue,
        # saturation and color temp
        if entity.domain == light.DOMAIN:
            if parsed[STATE_ON]:
                if (
                    light.brightness_supported(color_modes)
                    and parsed[STATE_BRIGHTNESS] is not None
                ):
                    data[ATTR_BRIGHTNESS] = hue_brightness_to_hass(
                        parsed[STATE_BRIGHTNESS]
                    )

                if light.color_supported(color_modes):
                    if any((parsed[STATE_HUE], parsed[STATE_SATURATION])):
                        if parsed[STATE_HUE] is not None:
                            hue = parsed[STATE_HUE]
                        else:
                            hue = 0

                        if parsed[STATE_SATURATION] is not None:
                            sat = parsed[STATE_SATURATION]
                        else:
                            sat = 0

                        # Convert hs values to hass hs values
                        hue = int((hue / HUE_API_STATE_HUE_MAX) * 360)
                        sat = int((sat / HUE_API_STATE_SAT_MAX) * 100)

                        data[ATTR_HS_COLOR] = (hue, sat)

                    if parsed[STATE_XY] is not None:
                        data[ATTR_XY_COLOR] = parsed[STATE_XY]

                if (
                    light.color_temp_supported(color_modes)
                    and parsed[STATE_COLOR_TEMP] is not None
                ):
                    data[ATTR_COLOR_TEMP] = parsed[STATE_COLOR_TEMP]

                if (
                    entity_features & SUPPORT_TRANSITION
                    and parsed[STATE_TRANSITON] is not None
                ):
                    data[ATTR_TRANSITION] = parsed[STATE_TRANSITON] / 10

        # If the requested entity is a script, add some variables
        elif entity.domain == script.DOMAIN:
            data["variables"] = {
                "requested_state": STATE_ON if parsed[STATE_ON] else STATE_OFF
            }

            if parsed[STATE_BRIGHTNESS] is not None:
                data["variables"]["requested_level"] = parsed[STATE_BRIGHTNESS]

        # If the requested entity is a climate, set the temperature
        elif entity.domain == climate.DOMAIN:
            # We don't support turning climate devices on or off,
            # only setting the temperature
            service = None

            if (
                entity_features & SUPPORT_TARGET_TEMPERATURE
                and parsed[STATE_BRIGHTNESS] is not None
            ):
                domain = entity.domain
                service = SERVICE_SET_TEMPERATURE
                data[ATTR_TEMPERATURE] = parsed[STATE_BRIGHTNESS]

        # If the requested entity is a humidifier, set the humidity
        elif entity.domain == humidifier.DOMAIN:
            if parsed[STATE_BRIGHTNESS] is not None:
                turn_on_needed = True
                domain = entity.domain
                service = SERVICE_SET_HUMIDITY
                data[ATTR_HUMIDITY] = parsed[STATE_BRIGHTNESS]

        # If the requested entity is a media player, convert to volume
        elif entity.domain == media_player.DOMAIN:
            if (
                entity_features & SUPPORT_VOLUME_SET
                and parsed[STATE_BRIGHTNESS] is not None
            ):
                turn_on_needed = True
                domain = entity.domain
                service = SERVICE_VOLUME_SET
                # Convert 0-100 to 0.0-1.0
                data[ATTR_MEDIA_VOLUME_LEVEL] = parsed[STATE_BRIGHTNESS] / 100.0

        # If the requested entity is a cover, convert to open_cover/close_cover
        elif entity.domain == cover.DOMAIN:
            domain = entity.domain
            if service == SERVICE_TURN_ON:
                service = SERVICE_OPEN_COVER
            else:
                service = SERVICE_CLOSE_COVER

            if (
                entity_features & SUPPORT_SET_POSITION
                and parsed[STATE_BRIGHTNESS] is not None
            ):
                domain = entity.domain
                service = SERVICE_SET_COVER_POSITION
                data[ATTR_POSITION] = parsed[STATE_BRIGHTNESS]

        # If the requested entity is a fan, convert to speed
        elif (
            entity.domain == fan.DOMAIN
            and entity_features & SUPPORT_SET_SPEED
            and parsed[STATE_BRIGHTNESS] is not None
        ):
            domain = entity.domain
            # Convert 0-100 to a fan speed
            brightness = parsed[STATE_BRIGHTNESS]
            if brightness == 0:
                data[ATTR_SPEED] = SPEED_OFF
            elif 0 < brightness <= 33.3:
                data[ATTR_SPEED] = SPEED_LOW
            elif 33.3 < brightness <= 66.6:
                data[ATTR_SPEED] = SPEED_MEDIUM
            elif 66.6 < brightness <= 100:
                data[ATTR_SPEED] = SPEED_HIGH

        # Map the off command to on
        if entity.domain in config.off_maps_to_on_domains:
            service = SERVICE_TURN_ON

        # Separate call to turn on needed
        if turn_on_needed:
            hass.async_create_task(
                hass.services.async_call(
                    core.DOMAIN,
                    SERVICE_TURN_ON,
                    {ATTR_ENTITY_ID: entity_id},
                    blocking=True,
                )
            )

        if service is not None:
            state_will_change = parsed[STATE_ON] != (entity.state != STATE_OFF)

            hass.async_create_task(
                hass.services.async_call(domain, service, data, blocking=True)
            )

            if state_will_change:
                # Wait for the state to change.
                await wait_for_state_change_or_timeout(
                    hass, entity_id, STATE_CACHED_TIMEOUT
                )

        # Create success responses for all received keys
        json_response = [
            create_hue_success_response(
                entity_number, HUE_API_STATE_ON, parsed[STATE_ON]
            )
        ]

        for (key, val) in (
            (STATE_BRIGHTNESS, HUE_API_STATE_BRI),
            (STATE_HUE, HUE_API_STATE_HUE),
            (STATE_SATURATION, HUE_API_STATE_SAT),
            (STATE_COLOR_TEMP, HUE_API_STATE_CT),
            (STATE_XY, HUE_API_STATE_XY),
            (STATE_TRANSITON, HUE_API_STATE_TRANSITION),
        ):
            if parsed[key] is not None:
                json_response.append(
                    create_hue_success_response(entity_number, val, parsed[key])
                )

        if entity.domain in config.off_maps_to_on_domains:
            # Caching is required because things like scripts and scenes won't
            # report as "off" to Alexa if an "off" command is received, because
            # they'll map to "on". Thus, instead of reporting its actual
            # status, we report what Alexa will want to see, which is the same
            # as the actual requested command.
            config.cached_states[entity_id] = [parsed, None]
        else:
            config.cached_states[entity_id] = [parsed, time.time()]

        return self.json(json_response)
Example #14
0
def state_to_json(config: Config, state: State) -> dict[str, Any]:
    """Convert an entity to its Hue bridge JSON representation."""
    entity_features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
    color_modes = state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES, [])
    unique_id = _entity_unique_id(state.entity_id)
    state_dict = get_entity_state_dict(config, state)

    json_state: dict[str, str | bool | int] = {
        HUE_API_STATE_ON: state_dict[STATE_ON],
        "reachable": state.state != STATE_UNAVAILABLE,
        "mode": "homeautomation",
    }
    retval: dict[str, str | dict[str, str | bool | int]] = {
        "state": json_state,
        "name": config.get_entity_name(state),
        "uniqueid": unique_id,
        "manufacturername": "Home Assistant",
        "swversion": "123",
    }

    if light.color_supported(color_modes) and light.color_temp_supported(
            color_modes):
        # Extended Color light (Zigbee Device ID: 0x0210)
        # Same as Color light, but which supports additional setting of color temperature
        retval["type"] = "Extended color light"
        retval["modelid"] = "HASS231"
        json_state.update({
            HUE_API_STATE_BRI: state_dict[STATE_BRIGHTNESS],
            HUE_API_STATE_HUE: state_dict[STATE_HUE],
            HUE_API_STATE_SAT: state_dict[STATE_SATURATION],
            HUE_API_STATE_CT: state_dict[STATE_COLOR_TEMP],
            HUE_API_STATE_EFFECT: "none",
        })
        if state_dict[STATE_HUE] > 0 or state_dict[STATE_SATURATION] > 0:
            json_state[HUE_API_STATE_COLORMODE] = "hs"
        else:
            json_state[HUE_API_STATE_COLORMODE] = "ct"
    elif light.color_supported(color_modes):
        # Color light (Zigbee Device ID: 0x0200)
        # Supports on/off, dimming and color control (hue/saturation, enhanced hue, color loop and XY)
        retval["type"] = "Color light"
        retval["modelid"] = "HASS213"
        json_state.update({
            HUE_API_STATE_BRI: state_dict[STATE_BRIGHTNESS],
            HUE_API_STATE_COLORMODE: "hs",
            HUE_API_STATE_HUE: state_dict[STATE_HUE],
            HUE_API_STATE_SAT: state_dict[STATE_SATURATION],
            HUE_API_STATE_EFFECT: "none",
        })
    elif light.color_temp_supported(color_modes):
        # Color temperature light (Zigbee Device ID: 0x0220)
        # Supports groups, scenes, on/off, dimming, and setting of a color temperature
        retval["type"] = "Color temperature light"
        retval["modelid"] = "HASS312"
        json_state.update({
            HUE_API_STATE_COLORMODE: "ct",
            HUE_API_STATE_CT: state_dict[STATE_COLOR_TEMP],
            HUE_API_STATE_BRI: state_dict[STATE_BRIGHTNESS],
        })
    elif entity_features & (CoverEntityFeature.SET_POSITION
                            | FanEntityFeature.SET_SPEED
                            | MediaPlayerEntityFeature.VOLUME_SET
                            | ClimateEntityFeature.TARGET_TEMPERATURE
                            ) or light.brightness_supported(color_modes):
        # Dimmable light (Zigbee Device ID: 0x0100)
        # Supports groups, scenes, on/off and dimming
        retval["type"] = "Dimmable light"
        retval["modelid"] = "HASS123"
        json_state.update({HUE_API_STATE_BRI: state_dict[STATE_BRIGHTNESS]})
    elif not config.lights_all_dimmable:
        # On/Off light (ZigBee Device ID: 0x0000)
        # Supports groups, scenes and on/off control
        retval["type"] = "On/Off light"
        retval["productname"] = "On/Off light"
        retval["modelid"] = "HASS321"
    else:
        # Dimmable light (Zigbee Device ID: 0x0100)
        # Supports groups, scenes, on/off and dimming
        # Reports fixed brightness for compatibility with Alexa.
        retval["type"] = "Dimmable light"
        retval["modelid"] = "HASS123"
        json_state.update({HUE_API_STATE_BRI: HUE_API_STATE_BRI_MAX})

    return retval
Example #15
0
    def __init__(self, *args):
        """Initialize a new Light accessory object."""
        super().__init__(*args, category=CATEGORY_LIGHTBULB)

        self.chars_primary = []
        self.chars_secondary = []

        state = self.hass.states.get(self.entity_id)
        attributes = state.attributes
        color_modes = attributes.get(ATTR_SUPPORTED_COLOR_MODES)
        self.is_color_supported = color_supported(color_modes)
        self.is_color_temp_supported = color_temp_supported(color_modes)
        self.color_and_temp_supported = (self.is_color_supported
                                         and self.is_color_temp_supported)
        self.is_brightness_supported = brightness_supported(color_modes)

        if self.is_brightness_supported:
            self.chars_primary.append(CHAR_BRIGHTNESS)

        if self.is_color_supported:
            self.chars_primary.append(CHAR_HUE)
            self.chars_primary.append(CHAR_SATURATION)

        if self.is_color_temp_supported:
            if self.color_and_temp_supported:
                self.chars_primary.append(CHAR_NAME)
                self.chars_secondary.append(CHAR_NAME)
                self.chars_secondary.append(CHAR_COLOR_TEMPERATURE)
                if self.is_brightness_supported:
                    self.chars_secondary.append(CHAR_BRIGHTNESS)
            else:
                self.chars_primary.append(CHAR_COLOR_TEMPERATURE)

        serv_light_primary = self.add_preload_service(SERV_LIGHTBULB,
                                                      self.chars_primary)
        serv_light_secondary = None
        self.char_on_primary = serv_light_primary.configure_char(CHAR_ON,
                                                                 value=0)

        if self.color_and_temp_supported:
            serv_light_secondary = self.add_preload_service(
                SERV_LIGHTBULB, self.chars_secondary)
            serv_light_primary.add_linked_service(serv_light_secondary)
            serv_light_primary.configure_char(CHAR_NAME, value="RGB")
            self.char_on_secondary = serv_light_secondary.configure_char(
                CHAR_ON, value=0)
            serv_light_secondary.configure_char(CHAR_NAME, value="Temperature")

        if self.is_brightness_supported:
            # Initial value is set to 100 because 0 is a special value (off). 100 is
            # an arbitrary non-zero value. It is updated immediately by async_update_state
            # to set to the correct initial value.
            self.char_brightness_primary = serv_light_primary.configure_char(
                CHAR_BRIGHTNESS, value=100)
            if self.chars_secondary:
                self.char_brightness_secondary = serv_light_secondary.configure_char(
                    CHAR_BRIGHTNESS, value=100)

        if self.is_color_temp_supported:
            min_mireds = attributes.get(ATTR_MIN_MIREDS, 153)
            max_mireds = attributes.get(ATTR_MAX_MIREDS, 500)
            serv_light = serv_light_secondary or serv_light_primary
            self.char_color_temperature = serv_light.configure_char(
                CHAR_COLOR_TEMPERATURE,
                value=min_mireds,
                properties={
                    PROP_MIN_VALUE: min_mireds,
                    PROP_MAX_VALUE: max_mireds
                },
            )

        if self.is_color_supported:
            self.char_hue = serv_light_primary.configure_char(CHAR_HUE,
                                                              value=0)
            self.char_saturation = serv_light_primary.configure_char(
                CHAR_SATURATION, value=75)

        self.async_update_state(state)

        if self.color_and_temp_supported:
            serv_light_primary.setter_callback = self._set_chars_primary
            serv_light_secondary.setter_callback = self._set_chars_secondary
        else:
            serv_light_primary.setter_callback = self._set_chars
Example #16
0
class HueOneLightChangeView(HomeAssistantView):
    """Handle requests for setting info about entities."""

    url = "/api/{username}/lights/{entity_number}/state"
    name = "emulated_hue:light:state"
    requires_auth = False

    def __init__(self, config):
        """Initialize the instance of the view."""
        self.config = config

    async def put(self, request, username, entity_number):  # noqa: C901
        """Process a request to set the state of an individual light."""
        if not is_local(ip_address(request.remote)):
            return self.json_message("Only local IPs allowed",
                                     HTTPStatus.UNAUTHORIZED)

        config = self.config
        hass = request.app["hass"]
        entity_id = config.number_to_entity_id(entity_number)

        if entity_id is None:
            _LOGGER.error("Unknown entity number: %s", entity_number)
            return self.json_message("Entity not found", HTTPStatus.NOT_FOUND)

        if (entity := hass.states.get(entity_id)) is None:
            _LOGGER.error("Entity not found: %s", entity_id)
            return self.json_message("Entity not found", HTTPStatus.NOT_FOUND)

        if not config.is_entity_exposed(entity):
            _LOGGER.error("Entity not exposed: %s", entity_id)
            return self.json_message("Entity not exposed",
                                     HTTPStatus.UNAUTHORIZED)

        try:
            request_json = await request.json()
        except ValueError:
            _LOGGER.error("Received invalid json")
            return self.json_message("Invalid JSON", HTTPStatus.BAD_REQUEST)

        # Get the entity's supported features
        entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
        if entity.domain == light.DOMAIN:
            color_modes = entity.attributes.get(
                light.ATTR_SUPPORTED_COLOR_MODES, [])

        # Parse the request
        parsed = {
            STATE_ON: False,
            STATE_BRIGHTNESS: None,
            STATE_HUE: None,
            STATE_SATURATION: None,
            STATE_COLOR_TEMP: None,
            STATE_XY: None,
            STATE_TRANSITION: None,
        }

        if HUE_API_STATE_ON in request_json:
            if not isinstance(request_json[HUE_API_STATE_ON], bool):
                _LOGGER.error("Unable to parse data: %s", request_json)
                return self.json_message("Bad request", HTTPStatus.BAD_REQUEST)
            parsed[STATE_ON] = request_json[HUE_API_STATE_ON]
        else:
            parsed[STATE_ON] = entity.state != STATE_OFF

        for (key, attr) in (
            (HUE_API_STATE_BRI, STATE_BRIGHTNESS),
            (HUE_API_STATE_HUE, STATE_HUE),
            (HUE_API_STATE_SAT, STATE_SATURATION),
            (HUE_API_STATE_CT, STATE_COLOR_TEMP),
            (HUE_API_STATE_TRANSITION, STATE_TRANSITION),
        ):
            if key in request_json:
                try:
                    parsed[attr] = int(request_json[key])
                except ValueError:
                    _LOGGER.error("Unable to parse data (2): %s", request_json)
                    return self.json_message("Bad request",
                                             HTTPStatus.BAD_REQUEST)
        if HUE_API_STATE_XY in request_json:
            try:
                parsed[STATE_XY] = (
                    float(request_json[HUE_API_STATE_XY][0]),
                    float(request_json[HUE_API_STATE_XY][1]),
                )
            except ValueError:
                _LOGGER.error("Unable to parse data (2): %s", request_json)
                return self.json_message("Bad request", HTTPStatus.BAD_REQUEST)

        if HUE_API_STATE_BRI in request_json:
            if entity.domain == light.DOMAIN:
                if light.brightness_supported(color_modes):
                    parsed[STATE_ON] = parsed[STATE_BRIGHTNESS] > 0
                else:
                    parsed[STATE_BRIGHTNESS] = None

            elif entity.domain == scene.DOMAIN:
                parsed[STATE_BRIGHTNESS] = None
                parsed[STATE_ON] = True

            elif entity.domain in [
                    script.DOMAIN,
                    media_player.DOMAIN,
                    fan.DOMAIN,
                    cover.DOMAIN,
                    climate.DOMAIN,
                    humidifier.DOMAIN,
            ]:
                # Convert 0-254 to 0-100
                level = (parsed[STATE_BRIGHTNESS] /
                         HUE_API_STATE_BRI_MAX) * 100
                parsed[STATE_BRIGHTNESS] = round(level)
                parsed[STATE_ON] = True

        # Choose general HA domain
        domain = core.DOMAIN

        # Entity needs separate call to turn on
        turn_on_needed = False

        # Convert the resulting "on" status into the service we need to call
        service = SERVICE_TURN_ON if parsed[STATE_ON] else SERVICE_TURN_OFF

        # Construct what we need to send to the service
        data = {ATTR_ENTITY_ID: entity_id}

        # If the requested entity is a light, set the brightness, hue,
        # saturation and color temp
        if entity.domain == light.DOMAIN:
            if parsed[STATE_ON]:
                if (light.brightness_supported(color_modes)
                        and parsed[STATE_BRIGHTNESS] is not None):
                    data[ATTR_BRIGHTNESS] = hue_brightness_to_hass(
                        parsed[STATE_BRIGHTNESS])

                if light.color_supported(color_modes):
                    if any((parsed[STATE_HUE], parsed[STATE_SATURATION])):
                        if parsed[STATE_HUE] is not None:
                            hue = parsed[STATE_HUE]
                        else:
                            hue = 0

                        if parsed[STATE_SATURATION] is not None:
                            sat = parsed[STATE_SATURATION]
                        else:
                            sat = 0

                        # Convert hs values to hass hs values
                        hue = int((hue / HUE_API_STATE_HUE_MAX) * 360)
                        sat = int((sat / HUE_API_STATE_SAT_MAX) * 100)

                        data[ATTR_HS_COLOR] = (hue, sat)

                    if parsed[STATE_XY] is not None:
                        data[ATTR_XY_COLOR] = parsed[STATE_XY]

                if (light.color_temp_supported(color_modes)
                        and parsed[STATE_COLOR_TEMP] is not None):
                    data[ATTR_COLOR_TEMP] = parsed[STATE_COLOR_TEMP]

                if (entity_features & SUPPORT_TRANSITION
                        and parsed[STATE_TRANSITION] is not None):
                    data[ATTR_TRANSITION] = parsed[STATE_TRANSITION] / 10

        # If the requested entity is a script, add some variables
        elif entity.domain == script.DOMAIN:
            data["variables"] = {
                "requested_state": STATE_ON if parsed[STATE_ON] else STATE_OFF
            }

            if parsed[STATE_BRIGHTNESS] is not None:
                data["variables"]["requested_level"] = parsed[STATE_BRIGHTNESS]

        # If the requested entity is a climate, set the temperature
        elif entity.domain == climate.DOMAIN:
            # We don't support turning climate devices on or off,
            # only setting the temperature
            service = None

            if (entity_features & SUPPORT_TARGET_TEMPERATURE
                    and parsed[STATE_BRIGHTNESS] is not None):
                domain = entity.domain
                service = SERVICE_SET_TEMPERATURE
                data[ATTR_TEMPERATURE] = parsed[STATE_BRIGHTNESS]

        # If the requested entity is a humidifier, set the humidity
        elif entity.domain == humidifier.DOMAIN:
            if parsed[STATE_BRIGHTNESS] is not None:
                turn_on_needed = True
                domain = entity.domain
                service = SERVICE_SET_HUMIDITY
                data[ATTR_HUMIDITY] = parsed[STATE_BRIGHTNESS]

        # If the requested entity is a media player, convert to volume
        elif entity.domain == media_player.DOMAIN:
            if (entity_features & SUPPORT_VOLUME_SET
                    and parsed[STATE_BRIGHTNESS] is not None):
                turn_on_needed = True
                domain = entity.domain
                service = SERVICE_VOLUME_SET
                # Convert 0-100 to 0.0-1.0
                data[
                    ATTR_MEDIA_VOLUME_LEVEL] = parsed[STATE_BRIGHTNESS] / 100.0

        # If the requested entity is a cover, convert to open_cover/close_cover
        elif entity.domain == cover.DOMAIN:
            domain = entity.domain
            if service == SERVICE_TURN_ON:
                service = SERVICE_OPEN_COVER
            else:
                service = SERVICE_CLOSE_COVER

            if (entity_features & SUPPORT_SET_POSITION
                    and parsed[STATE_BRIGHTNESS] is not None):
                domain = entity.domain
                service = SERVICE_SET_COVER_POSITION
                data[ATTR_POSITION] = parsed[STATE_BRIGHTNESS]

        # If the requested entity is a fan, convert to speed
        elif (entity.domain == fan.DOMAIN
              and entity_features & SUPPORT_SET_SPEED
              and parsed[STATE_BRIGHTNESS] is not None):
            domain = entity.domain
            # Convert 0-100 to a fan speed
            if (brightness := parsed[STATE_BRIGHTNESS]) == 0:
                data[ATTR_SPEED] = SPEED_OFF
            elif 0 < brightness <= 33.3:
                data[ATTR_SPEED] = SPEED_LOW
            elif 33.3 < brightness <= 66.6:
                data[ATTR_SPEED] = SPEED_MEDIUM
            elif 66.6 < brightness <= 100:
                data[ATTR_SPEED] = SPEED_HIGH
Example #17
0
def entity_to_json(config, entity):
    """Convert an entity to its Hue bridge JSON representation."""
    entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
    color_modes = entity.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES, [])
    unique_id = hashlib.md5(entity.entity_id.encode()).hexdigest()
    unique_id = f"00:{unique_id[0:2]}:{unique_id[2:4]}:{unique_id[4:6]}:{unique_id[6:8]}:{unique_id[8:10]}:{unique_id[10:12]}:{unique_id[12:14]}-{unique_id[14:16]}"

    state = get_entity_state(config, entity)

    retval = {
        "state": {
            HUE_API_STATE_ON: state[STATE_ON],
            "reachable": entity.state != STATE_UNAVAILABLE,
            "mode": "homeautomation",
        },
        "name": config.get_entity_name(entity),
        "uniqueid": unique_id,
        "manufacturername": "Home Assistant",
        "swversion": "123",
    }

    if light.color_supported(color_modes) and light.color_temp_supported(
            color_modes):
        # Extended Color light (Zigbee Device ID: 0x0210)
        # Same as Color light, but which supports additional setting of color temperature
        retval["type"] = "Extended color light"
        retval["modelid"] = "HASS231"
        retval["state"].update({
            HUE_API_STATE_BRI: state[STATE_BRIGHTNESS],
            HUE_API_STATE_HUE: state[STATE_HUE],
            HUE_API_STATE_SAT: state[STATE_SATURATION],
            HUE_API_STATE_CT: state[STATE_COLOR_TEMP],
            HUE_API_STATE_EFFECT: "none",
        })
        if state[STATE_HUE] > 0 or state[STATE_SATURATION] > 0:
            retval["state"][HUE_API_STATE_COLORMODE] = "hs"
        else:
            retval["state"][HUE_API_STATE_COLORMODE] = "ct"
    elif light.color_supported(color_modes):
        # Color light (Zigbee Device ID: 0x0200)
        # Supports on/off, dimming and color control (hue/saturation, enhanced hue, color loop and XY)
        retval["type"] = "Color light"
        retval["modelid"] = "HASS213"
        retval["state"].update({
            HUE_API_STATE_BRI: state[STATE_BRIGHTNESS],
            HUE_API_STATE_COLORMODE: "hs",
            HUE_API_STATE_HUE: state[STATE_HUE],
            HUE_API_STATE_SAT: state[STATE_SATURATION],
            HUE_API_STATE_EFFECT: "none",
        })
    elif light.color_temp_supported(color_modes):
        # Color temperature light (Zigbee Device ID: 0x0220)
        # Supports groups, scenes, on/off, dimming, and setting of a color temperature
        retval["type"] = "Color temperature light"
        retval["modelid"] = "HASS312"
        retval["state"].update({
            HUE_API_STATE_COLORMODE: "ct",
            HUE_API_STATE_CT: state[STATE_COLOR_TEMP],
            HUE_API_STATE_BRI: state[STATE_BRIGHTNESS],
        })
    elif entity_features & (SUPPORT_SET_POSITION
                            | SUPPORT_SET_SPEED
                            | SUPPORT_VOLUME_SET
                            | SUPPORT_TARGET_TEMPERATURE
                            ) or light.brightness_supported(color_modes):
        # Dimmable light (Zigbee Device ID: 0x0100)
        # Supports groups, scenes, on/off and dimming
        retval["type"] = "Dimmable light"
        retval["modelid"] = "HASS123"
        retval["state"].update({HUE_API_STATE_BRI: state[STATE_BRIGHTNESS]})
    elif not config.lights_all_dimmable:
        # On/Off light (ZigBee Device ID: 0x0000)
        # Supports groups, scenes and on/off control
        retval["type"] = "On/Off light"
        retval["productname"] = "On/Off light"
        retval["modelid"] = "HASS321"
    else:
        # Dimmable light (Zigbee Device ID: 0x0100)
        # Supports groups, scenes, on/off and dimming
        # Reports fixed brightness for compatibility with Alexa.
        retval["type"] = "Dimmable light"
        retval["modelid"] = "HASS123"
        retval["state"].update({HUE_API_STATE_BRI: HUE_API_STATE_BRI_MAX})

    return retval
Example #18
0
File: light.py Project: rikroe/core
    async def async_turn_on(self, **kwargs):
        """Turn the entity on."""
        transition = kwargs.get(light.ATTR_TRANSITION)
        duration = (transition *
                    10 if transition else self._default_transition *
                    10 if self._default_transition else DEFAULT_TRANSITION)
        brightness = kwargs.get(light.ATTR_BRIGHTNESS)
        effect = kwargs.get(light.ATTR_EFFECT)
        flash = kwargs.get(light.ATTR_FLASH)

        if brightness is None and self._off_brightness is not None:
            brightness = self._off_brightness

        t_log = {}
        if (brightness is not None or transition) and brightness_supported(
                self._attr_supported_color_modes):
            if brightness is not None:
                level = min(254, brightness)
            else:
                level = self._brightness or 254
            result = await self._level_channel.move_to_level_with_on_off(
                level, duration)
            t_log["move_to_level_with_on_off"] = result
            if isinstance(result,
                          Exception) or result[1] is not Status.SUCCESS:
                self.debug("turned on: %s", t_log)
                return
            self._state = bool(level)
            if level:
                self._brightness = level

        if brightness is None or (self._FORCE_ON and brightness):
            # since some lights don't always turn on with move_to_level_with_on_off,
            # we should call the on command on the on_off cluster if brightness is not 0.
            result = await self._on_off_channel.on()
            t_log["on_off"] = result
            if isinstance(result,
                          Exception) or result[1] is not Status.SUCCESS:
                self.debug("turned on: %s", t_log)
                return
            self._state = True
        if light.ATTR_COLOR_TEMP in kwargs:
            temperature = kwargs[light.ATTR_COLOR_TEMP]
            result = await self._color_channel.move_to_color_temp(
                temperature, duration)
            t_log["move_to_color_temp"] = result
            if isinstance(result,
                          Exception) or result[1] is not Status.SUCCESS:
                self.debug("turned on: %s", t_log)
                return
            self._color_temp = temperature
            self._hs_color = None

        if light.ATTR_HS_COLOR in kwargs:
            hs_color = kwargs[light.ATTR_HS_COLOR]
            xy_color = color_util.color_hs_to_xy(*hs_color)
            result = await self._color_channel.move_to_color(
                int(xy_color[0] * 65535), int(xy_color[1] * 65535), duration)
            t_log["move_to_color"] = result
            if isinstance(result,
                          Exception) or result[1] is not Status.SUCCESS:
                self.debug("turned on: %s", t_log)
                return
            self._hs_color = hs_color
            self._color_temp = None

        if effect == light.EFFECT_COLORLOOP:
            result = await self._color_channel.color_loop_set(
                UPDATE_COLORLOOP_ACTION
                | UPDATE_COLORLOOP_DIRECTION
                | UPDATE_COLORLOOP_TIME,
                0x2,  # start from current hue
                0x1,  # only support up
                transition if transition else 7,  # transition
                0,  # no hue
            )
            t_log["color_loop_set"] = result
            self._effect = light.EFFECT_COLORLOOP
        elif (self._effect == light.EFFECT_COLORLOOP
              and effect != light.EFFECT_COLORLOOP):
            result = await self._color_channel.color_loop_set(
                UPDATE_COLORLOOP_ACTION,
                0x0,
                0x0,
                0x0,
                0x0,  # update action only, action off, no dir, time, hue
            )
            t_log["color_loop_set"] = result
            self._effect = None

        if flash is not None:
            result = await self._identify_channel.trigger_effect(
                FLASH_EFFECTS[flash], EFFECT_DEFAULT_VARIANT)
            t_log["trigger_effect"] = result

        self._off_brightness = None
        self.debug("turned on: %s", t_log)
        self.async_write_ha_state()