UNAVAILABLE_GRACE = 90 FIX_MAC_FW = AwesomeVersion("3.70") SWITCH_PRODUCT_IDS = [70, 71, 89] SERVICE_LIFX_SET_STATE = "set_state" ATTR_INFRARED = "infrared" ATTR_ZONES = "zones" ATTR_POWER = "power" LIFX_SET_STATE_SCHEMA = cv.make_entity_service_schema({ **LIGHT_TURN_ON_SCHEMA, ATTR_INFRARED: vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255)), ATTR_ZONES: vol.All(cv.ensure_list, [cv.positive_int]), ATTR_POWER: cv.boolean, }) SERVICE_EFFECT_PULSE = "effect_pulse" SERVICE_EFFECT_COLORLOOP = "effect_colorloop" SERVICE_EFFECT_STOP = "effect_stop" ATTR_POWER_ON = "power_on" ATTR_PERIOD = "period" ATTR_CYCLES = "cycles" ATTR_SPREAD = "spread" ATTR_CHANGE = "change"
# mypy: allow-untyped-defs, no-check-untyped-defs ATTR_CHANGED_BY = "changed_by" DOMAIN = "lock" SCAN_INTERVAL = timedelta(seconds=30) ENTITY_ID_ALL_LOCKS = group.ENTITY_ID_FORMAT.format("all_locks") ENTITY_ID_FORMAT = DOMAIN + ".{}" GROUP_NAME_ALL_LOCKS = "all locks" MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) LOCK_SERVICE_SCHEMA = make_entity_service_schema( {vol.Optional(ATTR_CODE): cv.string}) # Bitfield of features supported by the lock entity SUPPORT_OPEN = 1 _LOGGER = logging.getLogger(__name__) PROP_TO_ATTR = {"changed_by": ATTR_CHANGED_BY, "code_format": ATTR_CODE_FORMAT} @bind_hass def is_locked(hass, entity_id=None): """Return if the lock is locked based on the statemachine.""" entity_id = entity_id or ENTITY_ID_ALL_LOCKS return hass.states.is_state(entity_id, STATE_LOCKED)
async def async_setup(hass, config): """Track states and offer events for media_players.""" component = hass.data[DOMAIN] = EntityComponent( logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) hass.components.websocket_api.async_register_command( websocket_handle_thumbnail) hass.components.websocket_api.async_register_command( websocket_browse_media) hass.http.register_view(MediaPlayerImageView(component)) await component.async_setup(config) component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on", [SUPPORT_TURN_ON]) component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off", [SUPPORT_TURN_OFF]) component.async_register_entity_service( SERVICE_TOGGLE, {}, "async_toggle", [SUPPORT_TURN_OFF | SUPPORT_TURN_ON]) component.async_register_entity_service( SERVICE_VOLUME_UP, {}, "async_volume_up", [SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP], ) component.async_register_entity_service( SERVICE_VOLUME_DOWN, {}, "async_volume_down", [SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP], ) component.async_register_entity_service( SERVICE_MEDIA_PLAY_PAUSE, {}, "async_media_play_pause", [SUPPORT_PLAY | SUPPORT_PAUSE], ) component.async_register_entity_service(SERVICE_MEDIA_PLAY, {}, "async_media_play", [SUPPORT_PLAY]) component.async_register_entity_service(SERVICE_MEDIA_PAUSE, {}, "async_media_pause", [SUPPORT_PAUSE]) component.async_register_entity_service(SERVICE_MEDIA_STOP, {}, "async_media_stop", [SUPPORT_STOP]) component.async_register_entity_service(SERVICE_MEDIA_NEXT_TRACK, {}, "async_media_next_track", [SUPPORT_NEXT_TRACK]) component.async_register_entity_service( SERVICE_MEDIA_PREVIOUS_TRACK, {}, "async_media_previous_track", [SUPPORT_PREVIOUS_TRACK], ) component.async_register_entity_service(SERVICE_CLEAR_PLAYLIST, {}, "async_clear_playlist", [SUPPORT_CLEAR_PLAYLIST]) component.async_register_entity_service( SERVICE_VOLUME_SET, vol.All( cv.make_entity_service_schema( {vol.Required(ATTR_MEDIA_VOLUME_LEVEL): cv.small_float}), _rename_keys(volume=ATTR_MEDIA_VOLUME_LEVEL), ), "async_set_volume_level", [SUPPORT_VOLUME_SET], ) component.async_register_entity_service( SERVICE_VOLUME_MUTE, vol.All( cv.make_entity_service_schema( {vol.Required(ATTR_MEDIA_VOLUME_MUTED): cv.boolean}), _rename_keys(mute=ATTR_MEDIA_VOLUME_MUTED), ), "async_mute_volume", [SUPPORT_VOLUME_MUTE], ) component.async_register_entity_service( SERVICE_MEDIA_SEEK, vol.All( cv.make_entity_service_schema( {vol.Required(ATTR_MEDIA_SEEK_POSITION): cv.positive_float}), _rename_keys(position=ATTR_MEDIA_SEEK_POSITION), ), "async_media_seek", [SUPPORT_SEEK], ) component.async_register_entity_service( SERVICE_SELECT_SOURCE, {vol.Required(ATTR_INPUT_SOURCE): cv.string}, "async_select_source", [SUPPORT_SELECT_SOURCE], ) component.async_register_entity_service( SERVICE_SELECT_SOUND_MODE, {vol.Required(ATTR_SOUND_MODE): cv.string}, "async_select_sound_mode", [SUPPORT_SELECT_SOUND_MODE], ) component.async_register_entity_service( SERVICE_PLAY_MEDIA, vol.All( cv.make_entity_service_schema(MEDIA_PLAYER_PLAY_MEDIA_SCHEMA), _rename_keys( media_type=ATTR_MEDIA_CONTENT_TYPE, media_id=ATTR_MEDIA_CONTENT_ID, enqueue=ATTR_MEDIA_ENQUEUE, ), ), "async_play_media", [SUPPORT_PLAY_MEDIA], ) component.async_register_entity_service( SERVICE_SHUFFLE_SET, {vol.Required(ATTR_MEDIA_SHUFFLE): cv.boolean}, "async_set_shuffle", [SUPPORT_SHUFFLE_SET], ) component.async_register_entity_service( SERVICE_REPEAT_SET, {vol.Required(ATTR_MEDIA_REPEAT): vol.In(REPEAT_MODES)}, "async_set_repeat", [SUPPORT_REPEAT_SET], ) return True
async def async_setup(hass, config): """Expose light control via state machine and services.""" component = hass.data[DOMAIN] = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) await component.async_setup(config) # load profiles from files profiles_valid = await Profiles.load_profiles(hass) if not profiles_valid: return False def preprocess_data(data): """Preprocess the service data.""" base = { entity_field: data.pop(entity_field) for entity_field in cv.ENTITY_SERVICE_FIELDS if entity_field in data } base["params"] = preprocess_turn_on_alternatives(data) return base async def async_handle_light_on_service(light, call): """Handle turning a light on. If brightness is set to 0, this service will turn the light off. """ params = call.data["params"] if not params: default_profile = Profiles.get_default(light.entity_id) if default_profile is not None: params = {ATTR_PROFILE: default_profile} preprocess_turn_on_alternatives(params) elif ATTR_BRIGHTNESS_STEP in params or ATTR_BRIGHTNESS_STEP_PCT in params: brightness = light.brightness if light.is_on else 0 params = params.copy() if ATTR_BRIGHTNESS_STEP in params: brightness += params.pop(ATTR_BRIGHTNESS_STEP) else: brightness += round( params.pop(ATTR_BRIGHTNESS_STEP_PCT) / 100 * 255) params[ATTR_BRIGHTNESS] = max(0, min(255, brightness)) turn_light_off, off_params = preprocess_turn_off(params) if turn_light_off: await light.async_turn_off(**off_params) else: await light.async_turn_on(**params) async def async_handle_toggle_service(light, call): """Handle toggling a light. If brightness is set to 0, this service will turn the light off. """ if light.is_on: off_params = filter_turn_off_params(call.data["params"]) await light.async_turn_off(**off_params) else: await async_handle_light_on_service(light, call) # Listen for light on and light off service calls. component.async_register_entity_service( SERVICE_TURN_ON, vol.All(cv.make_entity_service_schema(LIGHT_TURN_ON_SCHEMA), preprocess_data), async_handle_light_on_service, ) component.async_register_entity_service( SERVICE_TURN_OFF, { ATTR_TRANSITION: VALID_TRANSITION, ATTR_FLASH: VALID_FLASH }, "async_turn_off", ) component.async_register_entity_service( SERVICE_TOGGLE, vol.All(cv.make_entity_service_schema(LIGHT_TURN_ON_SCHEMA), preprocess_data), async_handle_toggle_service, ) return True
async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Sonos from a config entry.""" if DATA_SONOS not in hass.data: hass.data[DATA_SONOS] = SonosData() config = hass.data[SONOS_DOMAIN].get("media_player", {}) _LOGGER.debug("Reached async_setup_entry, config=%s", config) advertise_addr = config.get(CONF_ADVERTISE_ADDR) if advertise_addr: pysonos.config.EVENT_ADVERTISE_IP = advertise_addr def _stop_discovery(event): data = hass.data[DATA_SONOS] if data.discovery_thread: data.discovery_thread.stop() data.discovery_thread = None if data.hosts_heartbeat: data.hosts_heartbeat() data.hosts_heartbeat = None def _discovery(now=None): """Discover players from network or configuration.""" hosts = config.get(CONF_HOSTS) def _discovered_player(soco): """Handle a (re)discovered player.""" try: _LOGGER.debug("Reached _discovered_player, soco=%s", soco) if soco.uid not in hass.data[DATA_SONOS].discovered: _LOGGER.debug("Adding new entity") hass.data[DATA_SONOS].discovered.append(soco.uid) hass.add_job(async_add_entities, [SonosEntity(soco)]) else: entity = _get_entity_from_soco_uid(hass, soco.uid) if entity and (entity.soco == soco or not entity.available): _LOGGER.debug("Seen %s", entity) hass.add_job(entity.async_seen(soco)) except SoCoException as ex: _LOGGER.debug("SoCoException, ex=%s", ex) if hosts: for host in hosts: try: _LOGGER.debug("Testing %s", host) player = pysonos.SoCo(socket.gethostbyname(host)) if player.is_visible: # Make sure that the player is available _ = player.volume _discovered_player(player) except (OSError, SoCoException) as ex: _LOGGER.debug("Exception %s", ex) if now is None: _LOGGER.warning("Failed to initialize '%s'", host) _LOGGER.debug("Tested all hosts") hass.data[ DATA_SONOS].hosts_heartbeat = hass.helpers.event.call_later( DISCOVERY_INTERVAL, _discovery) else: _LOGGER.debug("Starting discovery thread") hass.data[DATA_SONOS].discovery_thread = pysonos.discover_thread( _discovered_player, interval=DISCOVERY_INTERVAL, interface_addr=config.get(CONF_INTERFACE_ADDR), ) _LOGGER.debug("Adding discovery job") hass.async_add_executor_job(_discovery) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_discovery) platform = entity_platform.current_platform.get() @service.verify_domain_control(hass, SONOS_DOMAIN) async def async_service_handle(service_call: ServiceCall): """Handle dispatched services.""" entities = await platform.async_extract_from_service(service_call) if not entities: return if service_call.service == SERVICE_JOIN: master = platform.entities.get(service_call.data[ATTR_MASTER]) if master: await SonosEntity.join_multi(hass, master, entities) else: _LOGGER.error( "Invalid master specified for join service: %s", service_call.data[ATTR_MASTER], ) elif service_call.service == SERVICE_UNJOIN: await SonosEntity.unjoin_multi(hass, entities) elif service_call.service == SERVICE_SNAPSHOT: await SonosEntity.snapshot_multi( hass, entities, service_call.data[ATTR_WITH_GROUP]) elif service_call.service == SERVICE_RESTORE: await SonosEntity.restore_multi(hass, entities, service_call.data[ATTR_WITH_GROUP]) hass.services.async_register( SONOS_DOMAIN, SERVICE_JOIN, async_service_handle, cv.make_entity_service_schema( {vol.Required(ATTR_MASTER): cv.entity_id}), ) hass.services.async_register( SONOS_DOMAIN, SERVICE_UNJOIN, async_service_handle, cv.make_entity_service_schema({}), ) join_unjoin_schema = cv.make_entity_service_schema( {vol.Optional(ATTR_WITH_GROUP, default=True): cv.boolean}) hass.services.async_register(SONOS_DOMAIN, SERVICE_SNAPSHOT, async_service_handle, join_unjoin_schema) hass.services.async_register(SONOS_DOMAIN, SERVICE_RESTORE, async_service_handle, join_unjoin_schema) platform.async_register_entity_service( SERVICE_SET_TIMER, { vol.Required(ATTR_SLEEP_TIME): vol.All(vol.Coerce(int), vol.Range(min=0, max=86399)) }, "set_sleep_timer", ) platform.async_register_entity_service(SERVICE_CLEAR_TIMER, {}, "clear_sleep_timer") platform.async_register_entity_service( SERVICE_UPDATE_ALARM, { vol.Required(ATTR_ALARM_ID): cv.positive_int, vol.Optional(ATTR_TIME): cv.time, vol.Optional(ATTR_VOLUME): cv.small_float, vol.Optional(ATTR_ENABLED): cv.boolean, vol.Optional(ATTR_INCLUDE_LINKED_ZONES): cv.boolean, }, "set_alarm", ) platform.async_register_entity_service( SERVICE_SET_OPTION, { vol.Optional(ATTR_NIGHT_SOUND): cv.boolean, vol.Optional(ATTR_SPEECH_ENHANCE): cv.boolean, vol.Optional(ATTR_STATUS_LIGHT): cv.boolean, }, "set_option", ) platform.async_register_entity_service( SERVICE_PLAY_QUEUE, {vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int}, "play_queue", ) platform.async_register_entity_service( SERVICE_REMOVE_FROM_QUEUE, {vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int}, "remove_from_queue", )
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) SERVICE_SEND_COMMAND = "send_command" SERVICE_LEARN_COMMAND = "learn_command" SERVICE_DELETE_COMMAND = "delete_command" SERVICE_SYNC = "sync" DEFAULT_NUM_REPEATS = 1 DEFAULT_DELAY_SECS = 0.4 DEFAULT_HOLD_SECS = 0 SUPPORT_LEARN_COMMAND = 1 SUPPORT_DELETE_COMMAND = 2 SUPPORT_ACTIVITY = 4 REMOTE_SERVICE_ACTIVITY_SCHEMA = make_entity_service_schema( {vol.Optional(ATTR_ACTIVITY): cv.string}) @bind_hass def is_on(hass: HomeAssistant, entity_id: str) -> bool: """Return if the remote is on based on the statemachine.""" return hass.states.is_state(entity_id, STATE_ON) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Track states and offer events for remotes.""" component = hass.data[DOMAIN] = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) await component.async_setup(config) component.async_register_entity_service(SERVICE_TURN_OFF,
def validate_sql_select(value): """Validate that value is a SQL SELECT query.""" if not value.lstrip().lower().startswith('select'): raise vol.Invalid('Only SELECT queries allowed') return value SERVICE_SET = "set" SERVICE_SET_SCHEMA = make_entity_service_schema({ vol.Optional(ATTR_VALUE): cv.match_all, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_QUERY): vol.All(cv.string, validate_sql_select), vol.Optional(CONF_COLUMN): cv.string, vol.Optional(ATTR_UNIT_OF_MEASUREMENT): cv.string, vol.Optional(CONF_RESTORE): cv.boolean, vol.Optional(CONF_FORCE_UPDATE): cv.boolean, vol.Optional(ATTR_FRIENDLY_NAME): cv.string, vol.Optional(CONF_FRIENDLY_NAME_TEMPLATE): cv.template, vol.Optional(CONF_ICON): cv.icon, vol.Optional(CONF_ICON_TEMPLATE): cv.template, vol.Optional(ATTR_ENTITY_PICTURE): cv.string, vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template, vol.Optional(CONF_TRACKED_ENTITY_ID): cv.entity_ids, vol.Optional(CONF_TRACKED_EVENT_TYPE): validate_event_types, }) SERVICE_UPDATE = "update" SERVICE_UPDATE_SCHEMA = make_entity_service_schema({}) CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ cv.slug: vol.Any({
SCAN_INTERVAL = timedelta(seconds=60) CONVERTIBLE_ATTRIBUTE = [ ATTR_TEMPERATURE, ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH ] _LOGGER = logging.getLogger(__name__) SET_TEMPERATURE_SCHEMA = vol.All( cv.has_at_least_one_key(ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW), make_entity_service_schema({ vol.Exclusive(ATTR_TEMPERATURE, "temperature"): vol.Coerce(float), vol.Inclusive(ATTR_TARGET_TEMP_HIGH, "temperature"): vol.Coerce(float), vol.Inclusive(ATTR_TARGET_TEMP_LOW, "temperature"): vol.Coerce(float), vol.Optional(ATTR_HVAC_MODE): vol.In(HVAC_MODES), }), ) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up climate entities.""" component = hass.data[DOMAIN] = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) await component.async_setup(config) component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on")
SERVICE_SET_HVAC_RUN_MODE = "set_hvac_run_mode" SET_AIRCLEANER_SCHEMA = { vol.Required(ATTR_AIRCLEANER_MODE): cv.string, } SET_HUMIDITY_SCHEMA = { vol.Required(ATTR_HUMIDITY): vol.All(vol.Coerce(int), vol.Range(min=35, max=65)), } SET_HVAC_RUN_MODE_SCHEMA = vol.All( cv.has_at_least_one_key(ATTR_RUN_MODE, ATTR_HVAC_MODE), cv.make_entity_service_schema({ vol.Optional(ATTR_RUN_MODE): vol.In([HOLD_PERMANENT, HOLD_RESUME_SCHEDULE]), vol.Optional(ATTR_HVAC_MODE): vol.In([HVAC_MODE_HEAT, HVAC_MODE_COOL, HVAC_MODE_AUTO]), }), ) # # Nexia has two bits to determine hvac mode # There are actually eight states so we map to # the most significant state # # 1. Zone Mode : Auto / Cooling / Heating / Off # 2. Run Mode : Hold / Run Schedule # # HA_TO_NEXIA_HVAC_MODE_MAP = { HVAC_MODE_HEAT: OPERATION_MODE_HEAT,
async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Sonos from a config entry.""" platform = entity_platform.async_get_current_platform() @callback def async_create_entities(speaker: SonosSpeaker) -> None: """Handle device discovery and create entities.""" async_add_entities([SonosMediaPlayerEntity(speaker)]) @service.verify_domain_control(hass, SONOS_DOMAIN) async def async_service_handle(service_call: ServiceCall) -> None: """Handle dispatched services.""" assert platform is not None entities = await platform.async_extract_from_service(service_call) if not entities: return speakers = [] for entity in entities: assert isinstance(entity, SonosMediaPlayerEntity) speakers.append(entity.speaker) if service_call.service == SERVICE_JOIN: master = platform.entities.get(service_call.data[ATTR_MASTER]) if master: await SonosSpeaker.join_multi(hass, master.speaker, speakers ) # type: ignore[arg-type] else: _LOGGER.error( "Invalid master specified for join service: %s", service_call.data[ATTR_MASTER], ) elif service_call.service == SERVICE_UNJOIN: await SonosSpeaker.unjoin_multi(hass, speakers) # type: ignore[arg-type] elif service_call.service == SERVICE_SNAPSHOT: await SonosSpeaker.snapshot_multi( hass, speakers, service_call.data[ATTR_WITH_GROUP] # type: ignore[arg-type] ) elif service_call.service == SERVICE_RESTORE: await SonosSpeaker.restore_multi( hass, speakers, service_call.data[ATTR_WITH_GROUP] # type: ignore[arg-type] ) config_entry.async_on_unload( async_dispatcher_connect(hass, SONOS_CREATE_MEDIA_PLAYER, async_create_entities)) hass.services.async_register( SONOS_DOMAIN, SERVICE_JOIN, async_service_handle, cv.make_entity_service_schema( {vol.Required(ATTR_MASTER): cv.entity_id}), ) hass.services.async_register( SONOS_DOMAIN, SERVICE_UNJOIN, async_service_handle, cv.make_entity_service_schema({}), ) join_unjoin_schema = cv.make_entity_service_schema( {vol.Optional(ATTR_WITH_GROUP, default=True): cv.boolean}) hass.services.async_register(SONOS_DOMAIN, SERVICE_SNAPSHOT, async_service_handle, join_unjoin_schema) hass.services.async_register(SONOS_DOMAIN, SERVICE_RESTORE, async_service_handle, join_unjoin_schema) platform.async_register_entity_service( # type: ignore SERVICE_SET_TIMER, { vol.Required(ATTR_SLEEP_TIME): vol.All( vol.Coerce(int), vol.Range(min=0, max=86399) ) }, "set_sleep_timer", ) platform.async_register_entity_service(SERVICE_CLEAR_TIMER, {}, "clear_sleep_timer") # type: ignore platform.async_register_entity_service( # type: ignore SERVICE_UPDATE_ALARM, { vol.Required(ATTR_ALARM_ID): cv.positive_int, vol.Optional(ATTR_TIME): cv.time, vol.Optional(ATTR_VOLUME): cv.small_float, vol.Optional(ATTR_ENABLED): cv.boolean, vol.Optional(ATTR_INCLUDE_LINKED_ZONES): cv.boolean, }, "set_alarm", ) platform.async_register_entity_service( # type: ignore SERVICE_SET_OPTION, { vol.Optional(ATTR_BUTTONS_ENABLED): cv.boolean, vol.Optional(ATTR_NIGHT_SOUND): cv.boolean, vol.Optional(ATTR_SPEECH_ENHANCE): cv.boolean, vol.Optional(ATTR_STATUS_LIGHT): cv.boolean, }, "set_option", ) platform.async_register_entity_service( # type: ignore SERVICE_PLAY_QUEUE, {vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int}, "play_queue", ) platform.async_register_entity_service( # type: ignore SERVICE_REMOVE_FROM_QUEUE, {vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int}, "remove_from_queue", )
DOMAIN = "upb" ATTR_ADDRESS = "address" ATTR_BLINK_RATE = "blink_rate" ATTR_BRIGHTNESS = "brightness" ATTR_BRIGHTNESS_PCT = "brightness_pct" ATTR_RATE = "rate" CONF_NETWORK = "network" EVENT_UPB_SCENE_CHANGED = "upb.scene_changed" VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255)) VALID_BRIGHTNESS_PCT = vol.All(vol.Coerce(float), vol.Range(min=0, max=100)) VALID_RATE = vol.All(vol.Coerce(float), vol.Clamp(min=-1, max=3600)) UPB_BRIGHTNESS_RATE_SCHEMA = vol.All( cv.has_at_least_one_key(ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT), cv.make_entity_service_schema({ vol.Exclusive(ATTR_BRIGHTNESS, ATTR_BRIGHTNESS): VALID_BRIGHTNESS, vol.Exclusive(ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_PCT, vol.Optional(ATTR_RATE, default=-1): VALID_RATE, }), ) UPB_BLINK_RATE_SCHEMA = { vol.Required(ATTR_BLINK_RATE, default=0.5): vol.All(vol.Coerce(float), vol.Range(min=0, max=4.25)) }
async def async_setup(hass, config): """Expose light control via state machine and services.""" component = hass.data[DOMAIN] = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_LIGHTS) await component.async_setup(config) # load profiles from files profiles_valid = await Profiles.load_profiles(hass) if not profiles_valid: return False async def async_handle_light_on_service(service): """Handle a turn light on service call.""" # Get the validated data params = service.data.copy() # Convert the entity ids to valid light ids target_lights = await component.async_extract_from_service(service) params.pop(ATTR_ENTITY_ID, None) if service.context.user_id: user = await hass.auth.async_get_user(service.context.user_id) if user is None: raise UnknownUser(context=service.context) entity_perms = user.permissions.check_entity for light in target_lights: if not entity_perms(light, POLICY_CONTROL): raise Unauthorized( context=service.context, entity_id=light, permission=POLICY_CONTROL, ) preprocess_turn_on_alternatives(params) turn_lights_off, off_params = preprocess_turn_off(params) poll_lights = [] change_tasks = [] for light in target_lights: light.async_set_context(service.context) pars = params off_pars = off_params turn_light_off = turn_lights_off if not pars: pars = params.copy() pars[ATTR_PROFILE] = Profiles.get_default(light.entity_id) preprocess_turn_on_alternatives(pars) turn_light_off, off_pars = preprocess_turn_off(pars) if turn_light_off: task = light.async_request_call( light.async_turn_off(**off_pars)) else: task = light.async_request_call(light.async_turn_on(**pars)) change_tasks.append(task) if light.should_poll: poll_lights.append(light) if change_tasks: await asyncio.wait(change_tasks) if poll_lights: await asyncio.wait( [light.async_update_ha_state(True) for light in poll_lights]) # Listen for light on and light off service calls. hass.services.async_register( DOMAIN, SERVICE_TURN_ON, async_handle_light_on_service, schema=cv.make_entity_service_schema(LIGHT_TURN_ON_SCHEMA), ) component.async_register_entity_service( SERVICE_TURN_OFF, { ATTR_TRANSITION: VALID_TRANSITION, ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]), }, "async_turn_off", ) component.async_register_entity_service(SERVICE_TOGGLE, LIGHT_TURN_ON_SCHEMA, "async_toggle") return True