def register_services(hass): """Register services used by alarmo component.""" coordinator = hass.data[const.DOMAIN]["coordinator"] async def async_srv_toggle_user(call): """Enable a user by service call""" name = call.data.get(ATTR_NAME) enable = True if call.service == const.SERVICE_ENABLE_USER else False users = coordinator.store.async_get_users() user = next( (item for item in list(users.values()) if item[ATTR_NAME] == name), None) if user is None: _LOGGER.warning("Failed to {} user, no match for name '{}'".format( "enable" if enable else "disable", name)) return coordinator.store.async_update_user(user[const.ATTR_USER_ID], {const.ATTR_ENABLED: enable}) _LOGGER.debug("User user '{}' was {}".format( name, "enabled" if enable else "disabled")) async_register_admin_service(hass, const.DOMAIN, const.SERVICE_ENABLE_USER, async_srv_toggle_user, schema=const.SERVICE_TOGGLE_USER_SCHEMA) async_register_admin_service(hass, const.DOMAIN, const.SERVICE_DISABLE_USER, async_srv_toggle_user, schema=const.SERVICE_TOGGLE_USER_SCHEMA)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Scheduler integration from a config entry.""" session = async_get_clientsession(hass) coordinator = SchedulerCoordinator(hass, session, entry) device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, coordinator.id)}, name="Scheduler", model="Scheduler", sw_version="v1", manufacturer="@nielsfaber", ) hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = coordinator if entry.unique_id is None: hass.config_entries.async_update_entry(entry, unique_id=coordinator.id) hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, PLATFORM)) async def async_service_add(data): # TODO: add validation await coordinator.add_entity(data.data) service.async_register_admin_service(hass, DOMAIN, SERVICE_ADD, async_service_add, SCHEMA_ADD) return True
def register_component_services( component: EntityComponent, coordinator: IUCoordinator ) -> None: """Register the component""" @callback async def reload_service_handler(call: ServiceCall) -> None: """Reload yaml entities.""" # pylint: disable=unused-argument # pylint: disable=import-outside-toplevel from .binary_sensor import async_reload_platform conf = await component.async_prepare_reload(skip_reset=True) if conf is None or conf == {}: conf = {DOMAIN: {}} coordinator.load(conf[DOMAIN]) await async_reload_platform(component, coordinator) coordinator.start() async_register_admin_service( component.hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=RELOAD_SERVICE_SCHEMA, )
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up configured zones as well as Home Assistant zone if necessary.""" component = entity_component.EntityComponent(_LOGGER, DOMAIN, hass) id_manager = collection.IDManager() yaml_collection = collection.IDLessCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager) collection.sync_entity_lifecycle(hass, DOMAIN, DOMAIN, component, yaml_collection, Zone.from_yaml) storage_collection = ZoneStorageCollection( storage.Store(hass, STORAGE_VERSION, STORAGE_KEY), logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) collection.sync_entity_lifecycle(hass, DOMAIN, DOMAIN, component, storage_collection, Zone) if config[DOMAIN]: # AIS dom config can be empty if config[DOMAIN] != [{}]: await yaml_collection.async_load(config[DOMAIN]) await storage_collection.async_load() collection.StorageCollectionWebsocket(storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS).async_setup(hass) async def reload_service_handler(service_call: ServiceCall) -> None: """Remove all zones and load new ones from config.""" conf = await component.async_prepare_reload(skip_reset=True) if conf is None: return await yaml_collection.async_load(conf[DOMAIN]) service.async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=RELOAD_SERVICE_SCHEMA, ) if component.get_entity("zone.home"): return True home_zone = Zone(_home_conf(hass)) home_zone.entity_id = ENTITY_ID_HOME await component.async_add_entities([home_zone]) async def core_config_updated(_: Event) -> None: """Handle core config updated.""" await home_zone.async_update_config(_home_conf(hass)) hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, core_config_updated) hass.data[DOMAIN] = storage_collection return True
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the template integration.""" if DOMAIN in config: await _process_config(hass, config) async def _reload_config(call: Event | ServiceCall) -> None: """Reload top-level + platforms.""" try: unprocessed_conf = await conf_util.async_hass_config_yaml(hass) except HomeAssistantError as err: _LOGGER.error(err) return conf = await conf_util.async_process_component_config( hass, unprocessed_conf, await async_get_integration(hass, DOMAIN)) if conf is None: return await async_reload_integration_platforms(hass, DOMAIN, PLATFORMS) if DOMAIN in conf: await _process_config(hass, conf) hass.bus.async_fire(f"event_{DOMAIN}_reloaded", context=call.context) async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD, _reload_config) return True
async def async_setup(hass, config): """Set up all automations.""" # Local import to avoid circular import hass.data[DOMAIN] = component = EntityComponent(LOGGER, DOMAIN, hass) # To register the automation blueprints async_get_blueprints(hass) if not await _async_process_config(hass, config, component): await async_get_blueprints(hass).async_populate() async def trigger_service_handler(entity, service_call): """Handle forced automation trigger, e.g. from frontend.""" await entity.async_trigger( { **service_call.data[ATTR_VARIABLES], "trigger": { "platform": None } }, skip_condition=service_call.data[CONF_SKIP_CONDITION], context=service_call.context, ) component.async_register_entity_service( SERVICE_TRIGGER, { vol.Optional(ATTR_VARIABLES, default={}): dict, vol.Optional(CONF_SKIP_CONDITION, default=True): bool, }, trigger_service_handler, ) component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle") component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on") component.async_register_entity_service( SERVICE_TURN_OFF, { vol.Optional(CONF_STOP_ACTIONS, default=DEFAULT_STOP_ACTIONS): cv.boolean }, "async_turn_off", ) async def reload_service_handler(service_call): """Remove all automations and load new ones from config.""" conf = await component.async_prepare_reload() if conf is None: return async_get_blueprints(hass).async_reset_cache() await _async_process_config(hass, conf, component) hass.bus.async_fire(EVENT_AUTOMATION_RELOADED, context=service_call.context) async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.Schema({})) return True
async def async_setup_ha_cast(hass: core.HomeAssistant, entry: config_entries.ConfigEntry): """Set up Home Assistant Cast.""" user_id: str | None = entry.data.get("user_id") user: auth.models.User | None = None if user_id is not None: user = await hass.auth.async_get_user(user_id) if user is None: user = await hass.auth.async_create_system_user( "Home Assistant Cast", group_ids=[auth.const.GROUP_ID_ADMIN]) hass.config_entries.async_update_entry(entry, data={ **entry.data, "user_id": user.id }) if user.refresh_tokens: refresh_token: auth.models.RefreshToken = list( user.refresh_tokens.values())[0] else: refresh_token = await hass.auth.async_create_refresh_token(user) async def handle_show_view(call: core.ServiceCall) -> None: """Handle a Show View service call.""" try: hass_url = get_url(hass, require_ssl=True, prefer_external=True) except NoURLAvailableError as err: raise HomeAssistantError(NO_URL_AVAILABLE_ERROR) from err controller = HomeAssistantController( # If you are developing Home Assistant Cast, uncomment and set to your dev app id. # app_id="5FE44367", hass_url=hass_url, client_id=None, refresh_token=refresh_token.token, ) dispatcher.async_dispatcher_send( hass, SIGNAL_HASS_CAST_SHOW_VIEW, controller, call.data[ATTR_ENTITY_ID], call.data[ATTR_VIEW_PATH], call.data.get(ATTR_URL_PATH), ) async_register_admin_service( hass, DOMAIN, SERVICE_SHOW_VIEW, handle_show_view, vol.Schema({ ATTR_ENTITY_ID: cv.entity_id, ATTR_VIEW_PATH: str, vol.Optional(ATTR_URL_PATH): str, }), )
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up an input select.""" component = EntityComponent(LOGGER, DOMAIN, hass) # Process integration platforms right away since # we will create entities before firing EVENT_COMPONENT_LOADED await async_process_integration_platform_for_component(hass, DOMAIN) id_manager = IDManager() yaml_collection = YamlCollection(LOGGER, id_manager) sync_entity_lifecycle(hass, DOMAIN, DOMAIN, component, yaml_collection, Schedule.from_yaml) storage_collection = ScheduleStorageCollection( Store( hass, key=DOMAIN, version=STORAGE_VERSION, minor_version=STORAGE_VERSION_MINOR, ), logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) sync_entity_lifecycle(hass, DOMAIN, DOMAIN, component, storage_collection, Schedule) await yaml_collection.async_load([{ CONF_ID: id_, **cfg } for id_, cfg in config.get(DOMAIN, {}).items()]) await storage_collection.async_load() StorageCollectionWebsocket( storage_collection, DOMAIN, DOMAIN, BASE_SCHEMA | STORAGE_SCHEDULE_SCHEMA, BASE_SCHEMA | STORAGE_SCHEDULE_SCHEMA, ).async_setup(hass) async def reload_service_handler(service_call: ServiceCall) -> None: """Reload yaml entities.""" conf = await component.async_prepare_reload(skip_reset=True) if conf is None: conf = {DOMAIN: {}} await yaml_collection.async_load([{ CONF_ID: id_, **cfg } for id_, cfg in conf.get(DOMAIN, {}).items()]) async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, ) return True
async def async_setup(hass, config): """Set up the KNX component.""" try: hass.data[DOMAIN] = KNXModule(hass, config) hass.data[DOMAIN].async_create_exposures() await hass.data[DOMAIN].start() except XKNXException as ex: _LOGGER.warning("Could not connect to KNX interface: %s", ex) hass.components.persistent_notification.async_create( f"Could not connect to KNX interface: <br><b>{ex}</b>", title="KNX" ) for platform in SupportedPlatforms: if platform.value in config[DOMAIN]: for device_config in config[DOMAIN][platform.value]: create_knx_device(platform, hass.data[DOMAIN].xknx, device_config) # We need to wait until all entities are loaded into the device list since they could also be created from other platforms for platform in SupportedPlatforms: hass.async_create_task( discovery.async_load_platform(hass, platform.value, DOMAIN, {}, config) ) if not hass.data[DOMAIN].xknx.devices: _LOGGER.warning( "No KNX devices are configured. Please read " "https://www.home-assistant.io/blog/2020/09/17/release-115/#breaking-changes" ) hass.services.async_register( DOMAIN, SERVICE_KNX_SEND, hass.data[DOMAIN].service_send_to_knx_bus, schema=SERVICE_KNX_SEND_SCHEMA, ) async def reload_service_handler(service_call: ServiceCallType) -> None: """Remove all KNX components and load new ones from config.""" # First check for config file. If for some reason it is no longer there # or knx is no longer mentioned, stop the reload. config = await async_integration_yaml_config(hass, DOMAIN) if not config or DOMAIN not in config: return await hass.data[DOMAIN].xknx.stop() await asyncio.gather( *[platform.async_reset() for platform in async_get_platforms(hass, DOMAIN)] ) await async_setup(hass, config) async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.Schema({}) ) return True
async def async_setup(hass: HomeAssistant, config: ConfigType): """Set up the person component.""" entity_component = EntityComponent(_LOGGER, DOMAIN, hass) id_manager = collection.IDManager() yaml_collection = collection.YamlCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager ) storage_collection = PersonStorageCollection( PersonStore(hass, STORAGE_VERSION, STORAGE_KEY), logging.getLogger(f"{__name__}.storage_collection"), id_manager, yaml_collection, ) collection.sync_entity_lifecycle( hass, DOMAIN, DOMAIN, entity_component, yaml_collection, Person ) collection.sync_entity_lifecycle( hass, DOMAIN, DOMAIN, entity_component, storage_collection, Person.from_yaml ) await yaml_collection.async_load( await filter_yaml_data(hass, config.get(DOMAIN, [])) ) await storage_collection.async_load() hass.data[DOMAIN] = (yaml_collection, storage_collection) collection.StorageCollectionWebsocket( storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS ).async_setup(hass, create_list=False) websocket_api.async_register_command(hass, ws_list_person) async def _handle_user_removed(event: Event) -> None: """Handle a user being removed.""" user_id = event.data[ATTR_USER_ID] for person in storage_collection.async_items(): if person[CONF_USER_ID] == user_id: await storage_collection.async_update_item( person[CONF_ID], {CONF_USER_ID: None} ) hass.bus.async_listen(EVENT_USER_REMOVED, _handle_user_removed) async def async_reload_yaml(call: ServiceCall): """Reload YAML.""" conf = await entity_component.async_prepare_reload(skip_reset=True) if conf is None: return await yaml_collection.async_load( await filter_yaml_data(hass, conf.get(DOMAIN, [])) ) service.async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD, async_reload_yaml ) return True
async def async_setup(hass, config): """Set up the automation.""" hass.data[DOMAIN] = component = EntityComponent(_LOGGER, DOMAIN, hass) await _async_process_config(hass, config, component) async def trigger_service_handler(entity, service_call): """Handle automation triggers.""" await entity.async_trigger( service_call.data[ATTR_VARIABLES], skip_condition=service_call.data[CONF_SKIP_CONDITION], context=service_call.context, ) component.async_register_entity_service( SERVICE_TRIGGER, { vol.Optional(ATTR_VARIABLES, default={}): dict, vol.Optional(CONF_SKIP_CONDITION, default=True): bool, }, trigger_service_handler, ) component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle") component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on") component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off") async def reload_service_handler(service_call): """Remove all automations and load new ones from config.""" conf = await component.async_prepare_reload() if conf is None: return await _async_process_config(hass, conf, component) hass.bus.async_fire(EVENT_AUTOMATION_RELOADED, context=service_call.context) async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.Schema({}) ) @callback def async_describe_logbook_event(event): """Describe a logbook event.""" return { "name": event.data.get(ATTR_NAME), "message": "has been triggered", "entity_id": event.data.get(ATTR_ENTITY_ID), } hass.components.logbook.async_describe_event( DOMAIN, EVENT_AUTOMATION_TRIGGERED, async_describe_logbook_event ) return True
def _async_setup_themes(hass, themes): """Set up themes data and services.""" hass.data[DATA_DEFAULT_THEME] = DEFAULT_THEME hass.data[DATA_THEMES] = themes or {} @callback def update_theme_and_fire_event(): """Update theme_color in manifest.""" name = hass.data[DATA_DEFAULT_THEME] themes = hass.data[DATA_THEMES] MANIFEST_JSON["theme_color"] = DEFAULT_THEME_COLOR if name != DEFAULT_THEME: MANIFEST_JSON["theme_color"] = themes[name].get( "app-header-background-color", themes[name].get(PRIMARY_COLOR, DEFAULT_THEME_COLOR), ) hass.bus.async_fire(EVENT_THEMES_UPDATED) @callback def set_theme(call): """Set backend-preferred theme.""" data = call.data name = data[CONF_NAME] if name == DEFAULT_THEME or name in hass.data[DATA_THEMES]: _LOGGER.info("Theme %s set as default", name) hass.data[DATA_DEFAULT_THEME] = name update_theme_and_fire_event() else: _LOGGER.warning("Theme %s is not defined.", name) async def reload_themes(_): """Reload themes.""" config = await async_hass_config_yaml(hass) new_themes = config[DOMAIN].get(CONF_THEMES, {}) hass.data[DATA_THEMES] = new_themes if hass.data[DATA_DEFAULT_THEME] not in new_themes: hass.data[DATA_DEFAULT_THEME] = DEFAULT_THEME update_theme_and_fire_event() service.async_register_admin_service( hass, DOMAIN, SERVICE_SET_THEME, set_theme, vol.Schema({vol.Required(CONF_NAME): cv.string}), ) service.async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD_THEMES, reload_themes )
async def async_setup(hass, config): """Set up the automation.""" hass.data[DOMAIN] = component = EntityComponent(_LOGGER, DOMAIN, hass) await _async_process_config(hass, config, component) async def trigger_service_handler(entity, service_call): """Handle automation triggers.""" await entity.async_trigger( service_call.data[ATTR_VARIABLES], skip_condition=service_call.data[CONF_SKIP_CONDITION], context=service_call.context, ) component.async_register_entity_service( SERVICE_TRIGGER, { vol.Optional(ATTR_VARIABLES, default={}): dict, vol.Optional(CONF_SKIP_CONDITION, default=True): bool, }, trigger_service_handler, ) component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle") component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on") component.async_register_entity_service( SERVICE_TURN_OFF, { vol.Optional(CONF_STOP_ACTIONS, default=DEFAULT_STOP_ACTIONS): cv.boolean }, "async_turn_off", ) async def reload_service_handler(service_call): """Remove all automations and load new ones from config.""" conf = await component.async_prepare_reload() if conf is None: return await _async_process_config(hass, conf, component) hass.bus.async_fire(EVENT_AUTOMATION_RELOADED, context=service_call.context) async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.Schema({})) return True
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Remote Python Debugger component.""" conf = config[DOMAIN] async def debug_start(call: ServiceCall | None = None, *, wait: bool = True) -> None: """Enable asyncio debugging and start the debugger.""" get_running_loop().set_debug(True) debugpy.listen((conf[CONF_HOST], conf[CONF_PORT])) wait = conf[CONF_WAIT] if wait: _LOGGER.warning( "Waiting for remote debug connection on %s:%s", conf[CONF_HOST], conf[CONF_PORT], ) ready = Event() def waitfor(): debugpy.wait_for_client() hass.loop.call_soon_threadsafe(ready.set) Thread(target=waitfor).start() await ready.wait() else: _LOGGER.warning( "Listening for remote debug connection on %s:%s", conf[CONF_HOST], conf[CONF_PORT], ) async_register_admin_service(hass, DOMAIN, SERVICE_START, debug_start, schema=vol.Schema({})) # If set to start the debugger on startup, do so if conf[CONF_START]: await debug_start(wait=conf[CONF_WAIT]) return True
def register_component_services(component: EntityComponent, coordinator: IUCoordinator) -> None: async def reload_service_handler(call: ServiceCall) -> None: """Reload yaml entities.""" conf = await component.async_prepare_reload(skip_reset=True) if conf is None: conf = {DOMAIN: {}} coordinator.load(conf[DOMAIN]) return async_register_admin_service( component.hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=RELOAD_SERVICE_SCHEMA, ) return
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Profiler from a config entry.""" lock = asyncio.Lock() async def _async_run_profile(call: ServiceCall): async with lock: await _async_generate_profile(hass, call) async_register_admin_service( hass, DOMAIN, SERVICE_START, _async_run_profile, schema=vol.Schema( {vol.Optional(CONF_SECONDS, default=60.0): vol.Coerce(float)}), ) return True
async def async_setup(hass: HomeAssistant, config: Dict) -> bool: """Set up configured zones as well as Home Assistant zone if necessary.""" component = entity_component.EntityComponent(_LOGGER, DOMAIN, hass) id_manager = collection.IDManager() yaml_collection = collection.IDLessCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager ) collection.attach_entity_component_collection( component, yaml_collection, lambda conf: Zone(conf, False) ) storage_collection = ZoneStorageCollection( storage.Store(hass, STORAGE_VERSION, STORAGE_KEY), logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) collection.attach_entity_component_collection( component, storage_collection, lambda conf: Zone(conf, True) ) if config[DOMAIN]: # AIS dom config can be empty if config[DOMAIN] != [{}]: await yaml_collection.async_load(config[DOMAIN]) await storage_collection.async_load() collection.StorageCollectionWebsocket( storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS ).async_setup(hass) async def _collection_changed(change_type: str, item_id: str, config: Dict) -> None: """Handle a collection change: clean up entity registry on removals.""" if change_type != collection.CHANGE_REMOVED: return ent_reg = await entity_registry.async_get_registry(hass) ent_reg.async_remove( cast(str, ent_reg.async_get_entity_id(DOMAIN, DOMAIN, item_id)) ) storage_collection.async_add_listener(_collection_changed) async def reload_service_handler(service_call: ServiceCall) -> None: """Remove all zones and load new ones from config.""" conf = await component.async_prepare_reload(skip_reset=True) if conf is None: return await yaml_collection.async_load(conf[DOMAIN]) service.async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=RELOAD_SERVICE_SCHEMA, ) if component.get_entity("zone.home"): return True home_zone = Zone( _home_conf(hass), True, ) home_zone.entity_id = ENTITY_ID_HOME await component.async_add_entities([home_zone]) async def core_config_updated(_: Event) -> None: """Handle core config updated.""" await home_zone.async_update_config(_home_conf(hass)) hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, core_config_updated) hass.data[DOMAIN] = storage_collection return True
async def async_setup(hass, config): """Set up the KNX component.""" try: knx_module = KNXModule(hass, config) hass.data[DOMAIN] = knx_module await knx_module.start() except XKNXException as ex: _LOGGER.warning("Could not connect to KNX interface: %s", ex) hass.components.persistent_notification.async_create( f"Could not connect to KNX interface: <br><b>{ex}</b>", title="KNX") if CONF_KNX_EXPOSE in config[DOMAIN]: for expose_config in config[DOMAIN][CONF_KNX_EXPOSE]: knx_module.exposures.append( create_knx_exposure(hass, knx_module.xknx, expose_config)) for platform in SupportedPlatforms: if platform.value in config[DOMAIN]: for device_config in config[DOMAIN][platform.value]: create_knx_device(platform, knx_module.xknx, device_config) # We need to wait until all entities are loaded into the device list since they could also be created from other platforms for platform in SupportedPlatforms: hass.async_create_task( discovery.async_load_platform(hass, platform.value, DOMAIN, {}, config)) hass.services.async_register( DOMAIN, SERVICE_KNX_SEND, knx_module.service_send_to_knx_bus, schema=SERVICE_KNX_SEND_SCHEMA, ) hass.services.async_register( DOMAIN, SERVICE_KNX_READ, knx_module.service_read_to_knx_bus, schema=SERVICE_KNX_READ_SCHEMA, ) async_register_admin_service( hass, DOMAIN, SERVICE_KNX_EVENT_REGISTER, knx_module.service_event_register_modify, schema=SERVICE_KNX_EVENT_REGISTER_SCHEMA, ) async_register_admin_service( hass, DOMAIN, SERVICE_KNX_EXPOSURE_REGISTER, knx_module.service_exposure_register_modify, schema=SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA, ) async def reload_service_handler(service_call: ServiceCallType) -> None: """Remove all KNX components and load new ones from config.""" # First check for config file. If for some reason it is no longer there # or knx is no longer mentioned, stop the reload. config = await async_integration_yaml_config(hass, DOMAIN) if not config or DOMAIN not in config: return await knx_module.xknx.stop() await asyncio.gather(*[ platform.async_reset() for platform in async_get_platforms(hass, DOMAIN) ]) await async_setup(hass, config) async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.Schema({})) return True
async def reload_service_handler(service_call): """Remove all automations and load new ones from config.""" if (conf := await component.async_prepare_reload()) is None: return async_get_blueprints(hass).async_reset_cache() await _async_process_config(hass, conf, component) hass.bus.async_fire(EVENT_AUTOMATION_RELOADED, context=service_call.context) reload_helper = ReloadServiceHelper(reload_service_handler) async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD, reload_helper.execute_service, schema=vol.Schema({}), ) return True class AutomationEntity(ToggleEntity, RestoreEntity): """Entity to show status of entity.""" _attr_should_poll = False def __init__( self, automation_id,
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Profiler from a config entry.""" lock = asyncio.Lock() domain_data = hass.data[DOMAIN] = {} async def _async_run_profile(call: ServiceCall) -> None: async with lock: await _async_generate_profile(hass, call) async def _async_run_memory_profile(call: ServiceCall) -> None: async with lock: await _async_generate_memory_profile(hass, call) async def _async_start_log_objects(call: ServiceCall) -> None: if LOG_INTERVAL_SUB in domain_data: domain_data[LOG_INTERVAL_SUB]() hass.components.persistent_notification.async_create( "Object growth logging has started. See [the logs](/config/logs) to track the growth of new objects.", title="Object growth logging started", notification_id="profile_object_logging", ) await hass.async_add_executor_job(_log_objects) domain_data[LOG_INTERVAL_SUB] = async_track_time_interval( hass, _log_objects, call.data[CONF_SCAN_INTERVAL]) async def _async_stop_log_objects(call: ServiceCall) -> None: if LOG_INTERVAL_SUB not in domain_data: return hass.components.persistent_notification.async_dismiss( "profile_object_logging") domain_data.pop(LOG_INTERVAL_SUB)() def _dump_log_objects(call: ServiceCall) -> None: obj_type = call.data[CONF_TYPE] _LOGGER.critical( "%s objects in memory: %s", obj_type, objgraph.by_type(obj_type), ) hass.components.persistent_notification.create( f"Objects with type {obj_type} have been dumped to the log. See [the logs](/config/logs) to review the repr of the objects.", title="Object dump completed", notification_id="profile_object_dump", ) async def _async_dump_thread_frames(call: ServiceCall) -> None: """Log all thread frames.""" frames = sys._current_frames() # pylint: disable=protected-access main_thread = threading.main_thread() for thread in threading.enumerate(): if thread == main_thread: continue _LOGGER.critical( "Thread [%s]: %s", thread.name, "".join(traceback.format_stack(frames.get( thread.ident))).strip(), ) async def _async_dump_scheduled(call: ServiceCall) -> None: """Log all scheduled in the event loop.""" arepr = reprlib.aRepr original_maxstring = arepr.maxstring original_maxother = arepr.maxother arepr.maxstring = 300 arepr.maxother = 300 try: for handle in hass.loop._scheduled: # pylint: disable=protected-access if not handle.cancelled(): _LOGGER.critical("Scheduled: %s", handle) finally: arepr.max_string = original_maxstring arepr.max_other = original_maxother async_register_admin_service( hass, DOMAIN, SERVICE_START, _async_run_profile, schema=vol.Schema( {vol.Optional(CONF_SECONDS, default=60.0): vol.Coerce(float)}), ) async_register_admin_service( hass, DOMAIN, SERVICE_MEMORY, _async_run_memory_profile, schema=vol.Schema( {vol.Optional(CONF_SECONDS, default=60.0): vol.Coerce(float)}), ) async_register_admin_service( hass, DOMAIN, SERVICE_START_LOG_OBJECTS, _async_start_log_objects, schema=vol.Schema({ vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): cv.time_period }), ) async_register_admin_service( hass, DOMAIN, SERVICE_STOP_LOG_OBJECTS, _async_stop_log_objects, ) async_register_admin_service( hass, DOMAIN, SERVICE_DUMP_LOG_OBJECTS, _dump_log_objects, schema=vol.Schema({vol.Required(CONF_TYPE): str}), ) async_register_admin_service( hass, DOMAIN, SERVICE_LOG_THREAD_FRAMES, _async_dump_thread_frames, ) async_register_admin_service( hass, DOMAIN, SERVICE_LOG_EVENT_LOOP_SCHEDULED, _async_dump_scheduled, ) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Load a config entry.""" conf = hass.data.get(DATA_KNX_CONFIG) # When reloading if conf is None: conf = await async_integration_yaml_config(hass, DOMAIN) if not conf or DOMAIN not in conf: return False conf = conf[DOMAIN] # If user didn't have configuration.yaml config, generate defaults if conf is None: conf = CONFIG_SCHEMA({DOMAIN: dict(entry.data)})[DOMAIN] config = {**conf, **entry.data} try: knx_module = KNXModule(hass, config, entry) await knx_module.start() except XKNXException as ex: raise ConfigEntryNotReady from ex hass.data[DATA_KNX_CONFIG] = conf hass.data[DOMAIN] = knx_module if CONF_KNX_EXPOSE in config: for expose_config in config[CONF_KNX_EXPOSE]: knx_module.exposures.append( create_knx_exposure(hass, knx_module.xknx, expose_config) ) hass.config_entries.async_setup_platforms( entry, [platform for platform in SUPPORTED_PLATFORMS if platform in config] ) # set up notify platform, no entry support for notify component yet, # have to use discovery to load platform. if NotifySchema.PLATFORM in conf: hass.async_create_task( discovery.async_load_platform( hass, "notify", DOMAIN, conf[NotifySchema.PLATFORM], config ) ) hass.services.async_register( DOMAIN, SERVICE_KNX_SEND, knx_module.service_send_to_knx_bus, schema=SERVICE_KNX_SEND_SCHEMA, ) hass.services.async_register( DOMAIN, SERVICE_KNX_READ, knx_module.service_read_to_knx_bus, schema=SERVICE_KNX_READ_SCHEMA, ) async_register_admin_service( hass, DOMAIN, SERVICE_KNX_EVENT_REGISTER, knx_module.service_event_register_modify, schema=SERVICE_KNX_EVENT_REGISTER_SCHEMA, ) async_register_admin_service( hass, DOMAIN, SERVICE_KNX_EXPOSURE_REGISTER, knx_module.service_exposure_register_modify, schema=SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA, ) return True
async def async_setup_services(hass: HomeAssistantType) -> None: """Set up the HomematicIP Cloud services.""" if hass.services.async_services().get(HMIPC_DOMAIN): return @verify_domain_control(hass, HMIPC_DOMAIN) async def async_call_hmipc_service(service: ServiceCallType): """Call correct HomematicIP Cloud service.""" service_name = service.service if service_name == SERVICE_ACTIVATE_ECO_MODE_WITH_DURATION: await _async_activate_eco_mode_with_duration(hass, service) elif service_name == SERVICE_ACTIVATE_ECO_MODE_WITH_PERIOD: await _async_activate_eco_mode_with_period(hass, service) elif service_name == SERVICE_ACTIVATE_VACATION: await _async_activate_vacation(hass, service) elif service_name == SERVICE_DEACTIVATE_ECO_MODE: await _async_deactivate_eco_mode(hass, service) elif service_name == SERVICE_DEACTIVATE_VACATION: await _async_deactivate_vacation(hass, service) elif service_name == SERVICE_DUMP_HAP_CONFIG: await _async_dump_hap_config(hass, service) elif service_name == SERVICE_RESET_ENERGY_COUNTER: await _async_reset_energy_counter(hass, service) elif service_name == SERVICE_SET_ACTIVE_CLIMATE_PROFILE: await _set_active_climate_profile(hass, service) hass.services.async_register( domain=HMIPC_DOMAIN, service=SERVICE_ACTIVATE_ECO_MODE_WITH_DURATION, service_func=async_call_hmipc_service, schema=SCHEMA_ACTIVATE_ECO_MODE_WITH_DURATION, ) hass.services.async_register( domain=HMIPC_DOMAIN, service=SERVICE_ACTIVATE_ECO_MODE_WITH_PERIOD, service_func=async_call_hmipc_service, schema=SCHEMA_ACTIVATE_ECO_MODE_WITH_PERIOD, ) hass.services.async_register( domain=HMIPC_DOMAIN, service=SERVICE_ACTIVATE_VACATION, service_func=async_call_hmipc_service, schema=SCHEMA_ACTIVATE_VACATION, ) hass.services.async_register( domain=HMIPC_DOMAIN, service=SERVICE_DEACTIVATE_ECO_MODE, service_func=async_call_hmipc_service, schema=SCHEMA_DEACTIVATE_ECO_MODE, ) hass.services.async_register( domain=HMIPC_DOMAIN, service=SERVICE_DEACTIVATE_VACATION, service_func=async_call_hmipc_service, schema=SCHEMA_DEACTIVATE_VACATION, ) hass.services.async_register( domain=HMIPC_DOMAIN, service=SERVICE_SET_ACTIVE_CLIMATE_PROFILE, service_func=async_call_hmipc_service, schema=SCHEMA_SET_ACTIVE_CLIMATE_PROFILE, ) async_register_admin_service( hass=hass, domain=HMIPC_DOMAIN, service=SERVICE_DUMP_HAP_CONFIG, service_func=async_call_hmipc_service, schema=SCHEMA_DUMP_HAP_CONFIG, ) async_register_admin_service( hass=hass, domain=HMIPC_DOMAIN, service=SERVICE_RESET_ENERGY_COUNTER, service_func=async_call_hmipc_service, schema=SCHEMA_RESET_ENERGY_COUNTER, )
async def _async_setup_themes(hass: HomeAssistant, themes: dict[str, Any] | None) -> None: """Set up themes data and services.""" hass.data[DATA_THEMES] = themes or {} store = hass.data[DATA_THEMES_STORE] = hass.helpers.storage.Store( THEMES_STORAGE_VERSION, THEMES_STORAGE_KEY) theme_data = await store.async_load() or {} theme_name = theme_data.get(DATA_DEFAULT_THEME, DEFAULT_THEME) dark_theme_name = theme_data.get(DATA_DEFAULT_DARK_THEME) if theme_name == DEFAULT_THEME or theme_name in hass.data[DATA_THEMES]: hass.data[DATA_DEFAULT_THEME] = theme_name else: hass.data[DATA_DEFAULT_THEME] = DEFAULT_THEME if dark_theme_name == DEFAULT_THEME or dark_theme_name in hass.data[ DATA_THEMES]: hass.data[DATA_DEFAULT_DARK_THEME] = dark_theme_name @callback def update_theme_and_fire_event() -> None: """Update theme_color in manifest.""" name = hass.data[DATA_DEFAULT_THEME] themes = hass.data[DATA_THEMES] if name != DEFAULT_THEME: MANIFEST_JSON.update_key( "theme_color", themes[name].get( "app-header-background-color", themes[name].get(PRIMARY_COLOR, DEFAULT_THEME_COLOR), ), ) else: MANIFEST_JSON.update_key("theme_color", DEFAULT_THEME_COLOR) hass.bus.async_fire(EVENT_THEMES_UPDATED) @callback def set_theme(call: ServiceCall) -> None: """Set backend-preferred theme.""" name = call.data[CONF_NAME] mode = call.data.get("mode", "light") if (name not in (DEFAULT_THEME, VALUE_NO_THEME) and name not in hass.data[DATA_THEMES]): _LOGGER.warning("Theme %s not found", name) return light_mode = mode == "light" theme_key = DATA_DEFAULT_THEME if light_mode else DATA_DEFAULT_DARK_THEME if name == VALUE_NO_THEME: to_set = DEFAULT_THEME if light_mode else None else: _LOGGER.info("Theme %s set as default %s theme", name, mode) to_set = name hass.data[theme_key] = to_set store.async_delay_save( lambda: { DATA_DEFAULT_THEME: hass.data[DATA_DEFAULT_THEME], DATA_DEFAULT_DARK_THEME: hass.data.get(DATA_DEFAULT_DARK_THEME ), }, THEMES_SAVE_DELAY, ) update_theme_and_fire_event() async def reload_themes(_: ServiceCall) -> None: """Reload themes.""" config = await async_hass_config_yaml(hass) new_themes = config[DOMAIN].get(CONF_THEMES, {}) hass.data[DATA_THEMES] = new_themes if hass.data[DATA_DEFAULT_THEME] not in new_themes: hass.data[DATA_DEFAULT_THEME] = DEFAULT_THEME if (hass.data.get(DATA_DEFAULT_DARK_THEME) and hass.data.get(DATA_DEFAULT_DARK_THEME) not in new_themes): hass.data[DATA_DEFAULT_DARK_THEME] = None update_theme_and_fire_event() service.async_register_admin_service( hass, DOMAIN, SERVICE_SET_THEME, set_theme, vol.Schema({ vol.Required(CONF_NAME): cv.string, vol.Optional(CONF_MODE): vol.Any("dark", "light"), }), ) service.async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD_THEMES, reload_themes)
"""Reload themes.""" config = await async_hass_config_yaml(hass) new_themes = config.get(DOMAIN, {}).get(CONF_THEMES, {}) hass.data[DATA_THEMES] = new_themes if hass.data[DATA_DEFAULT_THEME] not in new_themes: hass.data[DATA_DEFAULT_THEME] = DEFAULT_THEME if (hass.data.get(DATA_DEFAULT_DARK_THEME) and hass.data.get(DATA_DEFAULT_DARK_THEME) not in new_themes): hass.data[DATA_DEFAULT_DARK_THEME] = None update_theme_and_fire_event() service.async_register_admin_service( hass, DOMAIN, SERVICE_SET_THEME, set_theme, vol.Schema({ vol.Required(CONF_NAME): cv.string, vol.Optional(CONF_MODE): vol.Any("dark", "light"), }), ) service.async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD_THEMES, reload_themes) @callback @lru_cache(maxsize=1) def _async_render_index_cached(template: jinja2.Template, **kwargs: Any) -> str: return template.render(**kwargs)
async def test_register_admin_service(hass, hass_read_only_user, hass_admin_user): """Test the register admin service.""" calls = [] async def mock_service(call): calls.append(call) service.async_register_admin_service(hass, "test", "test", mock_service) service.async_register_admin_service( hass, "test", "test2", mock_service, vol.Schema({vol.Required("required"): cv.boolean}), ) with pytest.raises(exceptions.UnknownUser): await hass.services.async_call( "test", "test", {}, blocking=True, context=ha.Context(user_id="non-existing"), ) assert len(calls) == 0 with pytest.raises(exceptions.Unauthorized): await hass.services.async_call( "test", "test", {}, blocking=True, context=ha.Context(user_id=hass_read_only_user.id), ) assert len(calls) == 0 with pytest.raises(vol.Invalid): await hass.services.async_call( "test", "test", {"invalid": True}, blocking=True, context=ha.Context(user_id=hass_admin_user.id), ) assert len(calls) == 0 with pytest.raises(vol.Invalid): await hass.services.async_call( "test", "test2", {}, blocking=True, context=ha.Context(user_id=hass_admin_user.id), ) assert len(calls) == 0 await hass.services.async_call( "test", "test2", {"required": True}, blocking=True, context=ha.Context(user_id=hass_admin_user.id), ) assert len(calls) == 1 assert calls[0].context.user_id == hass_admin_user.id
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up SimpliSafe as config entry.""" hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = {} _async_standardize_config_entry(hass, entry) _verify_domain_control = verify_domain_control(hass, DOMAIN) websession = aiohttp_client.async_get_clientsession(hass) try: api = await API.async_from_refresh_token(entry.data[CONF_TOKEN], session=websession) except InvalidCredentialsError as err: raise ConfigEntryAuthFailed from err except SimplipyError as err: LOGGER.error("Config entry failed: %s", err) raise ConfigEntryNotReady from err simplisafe = SimpliSafe(hass, entry, api) try: await simplisafe.async_init() except SimplipyError as err: raise ConfigEntryNotReady from err hass.data[DOMAIN][entry.entry_id][DATA_CLIENT] = simplisafe hass.config_entries.async_setup_platforms(entry, PLATFORMS) @callback def verify_system_exists( coro: Callable[..., Awaitable]) -> Callable[..., Awaitable]: """Log an error if a service call uses an invalid system ID.""" async def decorator(call: ServiceCall) -> None: """Decorate.""" system_id = int(call.data[ATTR_SYSTEM_ID]) if system_id not in simplisafe.systems: LOGGER.error("Unknown system ID in service call: %s", system_id) return await coro(call) return decorator @callback def v3_only(coro: Callable[..., Awaitable]) -> Callable[..., Awaitable]: """Log an error if the decorated coroutine is called with a v2 system.""" async def decorator(call: ServiceCall) -> None: """Decorate.""" system = simplisafe.systems[int(call.data[ATTR_SYSTEM_ID])] if system.version != 3: LOGGER.error("Service only available on V3 systems") return await coro(call) return decorator @verify_system_exists @_verify_domain_control async def clear_notifications(call: ServiceCall) -> None: """Clear all active notifications.""" system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] try: await system.async_clear_notifications() except SimplipyError as err: LOGGER.error("Error during service call: %s", err) @verify_system_exists @_verify_domain_control async def remove_pin(call: ServiceCall) -> None: """Remove a PIN.""" system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] try: await system.async_remove_pin(call.data[ATTR_PIN_LABEL_OR_VALUE]) except SimplipyError as err: LOGGER.error("Error during service call: %s", err) @verify_system_exists @_verify_domain_control async def set_pin(call: ServiceCall) -> None: """Set a PIN.""" system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] try: await system.async_set_pin(call.data[ATTR_PIN_LABEL], call.data[ATTR_PIN_VALUE]) except SimplipyError as err: LOGGER.error("Error during service call: %s", err) @verify_system_exists @v3_only @_verify_domain_control async def set_system_properties(call: ServiceCall) -> None: """Set one or more system parameters.""" system = cast(SystemV3, simplisafe.systems[call.data[ATTR_SYSTEM_ID]]) try: await system.async_set_properties({ prop: value for prop, value in call.data.items() if prop != ATTR_SYSTEM_ID }) except SimplipyError as err: LOGGER.error("Error during service call: %s", err) for service, method, schema in ( ("clear_notifications", clear_notifications, None), ("remove_pin", remove_pin, SERVICE_REMOVE_PIN_SCHEMA), ("set_pin", set_pin, SERVICE_SET_PIN_SCHEMA), ( "set_system_properties", set_system_properties, SERVICE_SET_SYSTEM_PROPERTIES_SCHEMA, ), ): async_register_admin_service(hass, DOMAIN, service, method, schema=schema) entry.async_on_unload(entry.add_update_listener(async_reload_entry)) return True
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Initialize the Home Assistant cloud.""" # Process configs if DOMAIN in config: kwargs = dict(config[DOMAIN]) else: kwargs = {CONF_MODE: DEFAULT_MODE} # Alexa/Google custom config alexa_conf = kwargs.pop(CONF_ALEXA, None) or ALEXA_SCHEMA({}) google_conf = kwargs.pop(CONF_GOOGLE_ACTIONS, None) or GACTIONS_SCHEMA({}) # Cloud settings prefs = CloudPreferences(hass) await prefs.async_initialize() # Initialize Cloud websession = async_get_clientsession(hass) client = CloudClient(hass, prefs, websession, alexa_conf, google_conf) cloud = hass.data[DOMAIN] = Cloud(client, **kwargs) async def _shutdown(event): """Shutdown event.""" await cloud.stop() hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown) _remote_handle_prefs_updated(cloud) async def _service_handler(service: ServiceCall) -> None: """Handle service for cloud.""" if service.service == SERVICE_REMOTE_CONNECT: await prefs.async_update(remote_enabled=True) elif service.service == SERVICE_REMOTE_DISCONNECT: await prefs.async_update(remote_enabled=False) async_register_admin_service(hass, DOMAIN, SERVICE_REMOTE_CONNECT, _service_handler) async_register_admin_service(hass, DOMAIN, SERVICE_REMOTE_DISCONNECT, _service_handler) loaded = False async def _on_connect(): """Discover RemoteUI binary sensor.""" nonlocal loaded # Prevent multiple discovery if loaded: return loaded = True await async_load_platform(hass, Platform.BINARY_SENSOR, DOMAIN, {}, config) await async_load_platform(hass, Platform.STT, DOMAIN, {}, config) await async_load_platform(hass, Platform.TTS, DOMAIN, {}, config) async_dispatcher_send(hass, SIGNAL_CLOUD_CONNECTION_STATE, CloudConnectionState.CLOUD_CONNECTED) async def _on_disconnect(): """Handle cloud disconnect.""" async_dispatcher_send(hass, SIGNAL_CLOUD_CONNECTION_STATE, CloudConnectionState.CLOUD_DISCONNECTED) async def _on_initialized(): """Update preferences.""" await prefs.async_update(remote_domain=cloud.remote.instance_domain) cloud.iot.register_on_connect(_on_connect) cloud.iot.register_on_disconnect(_on_disconnect) cloud.register_on_initialized(_on_initialized) await cloud.initialize() await http_api.async_setup(hass) account_link.async_setup(hass) return True
async def async_setup_entry(hass, config_entry): # noqa: C901 """Set up SimpliSafe as config entry.""" hass.data[DOMAIN][DATA_LISTENER][config_entry.entry_id] = [] entry_updates = {} if not config_entry.unique_id: # If the config entry doesn't already have a unique ID, set one: entry_updates["unique_id"] = config_entry.data[CONF_USERNAME] if CONF_CODE in config_entry.data: # If an alarm code was provided as part of configuration.yaml, pop it out of # the config entry's data and move it to options: data = {**config_entry.data} entry_updates["data"] = data entry_updates["options"] = { **config_entry.options, CONF_CODE: data.pop(CONF_CODE), } if entry_updates: hass.config_entries.async_update_entry(config_entry, **entry_updates) _verify_domain_control = verify_domain_control(hass, DOMAIN) client_id = await async_get_client_id(hass) websession = aiohttp_client.async_get_clientsession(hass) try: api = await API.login_via_token(config_entry.data[CONF_TOKEN], client_id=client_id, session=websession) except InvalidCredentialsError: LOGGER.error("Invalid credentials provided") return False except SimplipyError as err: LOGGER.error("Config entry failed: %s", err) raise ConfigEntryNotReady from err _async_save_refresh_token(hass, config_entry, api.refresh_token) simplisafe = SimpliSafe(hass, api, config_entry) try: await simplisafe.async_init() except SimplipyError as err: raise ConfigEntryNotReady from err hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = simplisafe hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) @callback def verify_system_exists(coro): """Log an error if a service call uses an invalid system ID.""" async def decorator(call): """Decorate.""" system_id = int(call.data[ATTR_SYSTEM_ID]) if system_id not in simplisafe.systems: LOGGER.error("Unknown system ID in service call: %s", system_id) return await coro(call) return decorator @callback def v3_only(coro): """Log an error if the decorated coroutine is called with a v2 system.""" async def decorator(call): """Decorate.""" system = simplisafe.systems[int(call.data[ATTR_SYSTEM_ID])] if system.version != 3: LOGGER.error("Service only available on V3 systems") return await coro(call) return decorator @verify_system_exists @_verify_domain_control async def clear_notifications(call): """Clear all active notifications.""" system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] try: await system.clear_notifications() except SimplipyError as err: LOGGER.error("Error during service call: %s", err) return @verify_system_exists @_verify_domain_control async def remove_pin(call): """Remove a PIN.""" system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] try: await system.remove_pin(call.data[ATTR_PIN_LABEL_OR_VALUE]) except SimplipyError as err: LOGGER.error("Error during service call: %s", err) return @verify_system_exists @_verify_domain_control async def set_pin(call): """Set a PIN.""" system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] try: await system.set_pin(call.data[ATTR_PIN_LABEL], call.data[ATTR_PIN_VALUE]) except SimplipyError as err: LOGGER.error("Error during service call: %s", err) return @verify_system_exists @v3_only @_verify_domain_control async def set_system_properties(call): """Set one or more system parameters.""" system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] try: await system.set_properties({ prop: value for prop, value in call.data.items() if prop != ATTR_SYSTEM_ID }) except SimplipyError as err: LOGGER.error("Error during service call: %s", err) return for service, method, schema in [ ("clear_notifications", clear_notifications, None), ("remove_pin", remove_pin, SERVICE_REMOVE_PIN_SCHEMA), ("set_pin", set_pin, SERVICE_SET_PIN_SCHEMA), ( "set_system_properties", set_system_properties, SERVICE_SET_SYSTEM_PROPERTIES_SCHEMA, ), ]: async_register_admin_service(hass, DOMAIN, service, method, schema=schema) hass.data[DOMAIN][DATA_LISTENER][config_entry.entry_id].append( config_entry.add_update_listener(async_reload_entry)) return True
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Lovelace commands.""" mode = config[DOMAIN][CONF_MODE] yaml_resources = config[DOMAIN].get(CONF_RESOURCES) frontend.async_register_built_in_panel(hass, DOMAIN, config={"mode": mode}) async def reload_resources_service_handler(service_call: ServiceCall) -> None: """Reload yaml resources.""" try: conf = await async_hass_config_yaml(hass) except HomeAssistantError as err: _LOGGER.error(err) return integration = await async_get_integration(hass, DOMAIN) config = await async_process_component_config(hass, conf, integration) resource_collection = await create_yaml_resource_col( hass, config[DOMAIN].get(CONF_RESOURCES) ) hass.data[DOMAIN]["resources"] = resource_collection if mode == MODE_YAML: default_config = dashboard.LovelaceYAML(hass, None, None) resource_collection = await create_yaml_resource_col(hass, yaml_resources) async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD_RESOURCES, reload_resources_service_handler, schema=RESOURCE_RELOAD_SERVICE_SCHEMA, ) else: default_config = dashboard.LovelaceStorage(hass, None) if yaml_resources is not None: _LOGGER.warning( "Lovelace is running in storage mode. Define resources via user interface" ) resource_collection = resources.ResourceStorageCollection(hass, default_config) collection.StorageCollectionWebsocket( resource_collection, "lovelace/resources", "resource", RESOURCE_CREATE_FIELDS, RESOURCE_UPDATE_FIELDS, ).async_setup(hass, create_list=False) websocket_api.async_register_command(hass, websocket.websocket_lovelace_config) websocket_api.async_register_command(hass, websocket.websocket_lovelace_save_config) websocket_api.async_register_command( hass, websocket.websocket_lovelace_delete_config ) websocket_api.async_register_command(hass, websocket.websocket_lovelace_resources) websocket_api.async_register_command(hass, websocket.websocket_lovelace_dashboards) hass.data[DOMAIN] = { # We store a dictionary mapping url_path: config. None is the default. "mode": mode, "dashboards": {None: default_config}, "resources": resource_collection, "yaml_dashboards": config[DOMAIN].get(CONF_DASHBOARDS, {}), } if hass.config.safe_mode: return True async def storage_dashboard_changed(change_type, item_id, item): """Handle a storage dashboard change.""" url_path = item[CONF_URL_PATH] if change_type == collection.CHANGE_REMOVED: frontend.async_remove_panel(hass, url_path) await hass.data[DOMAIN]["dashboards"].pop(url_path).async_delete() return if change_type == collection.CHANGE_ADDED: existing = hass.data[DOMAIN]["dashboards"].get(url_path) if existing: _LOGGER.warning( "Cannot register panel at %s, it is already defined in %s", url_path, existing, ) return hass.data[DOMAIN]["dashboards"][url_path] = dashboard.LovelaceStorage( hass, item ) update = False else: hass.data[DOMAIN]["dashboards"][url_path].config = item update = True try: _register_panel(hass, url_path, MODE_STORAGE, item, update) except ValueError: _LOGGER.warning("Failed to %s panel %s from storage", change_type, url_path) # Process YAML dashboards for url_path, dashboard_conf in hass.data[DOMAIN]["yaml_dashboards"].items(): # For now always mode=yaml config = dashboard.LovelaceYAML(hass, url_path, dashboard_conf) hass.data[DOMAIN]["dashboards"][url_path] = config try: _register_panel(hass, url_path, MODE_YAML, dashboard_conf, False) except ValueError: _LOGGER.warning("Panel url path %s is not unique", url_path) # Process storage dashboards dashboards_collection = dashboard.DashboardsCollection(hass) dashboards_collection.async_add_listener(storage_dashboard_changed) await dashboards_collection.async_load() collection.StorageCollectionWebsocket( dashboards_collection, "lovelace/dashboards", "dashboard", STORAGE_DASHBOARD_CREATE_FIELDS, STORAGE_DASHBOARD_UPDATE_FIELDS, ).async_setup(hass, create_list=False) return True
SERVICE_KNX_SEND, knx_module.service_send_to_knx_bus, schema=SERVICE_KNX_SEND_SCHEMA, ) hass.services.async_register( DOMAIN, SERVICE_KNX_READ, knx_module.service_read_to_knx_bus, schema=SERVICE_KNX_READ_SCHEMA, ) async_register_admin_service( hass, DOMAIN, SERVICE_KNX_EVENT_REGISTER, knx_module.service_event_register_modify, schema=SERVICE_KNX_EVENT_REGISTER_SCHEMA, ) async_register_admin_service( hass, DOMAIN, SERVICE_KNX_EXPOSURE_REGISTER, knx_module.service_exposure_register_modify, schema=SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA, ) return True