def log_exception(ex, domain, config, hass=None): """Generate log exception for config validation.""" message = 'Invalid config for [{}]: '.format(domain) if hass is not None: _PERSISTENT_VALIDATION.add(domain) message = ('The following platforms contain invalid configuration: ' + ', '.join(list(_PERSISTENT_VALIDATION)) + ' (please check your configuration)') persistent_notification.create(hass, message, 'Invalid config', 'invalid_config') if 'extra keys not allowed' in ex.error_message: message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\ .format(ex.path[-1], domain, domain, '->'.join('%s' % m for m in ex.path)) else: message += '{}.'.format(humanize_error(config, ex)) if hasattr(config, '__line__'): message += " (See {}:{})".format(config.__config_file__, config.__line__ or '?') if domain != 'homeassistant': message += (' Please check the docs at ' 'https://home-assistant.io/components/{}/'.format(domain)) _LOGGER.error(message)
def check_auth(self): rdt = self.request_miot_api('v2/device/blt_get_beaconkey', { 'did': 'blt.0.14cj9o6b4eg00', 'pdid': 1, }) or {} eno = rdt.get('code', 0) if eno == 3: # auth err persistent_notification.create( self.hass, f'Xiaomi cloud: {self.user_id} auth failed, ' 'Please update option for this integration to refresh token.\n' f'小米账号:{self.user_id} 登陆失效,请重新保存集成选项以更新登陆信息。', 'Xiaomi Miot Warning', f'xiaomi-miot-auth-warning-{self.user_id}', ) _LOGGER.error( 'Xiaomi cloud: %s auth failed, Please update option for this integration to refresh token.', self.user_id, ) self.user_id = None self.service_token = None self.ssecurity = None if self.login(): return True _LOGGER.warning('Retry login xiaomi cloud failed: %s', self.username) return False
def test_create_notification_id(self): """Ensure overwrites existing notification with same id.""" notifications = self.hass.data[pn.DOMAIN]["notifications"] assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 assert len(notifications) == 0 pn.create(self.hass, "test", notification_id="Beer 2") self.hass.block_till_done() assert len(self.hass.states.entity_ids()) == 1 assert len(notifications) == 1 entity_id = "persistent_notification.beer_2" state = self.hass.states.get(entity_id) assert state.attributes.get("message") == "test" notification = notifications.get(entity_id) assert notification["message"] == "test" assert notification["title"] is None pn.create(self.hass, "test 2", notification_id="Beer 2") self.hass.block_till_done() # We should have overwritten old one assert len(self.hass.states.entity_ids()) == 1 state = self.hass.states.get(entity_id) assert state.attributes.get("message") == "test 2" notification = notifications.get(entity_id) assert notification["message"] == "test 2" notifications.clear()
def _update_data(self) -> PowerwallData: """Fetch data from API endpoint.""" _LOGGER.debug("Updating data") for attempt in range(2): try: if attempt == 1: self._recreate_powerwall_login() data = _fetch_powerwall_data(self.power_wall) except PowerwallUnreachableError as err: raise UpdateFailed( "Unable to fetch data from powerwall") from err except MissingAttributeError as err: _LOGGER.error("The powerwall api has changed: %s", str(err)) # The error might include some important information about what exactly changed. persistent_notification.create(self.hass, API_CHANGED_ERROR_BODY, API_CHANGED_TITLE) self.runtime_data[POWERWALL_API_CHANGED] = True raise UpdateFailed("The powerwall api has changed") from err except AccessDeniedError as err: if attempt == 1: # failed to authenticate => the credentials must be wrong raise ConfigEntryAuthFailed from err if self.password is None: raise ConfigEntryAuthFailed from err _LOGGER.debug("Access denied, trying to reauthenticate") # there is still an attempt left to authenticate, so we continue in the loop except APIError as err: raise UpdateFailed( f"Updated failed due to {err}, will retry") from err else: return data raise RuntimeError("unreachable")
def test_create(self): """Test creating notification without title or notification id.""" notifications = self.hass.data[pn.DOMAIN]["notifications"] assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 assert len(notifications) == 0 pn.create(self.hass, "Hello World {{ 1 + 1 }}", title="{{ 1 + 1 }} beers") self.hass.block_till_done() entity_ids = self.hass.states.entity_ids(pn.DOMAIN) assert len(entity_ids) == 1 assert len(notifications) == 1 state = self.hass.states.get(entity_ids[0]) assert state.state == pn.STATE assert state.attributes.get("message") == "Hello World 2" assert state.attributes.get("title") == "2 beers" notification = notifications.get(entity_ids[0]) assert notification["status"] == pn.STATUS_UNREAD assert notification["message"] == "Hello World 2" assert notification["title"] == "2 beers" assert notification["created_at"] is not None notifications.clear()
def setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Melnor RainCloud component.""" conf = config[DOMAIN] username = conf.get(CONF_USERNAME) password = conf.get(CONF_PASSWORD) scan_interval = conf.get(CONF_SCAN_INTERVAL) try: raincloud = RainCloudy(username=username, password=password) if not raincloud.is_connected: raise HTTPError hass.data[DATA_RAINCLOUD] = RainCloudHub(raincloud) except (ConnectTimeout, HTTPError) as ex: _LOGGER.error("Unable to connect to Rain Cloud service: %s", str(ex)) persistent_notification.create( hass, f"Error: {ex}<br />" "You will need to restart hass after fixing.", title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID, ) return False def hub_refresh(event_time): """Call Raincloud hub to refresh information.""" _LOGGER.debug("Updating RainCloud Hub component") hass.data[DATA_RAINCLOUD].data.update() dispatcher_send(hass, SIGNAL_UPDATE_RAINCLOUD) # Call the Raincloud API to refresh updates track_time_interval(hass, hub_refresh, scan_interval) return True
def device_need_refresh(**kwargs): device = kwargs['device'] persistent_notification.create(hass, ('The ZiGate device {} needs some' ' refresh (missing important' ' information)').format(device.addr), title='ZiGate')
def taskStatus(hass, task, command): """Check status of running task.""" from time import sleep from homeassistant.components import persistent_notification # wait while task is in progress state = vim.TaskInfo.State while task.info.state not in [state.success, state.error]: if task.info.progress is not None: _LOGGER.debug("Task %s progress %s", task.info.eventChainId, task.info.progress) sleep(2) # output task status once complete if task.info.state == "success": _LOGGER.info("Sending command to '%s' complete", task.info.entityName) message = "Complete - " + command persistent_notification.create(hass, message, "ESXi Stats") if task.info.state == "error": _LOGGER.info("Sending command to '%s' failed", task.info.entityName) _LOGGER.info(task.info.error.msg) message = "Failed - " + command + "\n\n" message += task.info.error.msg persistent_notification.create(hass, message, "ESXi Stats")
def setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up Lupusec component.""" conf = config[DOMAIN] username = conf[CONF_USERNAME] password = conf[CONF_PASSWORD] ip_address = conf[CONF_IP_ADDRESS] name = conf.get(CONF_NAME) try: hass.data[DOMAIN] = LupusecSystem(username, password, ip_address, name) except LupusecException as ex: _LOGGER.error(ex) persistent_notification.create( hass, f"Error: {ex}<br />You will need to restart hass after fixing.", title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID, ) return False for platform in LUPUSEC_PLATFORMS: discovery.load_platform(hass, platform, DOMAIN, {}, config) return True
def _update_data(self) -> PowerwallData: """Fetch data from API endpoint.""" _LOGGER.debug("Updating data") for attempt in range(2): try: if attempt == 1: self._recreate_powerwall_login() data = _fetch_powerwall_data(self.power_wall) except PowerwallUnreachableError as err: raise UpdateFailed( "Unable to fetch data from powerwall") from err except MissingAttributeError as err: _LOGGER.error("The powerwall api has changed: %s", str(err)) # The error might include some important information about what exactly changed. persistent_notification.create(self.hass, API_CHANGED_ERROR_BODY, API_CHANGED_TITLE) self.runtime_data[POWERWALL_API_CHANGED] = True raise UpdateFailed("The powerwall api has changed") from err except AccessDeniedError as err: if attempt == 1: self._increment_failed_logins() raise ConfigEntryAuthFailed from err if self.password is None: raise ConfigEntryAuthFailed from err raise UpdateFailed( f"Login attempt {self.login_failed_count}/{MAX_LOGIN_FAILURES} failed, will retry: {err}" ) from err except APIError as err: raise UpdateFailed( f"Updated failed due to {err}, will retry") from err else: self._clear_failed_logins() return data raise RuntimeError("unreachable")
def setup(hass: HomeAssistant, config: ConfigType) -> bool: """Establish connection to MAX! Cube.""" if DATA_KEY not in hass.data: hass.data[DATA_KEY] = {} connection_failed = 0 gateways = config[DOMAIN][CONF_GATEWAYS] for gateway in gateways: host = gateway[CONF_HOST] port = gateway[CONF_PORT] scan_interval = gateway[CONF_SCAN_INTERVAL].total_seconds() try: cube = MaxCube(host, port, now=now) hass.data[DATA_KEY][host] = MaxCubeHandle(cube, scan_interval) except timeout as ex: _LOGGER.error("Unable to connect to Max!Cube gateway: %s", str(ex)) persistent_notification.create( hass, f"Error: {ex}<br />You will need to restart Home Assistant after fixing.", title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID, ) connection_failed += 1 if connection_failed >= len(gateways): return False load_platform(hass, Platform.CLIMATE, DOMAIN, {}, config) load_platform(hass, Platform.BINARY_SENSOR, DOMAIN, {}, config) return True
async def check_xiaomi_account(hass, user_input, errors, renew_devices=False): dvs = [] mic = None try: mic = await MiotCloud.from_token(hass, user_input, login=False) await mic.async_login() if not await mic.async_check_auth(False): raise MiCloudException('Login failed') await mic.async_stored_auth(mic.user_id, save=True) user_input['xiaomi_cloud'] = mic dvs = await mic.async_get_devices(renew=renew_devices) or [] if renew_devices: await MiotSpec.async_get_model_type(hass, 'xiaomi.miot.auto', use_remote=True) except (MiCloudException, MiCloudAccessDenied, Exception) as exc: err = f'{exc}' errors['base'] = 'cannot_login' if isinstance(exc, MiCloudAccessDenied) and mic: if url := mic.attrs.pop('notificationUrl'): err = f'The login of Xiaomi account needs security verification. [Click here]({url}) to continue!\n' \ f'本次登陆小米账号需要安全验证,[点击这里]({url})继续!' persistent_notification.create( hass, err, f'Login to Xiaomi: {mic.username}', f'{DOMAIN}-login', ) if isinstance(exc, requests.exceptions.ConnectionError): errors['base'] = 'cannot_reach' elif 'ZoneInfoNotFoundError' in err: errors['base'] = 'tzinfo_error' hass.data[DOMAIN]['placeholders'] = {'tip': f'⚠️ {err}'} _LOGGER.error('Setup xiaomi cloud for user: %s failed: %s', mic.username, exc)
def _reconnect(self): """Reconnect on a failure.""" self._fails += 1 if self._fails > MAX_FAILS: _LOGGER.error( "Failed to refresh login credentials. Thread stopped") persistent_notification.create( self.hass, "Error:<br/>Connection to waterfurnace website failed " "the maximum number of times. Thread has stopped", title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID, ) self._shutdown = True return # sleep first before the reconnect attempt _LOGGER.debug("Sleeping for fail # %s", self._fails) time.sleep(self._fails * ERROR_INTERVAL.total_seconds()) try: self.client.login() self.data = self.client.read() except WFException: _LOGGER.exception("Failed to reconnect attempt %s", self._fails) else: _LOGGER.debug("Reconnected to furnace") self._fails = 0
def test_create(self): """Test creating notification without title or notification id.""" notifications = self.hass.data[pn.DOMAIN]['notifications'] assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 assert len(notifications) == 0 pn.create(self.hass, 'Hello World {{ 1 + 1 }}', title='{{ 1 + 1 }} beers') self.hass.block_till_done() entity_ids = self.hass.states.entity_ids(pn.DOMAIN) assert len(entity_ids) == 1 assert len(notifications) == 1 state = self.hass.states.get(entity_ids[0]) assert state.state == pn.STATE assert state.attributes.get('message') == 'Hello World 2' assert state.attributes.get('title') == '2 beers' notification = notifications.get(entity_ids[0]) assert notification['status'] == pn.STATUS_UNREAD assert notification['message'] == 'Hello World 2' assert notification['title'] == '2 beers' assert notification['created_at'] is not None notifications.clear()
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Create the IntesisHome climate devices.""" from pyintesishome import IntesisHome ihuser = config[CONF_USERNAME] ihpass = config[CONF_PASSWORD] controller = IntesisHome(ihuser, ihpass, hass.loop) await hass.async_add_executor_job(controller.connect) while not controller.is_connected and not controller.error_message: await asyncio.sleep(0.1) if controller.is_connected: intesis_devices = controller.get_devices().items() async_add_entities([IntesisAC(deviceid, device, controller) for deviceid, device in intesis_devices], True) elif controller.error_message == "WRONG_USERNAME_PASSWORD": persistent_notification.create( hass, "Wrong username/password.", "IntesisHome", 'intesishome') else: persistent_notification.create( hass, controller.error_message, "IntesisHome Error", 'intesishome') controller.stop() raise PlatformNotReady()
def check_auth(self, notify=False): rdt = self.get_user_device_data('1', 'power') or {} nid = f'xiaomi-miot-auth-warning-{self.user_id}' eno = rdt.get('code', 0) if eno == 3: # auth err if notify: persistent_notification.create( self.hass, f'Xiaomi cloud: {self.user_id} auth failed, ' 'Please update option for this integration to refresh token.\n' f'小米账号:{self.user_id} 登陆失效,请重新保存集成选项以更新登陆信息。', 'Xiaomi Miot Warning', nid, ) _LOGGER.error( 'Xiaomi cloud: %s auth failed, Please update option for this integration to refresh token.\n%s', self.user_id, rdt, ) self.user_id = None self.service_token = None self.ssecurity = None if self.login(): persistent_notification.dismiss(self.hass, nid) return True _LOGGER.warning('Retry login xiaomi cloud failed: %s', self.username) return False return True
async def check_xiaomi_account(hass, user_input, errors, renew_devices=False): dvs = [] mic = None try: mic = await MiotCloud.from_token(hass, user_input, login=False) mic.login_times = 0 await mic.async_login(captcha=user_input.get('captcha')) if not await mic.async_check_auth(False): raise MiCloudException('Login failed') user_input['xiaomi_cloud'] = mic dvs = await mic.async_get_devices(renew=renew_devices) or [] if renew_devices: await MiotSpec.async_get_model_type(hass, 'xiaomi.miot.auto', use_remote=True) except (MiCloudException, MiCloudAccessDenied, Exception) as exc: err = f'{exc}' errors['base'] = 'cannot_login' if isinstance(exc, MiCloudAccessDenied) and mic: if url := mic.attrs.pop('notificationUrl', None): err = f'The login of Xiaomi account needs security verification. [Click here]({url}) to continue!\n' \ f'本次登陆小米账号需要安全验证,[点击这里]({url})继续!' persistent_notification.create( hass, err, f'Login to Xiaomi: {mic.username}', f'{DOMAIN}-login', ) elif url := mic.attrs.pop('captchaImg', None): err = f'Captcha:\n![captcha](data:image/jpeg;base64,{url})' user_input['xiaomi_cloud'] = mic user_input['captchaIck'] = mic.attrs.get('captchaIck')
def run(self) -> None: """Start processing events to save.""" current_version = self._setup_recorder() if current_version is None: self.hass.add_job(self.async_connection_failed) return self.schema_version = current_version schema_is_current = migration.schema_is_current(current_version) if schema_is_current: self._setup_run() else: self.migration_in_progress = True self.migration_is_live = migration.live_migration(current_version) self.hass.add_job(self.async_connection_success) if self.migration_is_live or schema_is_current: # If the migrate is live or the schema is current, we need to # wait for startup to complete. If its not live, we need to continue # on. self.hass.add_job(self.async_set_db_ready) # If shutdown happened before Home Assistant finished starting if self._wait_startup_or_shutdown() is SHUTDOWN_TASK: self.migration_in_progress = False # Make sure we cleanly close the run if # we restart before startup finishes self._shutdown() self.hass.add_job(self.async_set_db_ready) return # We wait to start the migration until startup has finished # since it can be cpu intensive and we do not want it to compete # with startup which is also cpu intensive if not schema_is_current: if self._migrate_schema_and_setup_run(current_version): self.schema_version = SCHEMA_VERSION if not self._event_listener: # If the schema migration takes so long that the end # queue watcher safety kicks in because MAX_QUEUE_BACKLOG # is reached, we need to reinitialize the listener. self.hass.add_job(self.async_initialize) else: persistent_notification.create( self.hass, "The database migration failed, check [the logs](/config/logs)." "Database Migration Failed", "recorder_database_migration", ) self.hass.add_job(self.async_set_db_ready) self._shutdown() return self.hass.add_job(self.async_set_db_ready) _LOGGER.debug("Recorder processing the queue") self.hass.add_job(self._async_set_recorder_ready_migration_done) self._run_event_loop()
def _migrate_schema_and_setup_run(self, current_version: int) -> bool: """Migrate schema to the latest version.""" persistent_notification.create( self.hass, "System performance will temporarily degrade during the database upgrade. Do not power down or restart the system until the upgrade completes. Integrations that read the database, such as logbook and history, may return inconsistent results until the upgrade completes.", "Database upgrade in progress", "recorder_database_migration", ) self.hass.add_job(self._async_migration_started) try: migration.migrate_schema(self, self.hass, self.engine, self.get_session, current_version) except exc.DatabaseError as err: if self._handle_database_error(err): return True _LOGGER.exception("Database error during schema migration") return False except Exception: # pylint: disable=broad-except _LOGGER.exception("Error during schema migration") return False else: self._setup_run() return True finally: self.migration_in_progress = False persistent_notification.dismiss(self.hass, "recorder_database_migration")
async def check_api_token(now): """Check if the current API token has expired and renew if so.""" next_check_interval = TOKEN_CHECK_INTERVAL result = await noonlight_integration.check_api_token() if not result: _LOGGER.error("API token failed renewal, retrying in 3 min") check_api_token.fail_count += 1 persistent_notification.create( hass, "Noonlight API token failed to renew {} time{}!\n" "Home Assistant will automatically attempt to renew the " "API token in 3 minutes.".format( check_api_token.fail_count, 's' if check_api_token.fail_count > 1 else ''), "Noonlight Token Renewal Failure", NOTIFICATION_TOKEN_UPDATE_FAILURE) next_check_interval = timedelta(minutes=3) else: if check_api_token.fail_count > 0: persistent_notification.create( hass, "Noonlight API token has now been " "renewed successfully.", "Noonlight Token Renewal Success", NOTIFICATION_TOKEN_UPDATE_SUCCESS) check_api_token.fail_count = 0 async_track_point_in_utc_time(hass, check_api_token, dt_util.utcnow() + next_check_interval)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): hass.data.setdefault(DATA_KEY, {}) hass.data[DOMAIN]['add_entities'][ENTITY_DOMAIN] = async_add_entities model = str(config.get(CONF_MODEL) or '') entities = [] miot = config.get('miot_type') if miot: spec = await MiotSpec.async_from_type(hass, miot) for srv in spec.get_services(ENTITY_DOMAIN, 'camera_control', 'video_doorbell'): if not spec.get_service('camera_stream_for_google_home', 'camera_stream_for_amazon_alexa'): persistent_notification.create( hass, f'Your camera [**{model}**](https://miot-spec.org/miot-spec-v2/instance?type={miot}) ' 'doesn\'t support streaming services.\n' f'你的摄像机不支持流服务。\n' 'https://github.com/al-one/hass-xiaomi-miot/issues/60#issuecomment-819435571', 'Xiaomi Miot Warning', f'{DATA_KEY}-warning-{model}', ) continue cfg = {**config, 'name': f"{config.get('name')} {srv.description}"} entities.append(MiotCameraEntity(hass, cfg, srv)) for entity in entities: hass.data[DOMAIN]['entities'][entity.unique_id] = entity async_add_entities(entities) bind_services_to_entries(hass, SERVICE_TO_METHOD)
def test_create_notification_id(self): """Ensure overwrites existing notification with same id.""" notifications = self.hass.data[pn.DOMAIN]['notifications'] assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 assert len(notifications) == 0 pn.create(self.hass, 'test', notification_id='Beer 2') self.hass.block_till_done() assert len(self.hass.states.entity_ids()) == 1 assert len(notifications) == 1 entity_id = 'persistent_notification.beer_2' state = self.hass.states.get(entity_id) assert state.attributes.get('message') == 'test' notification = notifications.get(entity_id) assert notification['message'] == 'test' assert notification['title'] is None pn.create(self.hass, 'test 2', notification_id='Beer 2') self.hass.block_till_done() # We should have overwritten old one assert len(self.hass.states.entity_ids()) == 1 state = self.hass.states.get(entity_id) assert state.attributes.get('message') == 'test 2' notification = notifications.get(entity_id) assert notification['message'] == 'test 2' notifications.clear()
def setup_platform( hass: HomeAssistant, config: ConfigType, add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Aladdin Connect platform.""" username: str = config[CONF_USERNAME] password: str = config[CONF_PASSWORD] acc = AladdinConnectClient(username, password) try: if not acc.login(): raise ValueError("Username or Password is incorrect") add_entities( (AladdinDevice(acc, door) for door in acc.get_doors()), update_before_add=True, ) except (TypeError, KeyError, NameError, ValueError) as ex: _LOGGER.error("%s", ex) persistent_notification.create( hass, "Error: {ex}<br />You will need to restart hass after fixing.", title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID, )
def step2_exchange(now): """Keep trying to validate the user_code until it expires.""" # For some reason, oauth.step1_get_device_and_user_codes() returns a datetime # object without tzinfo. For the comparison below to work, it needs one. user_code_expiry = dev_flow.user_code_expiry.replace( tzinfo=timezone.utc) if now >= user_code_expiry: persistent_notification.create( hass, "Authentication code expired, please restart " "Home-Assistant and try again", title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID, ) listener() try: credentials = oauth.step2_exchange(device_flow_info=dev_flow) except FlowExchangeError: # not ready yet, call again return storage = Storage(hass.config.path(TOKEN_FILE)) storage.put(credentials) do_setup(hass, hass_config, config) listener() persistent_notification.create( hass, (f"We are all setup now. Check {YAML_DEVICES} for calendars that have " f"been found"), title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID, )
def setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Hunter Hydrawise component.""" conf = config[DOMAIN] access_token = conf[CONF_ACCESS_TOKEN] scan_interval = conf.get(CONF_SCAN_INTERVAL) try: hydrawise = Hydrawiser(user_token=access_token) hass.data[DATA_HYDRAWISE] = HydrawiseHub(hydrawise) except (ConnectTimeout, HTTPError) as ex: _LOGGER.error("Unable to connect to Hydrawise cloud service: %s", str(ex)) persistent_notification.create( hass, f"Error: {ex}<br />You will need to restart hass after fixing.", title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID, ) return False def hub_refresh(event_time): """Call Hydrawise hub to refresh information.""" _LOGGER.debug("Updating Hydrawise Hub component") hass.data[DATA_HYDRAWISE].data.update_controller_info() dispatcher_send(hass, SIGNAL_UPDATE_HYDRAWISE) # Call the Hydrawise API to refresh updates track_time_interval(hass, hub_refresh, scan_interval) return True
def setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Wireless Sensor Tag component.""" conf = config[DOMAIN] username = conf.get(CONF_USERNAME) password = conf.get(CONF_PASSWORD) try: wirelesstags = WirelessTags(username=username, password=password) platform = WirelessTagPlatform(hass, wirelesstags) platform.load_tags() platform.start_monitoring() hass.data[DOMAIN] = platform except (ConnectTimeout, HTTPError, WirelessTagsException) as ex: _LOGGER.error("Unable to connect to wirelesstag.net service: %s", str(ex)) persistent_notification.create( hass, f"Error: {ex}<br />Please restart hass after fixing this.", title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID, ) return False return True
def log_exception(ex, domain, config, hass=None): """Generate log exception for config validation.""" message = "Invalid config for [{}]: ".format(domain) if hass is not None: _PERSISTENT_VALIDATION.add(domain) message = ( "The following platforms contain invalid configuration: " + ", ".join(list(_PERSISTENT_VALIDATION)) + " (please check your configuration)" ) persistent_notification.create(hass, message, "Invalid config", "invalid_config") if "extra keys not allowed" in ex.error_message: message += "[{}] is an invalid option for [{}]. Check: {}->{}.".format( ex.path[-1], domain, domain, "->".join("%s" % m for m in ex.path) ) else: message += "{}.".format(humanize_error(config, ex)) if hasattr(config, "__line__"): message += " (See {}:{})".format(config.__config_file__, config.__line__ or "?") if domain != "homeassistant": message += " Please check the docs at " "https://home-assistant.io/components/{}/".format(domain) _LOGGER.error(message)
def handle_request(self, request, **values): """Handle request to url.""" from werkzeug.exceptions import MethodNotAllowed, Unauthorized if request.method == "OPTIONS": # For CORS preflight requests. return self.options(request) try: handler = getattr(self, request.method.lower()) except AttributeError: raise MethodNotAllowed remote_addr = HomeAssistantWSGI.get_real_ip(request) # Auth code verbose on purpose authenticated = False if self.hass.wsgi.api_password is None: authenticated = True elif self.hass.wsgi.is_trusted_ip(remote_addr): authenticated = True elif hmac.compare_digest(request.headers.get(HTTP_HEADER_HA_AUTH, ''), self.hass.wsgi.api_password): # A valid auth header has been set authenticated = True elif hmac.compare_digest(request.args.get(DATA_API_PASSWORD, ''), self.hass.wsgi.api_password): authenticated = True if self.requires_auth and not authenticated: _LOGGER.warning( 'Login attempt or request with an invalid ' 'password from %s', remote_addr) persistent_notification.create( self.hass, 'Invalid password used from {}'.format(remote_addr), 'Login attempt failed', NOTIFICATION_ID_LOGIN) raise Unauthorized() request.authenticated = authenticated _LOGGER.info('Serving %s to %s (auth: %s)', request.path, remote_addr, authenticated) result = handler(request, **values) if isinstance(result, self.Response): # The method handler returned a ready-made Response, how nice of it return result status_code = 200 if isinstance(result, tuple): result, status_code = result return self.Response(result, status=status_code)
def turn_on(self): if self.gw.lock_firmware(enable=True): self._state = True self.schedule_update_ha_state() persistent_notification.create( self.hass, "Firmware update is locked. You can sleep well.", "Xiaomi Gateway 3")
def handle_request(self, request, **values): """Handle request to url.""" from werkzeug.exceptions import MethodNotAllowed, Unauthorized if request.method == "OPTIONS": # For CORS preflight requests. return self.options(request) try: handler = getattr(self, request.method.lower()) except AttributeError: raise MethodNotAllowed # Auth code verbose on purpose authenticated = False if self.hass.wsgi.api_password is None: authenticated = True elif request.remote_addr in self.hass.wsgi.approved_ips: authenticated = True elif hmac.compare_digest(request.headers.get(HTTP_HEADER_HA_AUTH, ''), self.hass.wsgi.api_password): # A valid auth header has been set authenticated = True elif hmac.compare_digest(request.args.get(DATA_API_PASSWORD, ''), self.hass.wsgi.api_password): authenticated = True if self.requires_auth and not authenticated: _LOGGER.warning('Login attempt or request with an invalid ' 'password from %s', request.remote_addr) persistent_notification.create( self.hass, 'Invalid password used from {}'.format(request.remote_addr), 'Login attempt failed', NOTIFICATION_ID_LOGIN) raise Unauthorized() request.authenticated = authenticated _LOGGER.info('Serving %s to %s (auth: %s)', request.path, request.remote_addr, authenticated) result = handler(request, **values) if isinstance(result, self.Response): # The method handler returned a ready-made Response, how nice of it return result status_code = 200 if isinstance(result, tuple): result, status_code = result return self.Response(result, status=status_code)
def setup_platform( hass: HomeAssistant, config: ConfigType, add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Decora WiFi platform.""" email = config[CONF_USERNAME] password = config[CONF_PASSWORD] session = DecoraWiFiSession() try: success = session.login(email, password) # If login failed, notify user. if success is None: msg = "Failed to log into myLeviton Services. Check credentials." _LOGGER.error(msg) persistent_notification.create(hass, msg, title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID) return # Gather all the available devices... perms = session.user.get_residential_permissions() all_switches = [] for permission in perms: if permission.residentialAccountId is not None: acct = ResidentialAccount(session, permission.residentialAccountId) for residence in acct.get_residences(): for switch in residence.get_iot_switches(): all_switches.append(switch) elif permission.residenceId is not None: residence = Residence(session, permission.residenceId) for switch in residence.get_iot_switches(): all_switches.append(switch) add_entities(DecoraWifiLight(sw) for sw in all_switches) except ValueError: _LOGGER.error("Failed to communicate with myLeviton Service") # Listen for the stop event and log out. def logout(event): """Log out...""" try: if session is not None: Person.logout(session) except ValueError: _LOGGER.error("Failed to log out of myLeviton Service") hass.bus.listen(EVENT_HOMEASSISTANT_STOP, logout)
def test_dismiss_notification(self): """Ensure removal of specific notification.""" assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 pn.create(self.hass, 'test', notification_id='Beer 2') self.hass.block_till_done() assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 1 pn.dismiss(self.hass, notification_id='Beer 2') self.hass.block_till_done() assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0
def test_create_template_error(self): """Ensure we output templates if contain error.""" assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 pn.create(self.hass, '{{ message + 1 }}', '{{ title + 1 }}') self.hass.block_till_done() entity_ids = self.hass.states.entity_ids(pn.DOMAIN) assert len(entity_ids) == 1 state = self.hass.states.get(entity_ids[0]) assert state.state == '{{ message + 1 }}' assert state.attributes.get('title') == '{{ title + 1 }}'
def test_create(self): """Test creating notification without title or notification id.""" assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 pn.create(self.hass, 'Hello World {{ 1 + 1 }}', title='{{ 1 + 1 }} beers') self.hass.block_till_done() entity_ids = self.hass.states.entity_ids(pn.DOMAIN) assert len(entity_ids) == 1 state = self.hass.states.get(entity_ids[0]) assert state.state == 'Hello World 2' assert state.attributes.get('title') == '2 beers'
def test_dismiss_notification(self): """Ensure removal of specific notification.""" notifications = self.hass.data[pn.DOMAIN]['notifications'] assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 assert len(notifications) == 0 pn.create(self.hass, 'test', notification_id='Beer 2') self.hass.block_till_done() assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 1 assert len(notifications) == 1 pn.dismiss(self.hass, notification_id='Beer 2') self.hass.block_till_done() assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 assert len(notifications) == 0 notifications.clear()
def test_create_notification_id(self): """Ensure overwrites existing notification with same id.""" assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 pn.create(self.hass, 'test', notification_id='Beer 2') self.hass.block_till_done() assert len(self.hass.states.entity_ids()) == 1 state = self.hass.states.get('persistent_notification.beer_2') assert state.state == 'test' pn.create(self.hass, 'test 2', notification_id='Beer 2') self.hass.block_till_done() # We should have overwritten old one assert len(self.hass.states.entity_ids()) == 1 state = self.hass.states.get('persistent_notification.beer_2') assert state.state == 'test 2'
def test_create_template_error(self): """Ensure we output templates if contain error.""" notifications = self.hass.data[pn.DOMAIN]['notifications'] assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 assert len(notifications) == 0 pn.create(self.hass, '{{ message + 1 }}', '{{ title + 1 }}') self.hass.block_till_done() entity_ids = self.hass.states.entity_ids(pn.DOMAIN) assert len(entity_ids) == 1 assert len(notifications) == 1 state = self.hass.states.get(entity_ids[0]) assert state.attributes.get('message') == '{{ message + 1 }}' assert state.attributes.get('title') == '{{ title + 1 }}' notification = notifications.get(entity_ids[0]) assert notification['message'] == '{{ message + 1 }}' assert notification['title'] == '{{ title + 1 }}' notifications.clear()
def prepare_setup_platform(hass: core.HomeAssistant, config, domain: str, platform_name: str) -> Optional[ModuleType]: """Load a platform and makes sure dependencies are setup.""" _ensure_loader_prepared(hass) platform_path = PLATFORM_FORMAT.format(domain, platform_name) platform = loader.get_platform(domain, platform_name) # Not found if platform is None: _LOGGER.error("Unable to find platform %s", platform_path) _PERSISTENT_PLATFORMS.add(platform_path) message = ( "Unable to find the following platforms: " + ", ".join(list(_PERSISTENT_PLATFORMS)) + "(please check your configuration)" ) persistent_notification.create(hass, message, "Invalid platforms", "platform_errors") return None # Already loaded elif platform_path in hass.config.components: return platform # Load dependencies for component in getattr(platform, "DEPENDENCIES", []): if not setup_component(hass, component, config): _LOGGER.error( "Unable to prepare setup for platform %s because " "dependency %s could not be initialized", platform_path, component, ) return None if not _handle_requirements(hass, platform, platform_path): return None return platform
def test_mark_read(self): """Ensure notification is marked as Read.""" notifications = self.hass.data[pn.DOMAIN]['notifications'] assert len(notifications) == 0 pn.create(self.hass, 'test', notification_id='Beer 2') self.hass.block_till_done() entity_id = 'persistent_notification.beer_2' assert len(notifications) == 1 notification = notifications.get(entity_id) assert notification['status'] == pn.STATUS_UNREAD self.hass.services.call(pn.DOMAIN, pn.SERVICE_MARK_READ, { 'notification_id': 'Beer 2' }) self.hass.block_till_done() assert len(notifications) == 1 notification = notifications.get(entity_id) assert notification['status'] == pn.STATUS_READ notifications.clear()