class AlexaConfigStore: """A configuration store for Alexa.""" _STORAGE_VERSION = 1 _STORAGE_KEY = DOMAIN def __init__(self, hass): """Initialize a configuration store.""" self._data = None self._hass = hass self._store = Store(hass, self._STORAGE_VERSION, self._STORAGE_KEY) @property def authorized(self): """Return authorization status.""" return self._data[STORE_AUTHORIZED] @callback def set_authorized(self, authorized): """Set authorization status.""" if authorized != self._data[STORE_AUTHORIZED]: self._data[STORE_AUTHORIZED] = authorized self._store.async_delay_save(lambda: self._data, 1.0) async def async_load(self): """Load saved configuration from disk.""" if data := await self._store.async_load(): self._data = data else:
class GoogleConfigStore: """A configuration store for google assistant.""" _STORAGE_VERSION = 1 _STORAGE_KEY = DOMAIN def __init__(self, hass): """Initialize a configuration store.""" self._hass = hass self._store = Store(hass, self._STORAGE_VERSION, self._STORAGE_KEY) self._data = {STORE_AGENT_USER_IDS: {}} @property def agent_user_ids(self): """Return a list of connected agent user_ids.""" return self._data[STORE_AGENT_USER_IDS] @callback def add_agent_user_id(self, agent_user_id): """Add an agent user id to store.""" if agent_user_id not in self._data[STORE_AGENT_USER_IDS]: self._data[STORE_AGENT_USER_IDS][agent_user_id] = {} self._store.async_delay_save(lambda: self._data, 1.0) @callback def pop_agent_user_id(self, agent_user_id): """Remove agent user id from store.""" if agent_user_id in self._data[STORE_AGENT_USER_IDS]: self._data[STORE_AGENT_USER_IDS].pop(agent_user_id, None) self._store.async_delay_save(lambda: self._data, 1.0) async def async_load(self): """Store current configuration to disk.""" if data := await self._store.async_load(): self._data = data
class CacheStore: _STORAGE_VERSION = 1 _STORAGE_KEY = f'{DOMAIN}.cache' def __init__(self, hass): self._hass = hass self._store = Store(hass, self._STORAGE_VERSION, self._STORAGE_KEY) self._data = {STORE_CACHE_ATTRS: {}} def get_attr_value(self, entity_id: str, attr: str) -> Any | None: """Return a cached value of attribute for entity.""" if entity_id not in self._data[STORE_CACHE_ATTRS]: return None return self._data[STORE_CACHE_ATTRS][entity_id].get(attr) @callback def save_attr_value(self, entity_id: str, attr: str, value: Any): """Cache entity's attribute value to disk.""" if entity_id not in self._data[STORE_CACHE_ATTRS]: self._data[STORE_CACHE_ATTRS][entity_id] = {} has_changed = True else: has_changed = self._data[STORE_CACHE_ATTRS][entity_id][attr] != value self._data[STORE_CACHE_ATTRS][entity_id][attr] = value if has_changed: self._store.async_delay_save(lambda: self._data, 5.0) async def async_load(self): data = await self._store.async_load() if data: self._data = data
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Broadlink remote.""" host = config[CONF_HOST] mac_addr = config[CONF_MAC] timeout = config[CONF_TIMEOUT] name = config[CONF_NAME] unique_id = f"remote_{hexlify(mac_addr).decode('utf-8')}" if unique_id in hass.data.setdefault(DOMAIN, {}).setdefault(COMPONENT, []): _LOGGER.error("Duplicate: %s", unique_id) return hass.data[DOMAIN][COMPONENT].append(unique_id) api = broadlink.rm((host, DEFAULT_PORT), mac_addr, None) api.timeout = timeout code_storage = Store(hass, CODE_STORAGE_VERSION, CODE_STORAGE_KEY.format(unique_id)) flag_storage = Store(hass, FLAG_STORAGE_VERSION, FLAG_STORAGE_KEY.format(unique_id)) remote = BroadlinkRemote(name, unique_id, api, code_storage, flag_storage) connected, loaded = (False, False) try: connected, loaded = await asyncio.gather( hass.async_add_executor_job(api.auth), remote.async_load_storage_files() ) except socket.error: pass if not connected: hass.data[DOMAIN][COMPONENT].remove(unique_id) raise PlatformNotReady if not loaded: _LOGGER.error("Failed to set up %s", unique_id) hass.data[DOMAIN][COMPONENT].remove(unique_id) return async_add_entities([remote], False)
class EntityMapStorage: """ Holds a cache of entity structure data from a paired HomeKit device. HomeKit has a cacheable entity map that describes how an IP or BLE endpoint is structured. This object holds the latest copy of that data. An endpoint is made of accessories, services and characteristics. It is safe to cache this data until the c# discovery data changes. Caching this data means we can add HomeKit devices to HA immediately at start even if discovery hasn't seen them yet or they are out of range. It is also important for BLE devices - accessing the entity structure is very slow for these devices. """ def __init__(self, hass): """Create a new entity map store.""" self.hass = hass self.store = Store(hass, ENTITY_MAP_STORAGE_VERSION, ENTITY_MAP_STORAGE_KEY) self.storage_data = {} async def async_initialize(self): """Get the pairing cache data.""" raw_storage = await self.store.async_load() if not raw_storage: # There is no cached data about HomeKit devices yet return self.storage_data = raw_storage.get("pairings", {}) def get_map(self, homekit_id): """Get a pairing cache item.""" return self.storage_data.get(homekit_id) def async_create_or_update_map(self, homekit_id, config_num, accessories): """Create a new pairing cache.""" data = {"config_num": config_num, "accessories": accessories} self.storage_data[homekit_id] = data self._async_schedule_save() return data def async_delete_map(self, homekit_id): """Delete pairing cache.""" if homekit_id not in self.storage_data: return self.storage_data.pop(homekit_id) self._async_schedule_save() @callback def _async_schedule_save(self): """Schedule saving the entity map cache.""" self.store.async_delay_save(self._data_to_save, ENTITY_MAP_SAVE_DELAY) @callback def _data_to_save(self): """Return data of entity map to store in a file.""" return {"pairings": self.storage_data}
def __init__(self, hass, config_entry, mqtt_prefix): self.hass = hass self.config_entry = config_entry self.mqtt_prefix = mqtt_prefix self.async_add_devices = {} self.devices = {} self.store = Store(hass, STORAGE_VERSION, STORAGE_KEY) self._history = []
async def async_setup_entry(hass, config_entry, async_add_entities): """Set up a Broadlink remote.""" device = hass.data[DOMAIN].devices[config_entry.entry_id] remote = BroadlinkRemote( device, Store(hass, CODE_STORAGE_VERSION, f"broadlink_remote_{device.unique_id}_codes"), Store(hass, FLAG_STORAGE_VERSION, f"broadlink_remote_{device.unique_id}_flags"), ) async_add_entities([remote], False)
def __init__(self, hass: HomeAssistant): """Create a new entity map store.""" self.hass = hass self.store = Store(hass, AID_MANAGER_STORAGE_VERSION, AID_MANAGER_STORAGE_KEY) self.allocations = {} self.allocated_aids = set() self._entity_registry = None
def __init__(self, hass: HomeAssistantType, component: EntityComponent, lock_node_id, config_tags, config_ozw_log): """Initialize lock history storage.""" self.hass = hass self.component = component self.node_id = lock_node_id self.config_tags = config_tags self.store = Store(hass, STORAGE_VERSION, STORAGE_KEY) self.ozw_log = config_ozw_log self.ozw_file_offset = 0 self.ozw_file_inode = None self._used_tags = {} # indexed by tag index number self._history = []
async def async_initialize(self): """Load the latest AID data.""" self._entity_registry = ( await self.hass.helpers.entity_registry.async_get_registry()) aidstore = get_aid_storage_filename_for_entry_id(self._entry) self.store = Store(self.hass, AID_MANAGER_STORAGE_VERSION, aidstore) raw_storage = await self.store.async_load() if not raw_storage: # There is no data about aid allocations yet return self.allocations = raw_storage.get(ALLOCATIONS_KEY, {}) self.allocated_aids = set(self.allocations.values())
async def async_setup_entry(hass, config_entry, async_add_entities): """Set up a Broadlink remote.""" device = hass.data[DOMAIN].devices[config_entry.entry_id] remote = BroadlinkRemote( device, Store(hass, CODE_STORAGE_VERSION, f"broadlink_remote_{device.unique_id}_codes"), Store(hass, FLAG_STORAGE_VERSION, f"broadlink_remote_{device.unique_id}_flags"), ) loaded = await remote.async_load_storage_files() if not loaded: _LOGGER.error("Failed to create '%s Remote' entity: Storage error", device.name) return async_add_entities([remote], False)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Initialize the trace integration.""" hass.data[DATA_TRACE] = {} websocket_api.async_setup(hass) store = Store(hass, STORAGE_VERSION, STORAGE_KEY, encoder=ExtendedJSONEncoder) hass.data[DATA_TRACE_STORE] = store async def _async_store_traces_at_stop(*_) -> None: """Save traces to storage.""" _LOGGER.debug("Storing traces") try: await store.async_save({ key: list(traces.values()) for key, traces in hass.data[DATA_TRACE].items() }) except HomeAssistantError as exc: _LOGGER.error("Error storing traces", exc_info=exc) # Store traces when stopping hass hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_store_traces_at_stop) return True
async def _update_person(call: ServiceCall) -> bool: """Update the settings for a person.""" hass = get_base().hass store = Store(hass, 1, f"{DOMAIN}.{CONF_PERSONS}") data: Optional[PersonSettingsRegistry] = await store.async_load() if data is None: data = {} data[CONF_UPDATED] = False person = EnhancedPerson(call.data.get(CONF_ID)) await _update_key_value(data, call, person.id, ATTR_NAME, person.original_name) await _update_key_value(data, call, person.id, CONF_SORT_ORDER, DEFAULT_SORT_ORDER) await _update_key_value(data, call, person.id, CONF_VISIBLE, True) if await _store_data(store, data, person.id): hass.bus.fire( EVENT_PERSON_SETTINGS_CHANGED, { CONF_ACTION: CONF_UPDATE, CONF_ID: person.id }, ) return True return False
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up an input boolean.""" 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 = collection.IDManager() yaml_collection = collection.YamlCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager) collection.sync_entity_lifecycle(hass, DOMAIN, DOMAIN, component, yaml_collection, InputBoolean.from_yaml) storage_collection = InputBooleanStorageCollection( Store(hass, STORAGE_VERSION, STORAGE_KEY), logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) collection.sync_entity_lifecycle(hass, DOMAIN, DOMAIN, component, storage_collection, InputBoolean) await yaml_collection.async_load([{ CONF_ID: id_, **(conf or {}) } for id_, conf in config.get(DOMAIN, {}).items()]) 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 input booleans 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_ID: id_, **(conf or {}) } for id_, conf in conf.get(DOMAIN, {}).items()]) homeassistant.helpers.service.async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=RELOAD_SERVICE_SCHEMA, ) component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on") component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off") component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle") return True
def __init__(self, hass): """Initialize cloud prefs.""" self._hass = hass self._store = Store(hass, STORAGE_VERSION, STORAGE_KEY) self._prefs = None self._listeners = [] self.last_updated: set[str] = set()
async def async_load_from_store(hass, key): """Load the retained data from store and return de-serialized data.""" store = Store(hass, STORAGE_VERSION, f"hacs.{key}", encoder=JSONEncoder) restored = await store.async_load() if restored is None: return {} return restored
async def _update_entity(call: ServiceCall) -> bool: """Update the settings for an entity.""" hass = get_base().hass store = Store(hass, 1, f"{DOMAIN}.{CONF_ENTITIES}") data: Optional[EntitySettingsRegistry] = await store.async_load() if data is None: data = {} data[CONF_UPDATED] = False entity_id: str = call.data.get(CONF_ENTITY_ID) entity = _get_entity_by_id(entity_id) await _update_key_value(data, call, entity_id, CONF_AREA_NAME, entity[CONF_ORIGINAL_AREA_ID]) await _update_key_value(data, call, entity_id, CONF_SORT_ORDER, DEFAULT_SORT_ORDER) await _update_key_value(data, call, entity_id, CONF_TYPE, [entity[CONF_ORIGINAL_TYPE], None]) await _update_key_value(data, call, entity_id, CONF_VISIBLE, True) if await _store_data(store, data, entity_id): hass.bus.fire( EVENT_ENTITY_SETTINGS_CHANGED, { CONF_ACTION: CONF_UPDATE, ATTR_AREA_ID: entity_id }, ) return True return False
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: if config_entry.data: hass.config_entries.async_update_entry(config_entry, data={}, options=config_entry.data) store = Store(hass, STORAGE_VERSION, "{}.{}".format(DOMAIN, config_entry.entry_id), encoder=JSONEncoder) devices = await store.async_load() if devices is None: devices = {} client = LuciData(hass, config_entry, store, devices) hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = client async def async_close(event): await client.save_to_store() await client.api.logout() hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_close) await _init_services(hass, store) if not await client.async_setup(): return False return True
async def internal_cache_setup( hass: HomeAssistant, entry: ConfigEntry, devices: list = None ): await asyncio.gather(*[ hass.config_entries.async_forward_entry_setup(entry, domain) for domain in PLATFORMS ]) if devices is None: store = Store(hass, 1, f"{DOMAIN}/{entry.data['username']}.json") devices = await store.async_load() if devices: # 16 devices loaded from the Cloud Server _LOGGER.debug(f"{len(devices)} devices loaded from Cache") registry: XRegistry = hass.data[DOMAIN][entry.entry_id] if devices: devices = internal_unique_devices(entry.entry_id, devices) registry.setup_devices(devices) mode = entry.options.get(CONF_MODE, "auto") if mode != "local" and registry.cloud.auth: registry.cloud.start() if mode != "cloud": zc = await zeroconf.async_get_instance(hass) registry.local.start(zc) _LOGGER.debug(f"{mode.upper()} mode start") if not entry.update_listeners: entry.add_update_listener(async_update_options) entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, registry.stop) )
async def hass_areas() -> List[AreaSettings]: """A dictionary list for the HA area registry used for this integrations domain data.""" hass = get_base().hass areas: List[AreaSettings] = [] # make as an array so it can be sorted store = Store(hass, 1, f"{DOMAIN}.{CONF_AREAS}") data: Optional[AreaSettingsRegistry] = await store.async_load() if data is None: data = {} # Sorted by original name because this is what is needed for the picker area_registry: AreaRegistry = hass.data["area_registry"] areas_sorted: Iterable[AreaEntry] = sorted( area_registry.async_list_areas(), key=lambda entry: entry.name) for area in areas_sorted: area_data = data.get(area.id, {}) area_item: AreaSettings = { ATTR_ID: area.id, ATTR_NAME: area_data.get(CONF_NAME, area.name), CONF_ICON: area_data.get(CONF_ICON, DEFAULT_ROOM_ICON), CONF_ORIGINAL_NAME: area.name, CONF_SORT_ORDER: area_data.get(CONF_SORT_ORDER, DEFAULT_SORT_ORDER), CONF_VISIBLE: area_data.get(CONF_VISIBLE, True), } areas.append(area_item) return areas
async def _save_to_store(self, data): """Generate dynamic data to store and save it to the filesystem.""" store = Store(self.hass, STORAGE_VERSION, f"smartthings.{self._name}", encoder=JSONEncoder) await store.async_save(data)
async def _update_area(call: ServiceCall) -> bool: """Update the settings for an area.""" hass = get_base().hass store = Store(hass, 1, f"{DOMAIN}.{CONF_AREAS}") data: Optional[AreaSettingsRegistry] = await store.async_load() if data is None: data = {} data[CONF_UPDATED] = False area_name: str = call.data.get(CONF_AREA_NAME) area_id = await _get_area_id_by_name(area_name) await _update_key_value(data, call, area_id, ATTR_NAME, area_name) await _update_key_value(data, call, area_id, CONF_ICON, DEFAULT_ROOM_ICON) await _update_key_value(data, call, area_id, CONF_SORT_ORDER, DEFAULT_SORT_ORDER) await _update_key_value(data, call, area_id, CONF_VISIBLE, True) if await _store_data(store, data, area_id): hass.bus.fire( EVENT_AREA_SETTINGS_CHANGED, { CONF_ACTION: CONF_UPDATE, ATTR_AREA_ID: area_id }, ) return True return False
async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Set up an input boolean.""" component = EntityComponent(_LOGGER, DOMAIN, hass) id_manager = collection.IDManager() yaml_collection = collection.YamlCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager ) collection.attach_entity_component_collection( component, yaml_collection, lambda conf: InputBoolean(conf, from_yaml=True) ) storage_collection = InputBooleanStorageCollection( Store(hass, STORAGE_VERSION, STORAGE_KEY), logging.getLogger(f"{__name__}_storage_collection"), id_manager, ) collection.attach_entity_component_collection( component, storage_collection, InputBoolean ) await yaml_collection.async_load( [{CONF_ID: id_, **(conf or {})} for id_, conf in config.get(DOMAIN, {}).items()] ) await storage_collection.async_load() collection.StorageCollectionWebsocket( storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS ).async_setup(hass) collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, yaml_collection) collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, storage_collection) async def reload_service_handler(service_call: ServiceCallType) -> None: """Remove all input booleans 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_ID: id_, **(conf or {})} for id_, conf in conf.get(DOMAIN, {}).items() ] ) homeassistant.helpers.service.async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=RELOAD_SERVICE_SCHEMA, ) component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on") component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off") component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle") return True
async def async_save_to_store(hass, key, data): """Generate dynamic data to store and save it to the filesystem.""" from homeassistant.helpers.storage import Store key = key if "/" in key else f"hacs.{key}" store = Store(hass, VERSION_STORAGE, key, encoder=JSONEncoder) await store.async_save(data)
async def unload_smartapp_endpoint(hass: HomeAssistant): """Tear down the component configuration.""" if DOMAIN not in hass.data: return # Remove the cloudhook if it was created cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL] if cloudhook_url and cloud.async_is_logged_in(hass): await cloud.async_delete_cloudhook(hass, hass.data[DOMAIN][CONF_WEBHOOK_ID]) # Remove cloudhook from storage store = Store(hass, STORAGE_VERSION, STORAGE_KEY) await store.async_save({ CONF_INSTANCE_ID: hass.data[DOMAIN][CONF_INSTANCE_ID], CONF_WEBHOOK_ID: hass.data[DOMAIN][CONF_WEBHOOK_ID], CONF_CLOUDHOOK_URL: None, }) _LOGGER.debug("Cloudhook '%s' was removed", cloudhook_url) # Remove the webhook webhook.async_unregister(hass, hass.data[DOMAIN][CONF_WEBHOOK_ID]) # Disconnect all brokers for broker in hass.data[DOMAIN][DATA_BROKERS].values(): broker.disconnect() # Remove all handlers from manager hass.data[DOMAIN][DATA_MANAGER].dispatcher.disconnect_all() # Remove the component data hass.data.pop(DOMAIN)
async def async_step_verification_code(self, user_input=None, errors=None): """Ask the verification code to the user.""" if errors is None: errors = {} if user_input is None: return await self._show_verification_code_form(user_input, errors) self._verification_code = user_input[CONF_VERIFICATION_CODE] try: if self.api.requires_2fa: if not await self.hass.async_add_executor_job( self.api.validate_2fa_code, self._verification_code ): raise PyiCloudException("The code you entered is not valid.") else: if not await self.hass.async_add_executor_job( self.api.validate_verification_code, self._trusted_device, self._verification_code, ): raise PyiCloudException("The code you entered is not valid.") except PyiCloudException as error: # Reset to the initial 2FA state to allow the user to retry _LOGGER.error("Failed to verify verification code: %s", error) self._trusted_device = None self._verification_code = None errors["base"] = "validate_verification_code" if self.api.requires_2fa: try: self.api = await self.hass.async_add_executor_job( PyiCloudService, self._username, self._password, Store(self.hass, STORAGE_VERSION, STORAGE_KEY).path, True, None, self._with_family, ) return await self.async_step_verification_code(None, errors) except PyiCloudFailedLoginException as error_login: _LOGGER.error("Error logging into iCloud service: %s", error_login) self.api = None errors = {CONF_PASSWORD: "******"} return self._show_setup_form(user_input, errors, "user") else: return await self.async_step_trusted_device(None, errors) return await self.async_step_user( { CONF_USERNAME: self._username, CONF_PASSWORD: self._password, CONF_WITH_FAMILY: self._with_family, CONF_MAX_INTERVAL: self._max_interval, CONF_GPS_ACCURACY_THRESHOLD: self._gps_accuracy_threshold, } )
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: HomeAssistant, config: ConfigType) -> bool: """Set up an input slider.""" component = EntityComponent(_LOGGER, DOMAIN, hass) id_manager = collection.IDManager() yaml_collection = collection.YamlCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager) collection.sync_entity_lifecycle(hass, DOMAIN, DOMAIN, component, yaml_collection, InputNumber.from_yaml) storage_collection = NumberStorageCollection( Store(hass, STORAGE_VERSION, STORAGE_KEY), logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) collection.sync_entity_lifecycle(hass, DOMAIN, DOMAIN, component, storage_collection, InputNumber) await yaml_collection.async_load([{ CONF_ID: id_, **(conf or {}) } for id_, conf in config.get(DOMAIN, {}).items()]) 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: """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_, **conf } for id_, conf in conf.get(DOMAIN, {}).items()]) homeassistant.helpers.service.async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=RELOAD_SERVICE_SCHEMA, ) component.async_register_entity_service( SERVICE_SET_VALUE, {vol.Required(ATTR_VALUE): vol.Coerce(float)}, "async_set_value", ) component.async_register_entity_service(SERVICE_INCREMENT, {}, "async_increment") component.async_register_entity_service(SERVICE_DECREMENT, {}, "async_decrement") return True
def get_or_create_store(self, hass: HomeAssistant, entry: ConfigEntry) -> Store: """Get or create a Store instance for the given config entry.""" return self._stores.setdefault( entry.entry_id, Store( hass, STORAGE_VERSION, f"esphome.{entry.entry_id}", encoder=JSONEncoder ), )
async def async_remove_store(hass, key): """Remove a store element that should no longer be used""" from homeassistant.helpers.storage import Store if "/" not in key: return store = Store(hass, VERSION_STORAGE, key, encoder=JSONEncoder) await store.async_remove()
def __init__(self, hass): """Create a new entity map store.""" self.hass = hass self.store = Store( hass, ENTITY_MAP_STORAGE_VERSION, ENTITY_MAP_STORAGE_KEY ) self.storage_data = {}
def __init__(self, hass: HomeAssistantType, component: EntityComponent, config_persons): """Initialize person storage.""" self.hass = hass self.component = component self.store = Store(hass, STORAGE_VERSION, STORAGE_KEY) self.storage_data = None config_data = self.config_data = OrderedDict() for conf in config_persons: person_id = conf[CONF_ID] if person_id in config_data: _LOGGER.error( "Found config user with duplicate ID: %s", person_id) continue config_data[person_id] = conf
class EntityMapStorage: """ Holds a cache of entity structure data from a paired HomeKit device. HomeKit has a cacheable entity map that describes how an IP or BLE endpoint is structured. This object holds the latest copy of that data. An endpoint is made of accessories, services and characteristics. It is safe to cache this data until the c# discovery data changes. Caching this data means we can add HomeKit devices to HA immediately at start even if discovery hasn't seen them yet or they are out of range. It is also important for BLE devices - accessing the entity structure is very slow for these devices. """ def __init__(self, hass): """Create a new entity map store.""" self.hass = hass self.store = Store( hass, ENTITY_MAP_STORAGE_VERSION, ENTITY_MAP_STORAGE_KEY ) self.storage_data = {} async def async_initialize(self): """Get the pairing cache data.""" raw_storage = await self.store.async_load() if not raw_storage: # There is no cached data about HomeKit devices yet return self.storage_data = raw_storage.get('pairings', {}) def get_map(self, homekit_id): """Get a pairing cache item.""" return self.storage_data.get(homekit_id) def async_create_or_update_map(self, homekit_id, config_num, accessories): """Create a new pairing cache.""" data = { 'config_num': config_num, 'accessories': accessories, } self.storage_data[homekit_id] = data self._async_schedule_save() return data def async_delete_map(self, homekit_id): """Delete pairing cache.""" if homekit_id not in self.storage_data: return self.storage_data.pop(homekit_id) self._async_schedule_save() @callback def _async_schedule_save(self): """Schedule saving the entity map cache.""" self.store.async_delay_save(self._data_to_save, ENTITY_MAP_SAVE_DELAY) @callback def _data_to_save(self): """Return data of entity map to store in a file.""" return { 'pairings': self.storage_data, }
class PersonManager: """Manage person data.""" def __init__(self, hass: HomeAssistantType, component: EntityComponent, config_persons): """Initialize person storage.""" self.hass = hass self.component = component self.store = Store(hass, STORAGE_VERSION, STORAGE_KEY) self.storage_data = None config_data = self.config_data = OrderedDict() for conf in config_persons: person_id = conf[CONF_ID] if person_id in config_data: _LOGGER.error( "Found config user with duplicate ID: %s", person_id) continue config_data[person_id] = conf @property def storage_persons(self): """Iterate over persons stored in storage.""" return list(self.storage_data.values()) @property def config_persons(self): """Iterate over persons stored in config.""" return list(self.config_data.values()) async def async_initialize(self): """Get the person data.""" raw_storage = await self.store.async_load() if raw_storage is None: raw_storage = { 'persons': [] } storage_data = self.storage_data = OrderedDict() for person in raw_storage['persons']: storage_data[person[CONF_ID]] = person entities = [] seen_users = set() for person_conf in self.config_data.values(): person_id = person_conf[CONF_ID] user_id = person_conf.get(CONF_USER_ID) if user_id is not None: if await self.hass.auth.async_get_user(user_id) is None: _LOGGER.error( "Invalid user_id detected for person %s", person_id) continue if user_id in seen_users: _LOGGER.error( "Duplicate user_id %s detected for person %s", user_id, person_id) continue seen_users.add(user_id) entities.append(Person(person_conf, False)) # To make sure IDs don't overlap between config/storage seen_persons = set(self.config_data) for person_conf in storage_data.values(): person_id = person_conf[CONF_ID] user_id = person_conf[CONF_USER_ID] if person_id in seen_persons: _LOGGER.error( "Skipping adding person from storage with same ID as" " configuration.yaml entry: %s", person_id) continue if user_id is not None and user_id in seen_users: _LOGGER.error( "Duplicate user_id %s detected for person %s", user_id, person_id) continue # To make sure all users have just 1 person linked. seen_users.add(user_id) entities.append(Person(person_conf, True)) if entities: await self.component.async_add_entities(entities) self.hass.bus.async_listen(EVENT_USER_REMOVED, self._user_removed) async def async_create_person( self, *, name, device_trackers=None, user_id=None): """Create a new person.""" if not name: raise ValueError("Name is required") if user_id is not None: await self._validate_user_id(user_id) person = { CONF_ID: uuid.uuid4().hex, CONF_NAME: name, CONF_USER_ID: user_id, CONF_DEVICE_TRACKERS: device_trackers or [], } self.storage_data[person[CONF_ID]] = person self._async_schedule_save() await self.component.async_add_entities([Person(person, True)]) return person async def async_update_person(self, person_id, *, name=_UNDEF, device_trackers=_UNDEF, user_id=_UNDEF): """Update person.""" current = self.storage_data.get(person_id) if current is None: raise ValueError("Invalid person specified.") changes = { key: value for key, value in ( (CONF_NAME, name), (CONF_DEVICE_TRACKERS, device_trackers), (CONF_USER_ID, user_id) ) if value is not _UNDEF and current[key] != value } if CONF_USER_ID in changes and user_id is not None: await self._validate_user_id(user_id) self.storage_data[person_id].update(changes) self._async_schedule_save() for entity in self.component.entities: if entity.unique_id == person_id: entity.person_updated() break return self.storage_data[person_id] async def async_delete_person(self, person_id): """Delete person.""" if person_id not in self.storage_data: raise ValueError("Invalid person specified.") self.storage_data.pop(person_id) self._async_schedule_save() ent_reg = await self.hass.helpers.entity_registry.async_get_registry() for entity in self.component.entities: if entity.unique_id == person_id: await entity.async_remove() ent_reg.async_remove(entity.entity_id) break @callback def _async_schedule_save(self) -> None: """Schedule saving the area registry.""" self.store.async_delay_save(self._data_to_save, SAVE_DELAY) @callback def _data_to_save(self) -> dict: """Return data of area registry to store in a file.""" return { 'persons': list(self.storage_data.values()) } async def _validate_user_id(self, user_id): """Validate the used user_id.""" if await self.hass.auth.async_get_user(user_id) is None: raise ValueError("User does not exist") if any(person for person in chain(self.storage_data.values(), self.config_data.values()) if person.get(CONF_USER_ID) == user_id): raise ValueError("User already taken") async def _user_removed(self, event: Event): """Handle event that a person is removed.""" user_id = event.data['user_id'] for person in self.storage_data.values(): if person[CONF_USER_ID] == user_id: await self.async_update_person( person_id=person[CONF_ID], user_id=None )