def _set_chars(self, char_values): _LOGGER.debug("Light _set_chars: %s", char_values) events = [] service = SERVICE_TURN_ON params = {ATTR_ENTITY_ID: self.entity_id} if CHAR_ON in char_values: if not char_values[CHAR_ON]: service = SERVICE_TURN_OFF events.append(f"Set state to {char_values[CHAR_ON]}") if CHAR_BRIGHTNESS in char_values: if char_values[CHAR_BRIGHTNESS] == 0: events[-1] = "Set state to 0" service = SERVICE_TURN_OFF else: params[ATTR_BRIGHTNESS_PCT] = char_values[CHAR_BRIGHTNESS] events.append(f"brightness at {char_values[CHAR_BRIGHTNESS]}%") if CHAR_COLOR_TEMPERATURE in char_values: params[ATTR_COLOR_TEMP] = char_values[CHAR_COLOR_TEMPERATURE] events.append( f"color temperature at {char_values[CHAR_COLOR_TEMPERATURE]}") if (color_supported(self._color_modes) and CHAR_HUE in char_values and CHAR_SATURATION in char_values): color = (char_values[CHAR_HUE], char_values[CHAR_SATURATION]) _LOGGER.debug("%s: Set hs_color to %s", self.entity_id, color) params[ATTR_HS_COLOR] = color events.append(f"set color at {color}") self.async_call_service(DOMAIN, service, params, ", ".join(events))
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
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 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)
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)
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
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
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)
async def async_turn_on(self, **kwargs): """Forward the turn_on command to all lights in the light group.""" data = {ATTR_ENTITY_ID: self._entity_ids} emulate_color_temp_entity_ids = [] if ATTR_BRIGHTNESS in kwargs: data[ATTR_BRIGHTNESS] = kwargs[ATTR_BRIGHTNESS] if ATTR_HS_COLOR in kwargs: data[ATTR_HS_COLOR] = kwargs[ATTR_HS_COLOR] if ATTR_RGB_COLOR in kwargs: data[ATTR_RGB_COLOR] = kwargs[ATTR_RGB_COLOR] if ATTR_RGBW_COLOR in kwargs: data[ATTR_RGBW_COLOR] = kwargs[ATTR_RGBW_COLOR] if ATTR_RGBWW_COLOR in kwargs: data[ATTR_RGBWW_COLOR] = kwargs[ATTR_RGBWW_COLOR] if ATTR_XY_COLOR in kwargs: data[ATTR_XY_COLOR] = kwargs[ATTR_XY_COLOR] if ATTR_COLOR_TEMP in kwargs: data[ATTR_COLOR_TEMP] = kwargs[ATTR_COLOR_TEMP] # Create a new entity list to mutate updated_entities = list(self._entity_ids) # Walk through initial entity ids, split entity lists by support for entity_id in self._entity_ids: state = self.hass.states.get(entity_id) if not state: continue support = state.attributes.get(ATTR_SUPPORTED_COLOR_MODES) # Only pass color temperature to supported entity_ids if color_supported( support) and not color_temp_supported(support): emulate_color_temp_entity_ids.append(entity_id) updated_entities.remove(entity_id) data[ATTR_ENTITY_ID] = updated_entities if ATTR_WHITE_VALUE in kwargs: data[ATTR_WHITE_VALUE] = kwargs[ATTR_WHITE_VALUE] if ATTR_EFFECT in kwargs: data[ATTR_EFFECT] = kwargs[ATTR_EFFECT] if ATTR_TRANSITION in kwargs: data[ATTR_TRANSITION] = kwargs[ATTR_TRANSITION] if ATTR_FLASH in kwargs: data[ATTR_FLASH] = kwargs[ATTR_FLASH] if not emulate_color_temp_entity_ids: await self.hass.services.async_call( light.DOMAIN, light.SERVICE_TURN_ON, data, blocking=True, context=self._context, ) return emulate_color_temp_data = data.copy() temp_k = color_util.color_temperature_mired_to_kelvin( emulate_color_temp_data[ATTR_COLOR_TEMP]) hs_color = color_util.color_temperature_to_hs(temp_k) emulate_color_temp_data[ATTR_HS_COLOR] = hs_color del emulate_color_temp_data[ATTR_COLOR_TEMP] emulate_color_temp_data[ATTR_ENTITY_ID] = emulate_color_temp_entity_ids await asyncio.gather( self.hass.services.async_call( light.DOMAIN, light.SERVICE_TURN_ON, data, blocking=True, context=self._context, ), self.hass.services.async_call( light.DOMAIN, light.SERVICE_TURN_ON, emulate_color_temp_data, blocking=True, context=self._context, ), )
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
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