def process_wrong_login(request): """Process a wrong login attempt.""" remote_addr = request[KEY_REAL_IP] msg = ('Login attempt or request with invalid authentication ' 'from {}'.format(remote_addr)) _LOGGER.warning(msg) persistent_notification.async_create( request.app['hass'], msg, 'Login attempt failed', NOTIFICATION_ID_LOGIN) # Check if ban middleware is loaded if (KEY_BANNED_IPS not in request.app or request.app[KEY_LOGIN_THRESHOLD] < 1): return request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] += 1 if (request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] > request.app[KEY_LOGIN_THRESHOLD]): new_ban = IpBan(remote_addr) request.app[KEY_BANNED_IPS].append(new_ban) hass = request.app['hass'] yield from hass.async_add_job( update_ip_bans_config, hass.config.path(IP_BANS_FILE), new_ban) _LOGGER.warning( "Banned IP %s for too many login attempts", remote_addr) persistent_notification.async_create( hass, 'Too many login attempts from {}'.format(remote_addr), 'Banning IP address', NOTIFICATION_ID_BAN)
def async_notify_setup_error( hass: HomeAssistant, component: str, display_link: bool = False) -> None: """Print a persistent notification. This method must be run in the event loop. """ from homeassistant.components import persistent_notification errors = hass.data.get(DATA_PERSISTENT_ERRORS) if errors is None: errors = hass.data[DATA_PERSISTENT_ERRORS] = {} errors[component] = errors.get(component) or display_link message = 'The following components and platforms could not be set up:\n\n' for name, link in errors.items(): if link: part = HA_COMPONENT_URL.format(name.replace('_', '-'), name) else: part = name message += ' - {}\n'.format(part) message += '\nPlease check your config.' persistent_notification.async_create( hass, message, 'Invalid config', 'invalid_config')
def process_wrong_login(request): """Process a wrong login attempt.""" if (not request.app[KEY_BANS_ENABLED] or request.app[KEY_LOGIN_THRESHOLD] < 1): return if KEY_FAILED_LOGIN_ATTEMPTS not in request.app: request.app[KEY_FAILED_LOGIN_ATTEMPTS] = defaultdict(int) remote_addr = get_real_ip(request) request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] += 1 if (request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] > request.app[KEY_LOGIN_THRESHOLD]): new_ban = IpBan(remote_addr) request.app[KEY_BANNED_IPS].append(new_ban) hass = request.app['hass'] yield from hass.loop.run_in_executor( None, update_ip_bans_config, hass.config.path(IP_BANS_FILE), new_ban) _LOGGER.warning('Banned IP %s for too many login attempts', remote_addr) persistent_notification.async_create( hass, 'Too many login attempts from {}'.format(remote_addr), 'Banning IP address', NOTIFICATION_ID_BAN)
def connection_failed(): """Connect failed tasks.""" self.async_db_ready.set_result(False) persistent_notification.async_create( self.hass, "The recorder could not start, please check the log", "Recorder")
def _async_persistent_notification(hass: core.HomeAssistant, component: str, link: Optional[bool]=False): """Print a persistent notification. This method must be run in the event loop. """ _PERSISTENT_ERRORS[component] = _PERSISTENT_ERRORS.get(component) or link _lst = [HA_COMPONENT_URL.format(name.replace('_', '-'), name) if link else name for name, link in _PERSISTENT_ERRORS.items()] message = ('The following components and platforms could not be set up:\n' '* ' + '\n* '.join(list(_lst)) + '\nPlease check your config') persistent_notification.async_create( hass, message, 'Invalid config', 'invalid_config')
def handle(request): """Handle incoming request.""" if not request.app['hass'].is_running: return web.Response(status=503) remote_addr = get_real_ip(request) authenticated = request.get(KEY_AUTHENTICATED, False) if view.requires_auth and not authenticated: yield from process_wrong_login(request) _LOGGER.warning('Login attempt or request with an invalid ' 'password from %s', remote_addr) persistent_notification.async_create( request.app['hass'], 'Invalid password used from {}'.format(remote_addr), 'Login attempt failed', NOTIFICATION_ID_LOGIN) raise HTTPUnauthorized() _LOGGER.info('Serving %s to %s (auth: %s)', request.path, remote_addr, authenticated) result = handler(request, **request.match_info) if asyncio.iscoroutine(result): result = yield from result if isinstance(result, web.StreamResponse): # 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 if isinstance(result, str): result = result.encode('utf-8') elif result is None: result = b'' elif not isinstance(result, bytes): assert False, ('Result should be None, string, bytes or Response. ' 'Got: {}').format(result) return web.Response(body=result, status=status_code)
def async_notify_setup_error(hass, component, link=False): """Print a persistent notification. This method must be run in the event loop. """ from homeassistant.components import persistent_notification errors = hass.data.get(DATA_PERSISTENT_ERRORS) if errors is None: errors = hass.data[DATA_PERSISTENT_ERRORS] = {} errors[component] = errors.get(component) or link _lst = [HA_COMPONENT_URL.format(name.replace('_', '-'), name) if link else name for name, link in errors.items()] message = ('The following components and platforms could not be set up:\n' '* ' + '\n* '.join(list(_lst)) + '\nPlease check your config') persistent_notification.async_create( hass, message, 'Invalid config', 'invalid_config')
async def _async_generate_memory_profile(hass: HomeAssistant, call: ServiceCall): start_time = int(time.time() * 1000000) persistent_notification.async_create( hass, "The memory profile has started. This notification will be updated when it is complete.", title="Profile Started", notification_id=f"memory_profiler_{start_time}", ) heap_profiler = hpy() heap_profiler.setref() await asyncio.sleep(float(call.data[CONF_SECONDS])) heap = heap_profiler.heap() heap_path = hass.config.path(f"heap_profile.{start_time}.hpy") await hass.async_add_executor_job(_write_memory_profile, heap, heap_path) persistent_notification.async_create( hass, f"Wrote heapy memory profile to {heap_path}", title="Profile Complete", notification_id=f"memory_profiler_{start_time}", )
def async_show_setup_message(hass: HomeAssistant, entry_id: str, bridge_name: str, pincode: bytes, uri: str) -> None: """Display persistent notification with setup information.""" pin = pincode.decode() _LOGGER.info("Pincode: %s", pin) buffer = io.BytesIO() url = pyqrcode.create(uri) url.svg(buffer, scale=5, module_color="#000", background="#FFF") pairing_secret = secrets.token_hex(32) hass.data[DOMAIN][entry_id][HOMEKIT_PAIRING_QR] = buffer.getvalue() hass.data[DOMAIN][entry_id][HOMEKIT_PAIRING_QR_SECRET] = pairing_secret message = (f"To set up {bridge_name} in the Home App, " f"scan the QR code or enter the following code:\n" f"### {pin}\n" f"![image](/api/homekit/pairingqr?{entry_id}-{pairing_secret})") persistent_notification.async_create(hass, message, "HomeKit Pairing", entry_id)
def async_notify_setup_error(hass, component, link=False): """Print a persistent notification. This method must be run in the event loop. """ from homeassistant.components import persistent_notification errors = hass.data.get(DATA_PERSISTENT_ERRORS) if errors is None: errors = hass.data[DATA_PERSISTENT_ERRORS] = {} errors[component] = errors.get(component) or link _lst = [ HA_COMPONENT_URL.format(name.replace('_', '-'), name) if link else name for name, link in errors.items() ] message = ('The following components and platforms could not be set up:\n' '* ' + '\n* '.join(list(_lst)) + '\nPlease check your config') persistent_notification.async_create(hass, message, 'Invalid config', 'invalid_config')
async def test_create(hass): """Test creating notification without title or notification id.""" notifications = hass.data[pn.DOMAIN] assert len(hass.states.async_entity_ids(pn.DOMAIN)) == 0 assert len(notifications) == 0 pn.async_create(hass, "Hello World {{ 1 + 1 }}", title="{{ 1 + 1 }} beers") entity_ids = hass.states.async_entity_ids(pn.DOMAIN) assert len(entity_ids) == 1 assert len(notifications) == 1 state = 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
async def async_handle_core_service(call: ServiceCall) -> None: """Service handler for handling core services.""" if call.service in SHUTDOWN_SERVICES and recorder.async_migration_in_progress( hass ): _LOGGER.error( "The system cannot %s while a database upgrade is in progress", call.service, ) raise HomeAssistantError( f"The system cannot {call.service} " "while a database upgrade is in progress." ) if call.service == SERVICE_HOMEASSISTANT_STOP: await hassio.stop_homeassistant() return errors = await conf_util.async_check_ha_config_file(hass) if errors: _LOGGER.error( "The system cannot %s because the configuration is not valid: %s", call.service, errors, ) persistent_notification.async_create( hass, "Config error. See [the logs](/config/logs) for details.", "Config validating", f"{HASS_DOMAIN}.check_config", ) raise HomeAssistantError( f"The system cannot {call.service} " f"because the configuration is not valid: {errors}" ) if call.service == SERVICE_HOMEASSISTANT_RESTART: await hassio.restart_homeassistant()
def update(self, data: dict = None): if 'pairing_start' in data: self._state = STATE_ON elif 'pairing_stop' in data: self._state = STATE_OFF self.gw.pair_model = None elif 'added_device' in data: text = "New device:\n" + '\n'.join( f"{k}: {v}" for k, v in data['added_device'].items()) persistent_notification.async_create(self.hass, text, "Xiaomi Gateway 3") elif 'removed_did' in data: self.debug(f"Handle removed_did: {data['removed_did']}") utils.remove_device(self.hass, data['removed_did']) elif 'network_pan_id' in data: self._attrs.update(data) self.async_write_ha_state()
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Handle removal of an entry.""" lockname = config_entry.data[CONF_LOCK_NAME] notification_id = f"{DOMAIN}_{lockname}_unload" async_create( hass, (f"Removing `{lockname}` and all of the files that were generated for " "it. This may take some time so don't panic. This message will " "automatically clear when removal is complete."), title=f"{DOMAIN.title()} - Removing `{lockname}`", notification_id=notification_id, ) unload_ok = all(await asyncio.gather(*[ hass.config_entries.async_forward_entry_unload(config_entry, platform) for platform in PLATFORMS ])) if unload_ok: # Remove all package files and the base folder if needed await hass.async_add_executor_job(delete_lock_and_base_folder, hass, config_entry) await async_reload_package_platforms(hass) # Unsubscribe to any listeners for unsub_listener in hass.data[DOMAIN][config_entry.entry_id].get( UNSUB_LISTENERS, []): unsub_listener() hass.data[DOMAIN][config_entry.entry_id].get(UNSUB_LISTENERS, []).clear() hass.data[DOMAIN].pop(config_entry.entry_id) async_dismiss(hass, notification_id) return unload_ok
async def process_wrong_login(request): """Process a wrong login attempt. Increase failed login attempts counter for remote IP address. Add ip ban entry if failed login attempts exceeds threshold. """ remote_addr = request[KEY_REAL_IP] msg = ('Login attempt or request with invalid authentication ' 'from {}'.format(remote_addr)) _LOGGER.warning(msg) persistent_notification.async_create( request.app['hass'], msg, 'Login attempt failed', NOTIFICATION_ID_LOGIN) # Check if ban middleware is loaded if (KEY_BANNED_IPS not in request.app or request.app[KEY_LOGIN_THRESHOLD] < 1): return request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] += 1 if (request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] > request.app[KEY_LOGIN_THRESHOLD]): new_ban = IpBan(remote_addr) request.app[KEY_BANNED_IPS].append(new_ban) hass = request.app['hass'] await hass.async_add_job( update_ip_bans_config, hass.config.path(IP_BANS_FILE), new_ban) _LOGGER.warning( "Banned IP %s for too many login attempts", remote_addr) persistent_notification.async_create( hass, 'Too many login attempts from {}'.format(remote_addr), 'Banning IP address', NOTIFICATION_ID_BAN)
def async_check_ha_config_file(hass): """Check if HA config file valid. This method is a coroutine. """ import homeassistant.components.persistent_notification as pn proc = yield from asyncio.create_subprocess_exec( sys.executable, '-m', 'homeassistant', '--script', 'check_config', '--config', hass.config.config_dir, stdout=asyncio.subprocess.PIPE, loop=hass.loop) # Wait for the subprocess exit (stdout_data, dummy) = yield from proc.communicate() result = yield from proc.wait() if result: content = re.sub(r'\033\[[^m]*m', '', str(stdout_data, 'utf-8')) # Put error cleaned from color codes in the error log so it # will be visible at the UI. _LOGGER.error(content) pn.async_create( hass, "Config error. See dev-info panel for details.", "Config validating", "{0}.check_config".format(CONF_CORE)) raise HomeAssistantError("Invalid config")
async def _async_generate_profile(hass: HomeAssistant, call: ServiceCall): start_time = int(time.time() * 1000000) persistent_notification.async_create( hass, "The profile has started. This notification will be updated when it is complete.", title="Profile Started", notification_id=f"profiler_{start_time}", ) profiler = cProfile.Profile() profiler.enable() await asyncio.sleep(float(call.data[CONF_SECONDS])) profiler.disable() cprofile_path = hass.config.path(f"profile.{start_time}.cprof") callgrind_path = hass.config.path(f"callgrind.out.{start_time}") await hass.async_add_executor_job(_write_profile, profiler, cprofile_path, callgrind_path) persistent_notification.async_create( hass, f"Wrote cProfile data to {cprofile_path} and callgrind data to {callgrind_path}", title="Profile Complete", notification_id=f"profiler_{start_time}", )
async def async_setup_systems(hass, config, uplink): if not len(config.get(CONF_SYSTEMS)): systems = await uplink.get_systems() msg = json.dumps(systems, indent=1) persistent_notification.async_create( hass, 'No systems selected, please configure one system id of:<br/><br/><pre>{}</pre>' .format(msg), 'Invalid nibe config', 'invalid_config') return systems = [ NibeSystem(hass, uplink, config[CONF_SYSTEM], config) for config in config.get(CONF_SYSTEMS) ] hass.data[DATA_NIBE] = {} hass.data[DATA_NIBE]['systems'] = systems hass.data[DATA_NIBE]['uplink'] = uplink tasks = [system.load() for system in systems] await asyncio.gather(*tasks)
async def async_poll(self, now: datetime.datetime) -> None: """Poll the entity if subscriptions fail.""" if not self.speaker.subscriptions_failed: if soco_config.EVENT_ADVERTISE_IP: listener_msg = f"{self.speaker.subscription_address} (advertising as {soco_config.EVENT_ADVERTISE_IP})" else: listener_msg = self.speaker.subscription_address message = f"{self.speaker.zone_name} cannot reach {listener_msg}, falling back to polling, functionality may be limited" log_link_msg = f", see {SUB_FAIL_URL} for more details" notification_link_msg = f'.\n\nSee <a href="{SUB_FAIL_URL}">Sonos documentation</a> for more details.' _LOGGER.warning(message + log_link_msg) persistent_notification.async_create( self.hass, message + notification_link_msg, "Sonos networking issue", "sonos_subscriptions_failed", ) self.speaker.subscriptions_failed = True await self.speaker.async_unsubscribe() try: await self._async_poll() except (OSError, SoCoException) as ex: _LOGGER.debug("Error connecting to %s: %s", self.entity_id, ex)
def update(self, data: dict = None): if 'pairing_start' in data: self._state = True elif 'pairing_stop' in data: self._state = False self.gw.pair_model = None elif 'added_device' in data: text = "New device:\n" + '\n'.join( f"{k}: {v}" for k, v in data['added_device'].items()) persistent_notification.async_create(self.hass, text, "Xiaomi Gateway 3") elif 'removed_did' in data: did = data['removed_did'] # did not always string: # https://github.com/AlexxIT/XiaomiGateway3/issues/122 if isinstance(did, str) and did.startswith('lumi.'): self.debug(f"Handle removed_did: {did}") utils.remove_device(self.hass, did) self.async_write_ha_state()
def process_wrong_login(request): """Process a wrong login attempt.""" remote_addr = get_real_ip(request) msg = ('Login attempt or request with invalid authentication ' 'from {}'.format(remote_addr)) _LOGGER.warning(msg) persistent_notification.async_create( request.app['hass'], msg, 'Login attempt failed', NOTIFICATION_ID_LOGIN) if (not request.app[KEY_BANS_ENABLED] or request.app[KEY_LOGIN_THRESHOLD] < 1): return if KEY_FAILED_LOGIN_ATTEMPTS not in request.app: request.app[KEY_FAILED_LOGIN_ATTEMPTS] = defaultdict(int) request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] += 1 if (request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] > request.app[KEY_LOGIN_THRESHOLD]): new_ban = IpBan(remote_addr) request.app[KEY_BANNED_IPS].append(new_ban) hass = request.app['hass'] yield from hass.loop.run_in_executor( None, update_ip_bans_config, hass.config.path(IP_BANS_FILE), new_ban) _LOGGER.warning( "Banned IP %s for too many login attempts", remote_addr) persistent_notification.async_create( hass, 'Too many login attempts from {}'.format(remote_addr), 'Banning IP address', NOTIFICATION_ID_BAN)
async def _async_learn_ir_command(self, command): """Learn an infrared command.""" device = self._device try: await device.async_request(device.api.enter_learning) except (BroadlinkException, OSError) as err: _LOGGER.debug("Failed to enter learning mode: %s", err) raise persistent_notification.async_create( self.hass, f"Press the '{command}' button.", title="Learn command", notification_id="learn_command", ) try: start_time = dt.utcnow() while (dt.utcnow() - start_time) < LEARNING_TIMEOUT: await asyncio.sleep(1) try: code = await device.async_request(device.api.check_data) except (ReadError, StorageError): continue return b64encode(code).decode("utf8") raise TimeoutError( "No infrared code received within " f"{LEARNING_TIMEOUT.total_seconds()} seconds" ) finally: persistent_notification.async_dismiss( self.hass, notification_id="learn_command" )
def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" dict1 = {} statedict = {} if self._cloud['did'] in self.coordinator.data: if all(item['code'] == -704042011 for item in self.coordinator.data[self._cloud['did']]): if self._available == True or self._available == None: if OFFLINE_NOTIFY: persistent_notification.async_create( self._hass, f"请注意,云端接入设备 **{self._name}** 已离线。", "Xiaomi MIoT - 设备离线") else: _LOGGER.warn(f"请注意,云端接入设备 **{self._name}** 已离线。") self._available = False else: self._available = True for item in self.coordinator.data[self._cloud['did']]: if dict1.get(item['siid']): dict1[item['siid']][item['piid']] = item.get('value') else: dict1[item['siid']] = {} dict1[item['siid']][item['piid']] = item.get('value') for key, value in self._mapping.items(): try: statedict[key] = dict1[value['siid']][value['piid']] except KeyError: statedict[key] = None else: pass self._state_attrs.update(statedict) self._handle_platform_specific_attrs() self.async_write_ha_state() self.publish_updates()
for item in p: item['did'] = did pp = {'params': p} ppp = json.dumps(pp,separators=(',', ':')) _LOGGER.info(f"Control {self._name} params: {ppp}") results = await self._cloud_instance.set_props(ppp, self._cloud.get("server_location")) if results: if r := results.get('result'): for item in r: if item['code'] == 1: self._delay_update = LONG_DELAY elif item['code'] == -704042011: if self._available == True or self._available == None: if OFFLINE_NOTIFY: persistent_notification.async_create( self._hass, f"请注意,云端接入设备 **{self._name}** 已离线。", "Xiaomi MIoT - 设备离线") else: _LOGGER.warn(f"请注意,云端接入设备 **{self._name}** 已离线。") self._available = False self._skip_update = True return False elif item['code'] != 0: _LOGGER.error(f"Control {self._name} by cloud failed: {r}") return False self._skip_update = True return True return False except DeviceException as ex: _LOGGER.error('Set miot property to %s: %s(%s) failed: %s', self._name, field, params, ex)
async def async_setup_entry(hass: core.HomeAssistant, entry: config_entries.ConfigEntry): """Set up a bridge from a config entry.""" # Migrate allow_unreachable from config entry data to config entry options if (CONF_ALLOW_UNREACHABLE not in entry.options and CONF_ALLOW_UNREACHABLE in entry.data and entry.data[CONF_ALLOW_UNREACHABLE] != DEFAULT_ALLOW_UNREACHABLE): options = { **entry.options, CONF_ALLOW_UNREACHABLE: entry.data[CONF_ALLOW_UNREACHABLE], } data = entry.data.copy() data.pop(CONF_ALLOW_UNREACHABLE) hass.config_entries.async_update_entry(entry, data=data, options=options) # Migrate allow_hue_groups from config entry data to config entry options if (CONF_ALLOW_HUE_GROUPS not in entry.options and CONF_ALLOW_HUE_GROUPS in entry.data and entry.data[CONF_ALLOW_HUE_GROUPS] != DEFAULT_ALLOW_HUE_GROUPS): options = { **entry.options, CONF_ALLOW_HUE_GROUPS: entry.data[CONF_ALLOW_HUE_GROUPS], } data = entry.data.copy() data.pop(CONF_ALLOW_HUE_GROUPS) hass.config_entries.async_update_entry(entry, data=data, options=options) bridge = HueBridge(hass, entry) if not await bridge.async_setup(): return False hass.data[DOMAIN][entry.entry_id] = bridge config = bridge.api.config # For backwards compat unique_id = normalize_bridge_id(config.bridgeid) if entry.unique_id is None: hass.config_entries.async_update_entry(entry, unique_id=unique_id) # For recovering from bug where we incorrectly assumed homekit ID = bridge ID elif entry.unique_id != unique_id: # Find entries with this unique ID other_entry = next( (entry for entry in hass.config_entries.async_entries(DOMAIN) if entry.unique_id == unique_id), None, ) if other_entry is None: # If no other entry, update unique ID of this entry ID. hass.config_entries.async_update_entry(entry, unique_id=unique_id) elif other_entry.source == config_entries.SOURCE_IGNORE: # There is another entry but it is ignored, delete that one and update this one hass.async_create_task( hass.config_entries.async_remove(other_entry.entry_id)) hass.config_entries.async_update_entry(entry, unique_id=unique_id) else: # There is another entry that already has the right unique ID. Delete this entry hass.async_create_task( hass.config_entries.async_remove(entry.entry_id)) return False device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( config_entry_id=entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, config.mac)}, identifiers={(DOMAIN, config.bridgeid)}, manufacturer="Signify", name=config.name, model=config.modelid, sw_version=config.swversion, ) if config.modelid == "BSB002" and config.swversion < "1935144040": persistent_notification.async_create( hass, "Your Hue hub has a known security vulnerability ([CVE-2020-6007](https://cve.circl.lu/cve/CVE-2020-6007)). Go to the Hue app and check for software updates.", "Signify Hue", "hue_hub_firmware", ) elif config.swupdate2_bridge_state == "readytoinstall": err = ( "Please check for software updates of the bridge in the Philips Hue App.", "Signify Hue", "hue_hub_firmware", ) _LOGGER.warning(err) return True
async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" errors = {} # Check if already configured # await self.async_set_unique_id(DOMAIN) # self._abort_if_unique_id_configured() if user_input is not None: self._name = user_input[CONF_NAME] self._host = user_input[CONF_HOST] self._token = user_input[CONF_TOKEN] # self._mapping = user_input[CONF_MAPPING] # self._params = user_input[CONF_CONTROL_PARAMS] device = MiioDevice(self._host, self._token) try: self._info = device.info() except DeviceException: # print("DeviceException!!!!!!") errors['base'] = 'cannot_connect' # except ValueError: # errors['base'] = 'value_error' if self._info is not None: unique_id = format_mac(self._info.mac_address) # await self.async_set_unique_id(unique_id) for entry in self._async_current_entries(): if entry.unique_id == unique_id: persistent_notification.async_create( self.hass, f"您新添加的设备: **{self._name}** ,\n" f"其 MAC 地址与现有的某个设备相同。\n" f"只是通知,不会造成任何影响。", "设备可能重复") break self._abort_if_unique_id_configured() d = self._info.raw self._model = d['model'] device_info = (f"Model: {d['model']}\n" f"Firmware: {d['fw_ver']}\n" f"MAC: {d['mac']}\n") # self._info = self.get_devconfg_by_model(self._model) self._info = await async_get_mp_from_net(self.hass, self._model) \ or await guess_mp_from_model(self.hass, self._model) if self._info: device_info += "\n已经自动发现配置参数。\n如无特殊需要,无需修改下列内容。\n" devtype_default = self._info.get('device_type') mapping_default = self._info.get('mapping') params_default = self._info.get('params') else: device_info += "请手动进行配置。\n" devtype_default = '' mapping_default = '{"switch_status":{"siid":2,"piid":1}}' params_default = '{"switch_status":{"power_on":true,"power_off":false}}' self._input2 = user_input return self.async_show_form( step_id="devinfo", data_schema=vol.Schema({ vol.Required('devtype', default=devtype_default): vol.In(SUPPORTED_DOMAINS), vol.Required(CONF_MAPPING, default=mapping_default): str, vol.Required(CONF_CONTROL_PARAMS, default=params_default): str, vol.Optional('cloud_read'): bool, vol.Optional('cloud_write'): bool, }), description_placeholders={"device_info": device_info}, errors=errors, ) return self.async_show_form( step_id="user", data_schema=vol.Schema({ vol.Required(CONF_NAME): str, vol.Required(CONF_HOST, default='192.168.'): str, vol.Required(CONF_TOKEN): str, # vol.Required(CONF_MAPPING, default='{"switch_status":{"siid":2,"piid":1}}'): str, # vol.Required(CONF_CONTROL_PARAMS, default='{"switch_status":{"power_on":true,"power_off":false}}'): str, }), # description_placeholders={"device_info": "device_info"}, errors=errors, )
async def async_setup_entry(hass: core.HomeAssistant, entry: config_entries.ConfigEntry) -> bool: """Set up a bridge from a config entry.""" # check (and run) migrations if needed await check_migration(hass, entry) # setup the bridge instance bridge = HueBridge(hass, entry) if not await bridge.async_initialize_bridge(): return False # register Hue domain services async_register_services(hass) api = bridge.api # For backwards compat unique_id = normalize_bridge_id(api.config.bridge_id) if entry.unique_id is None: hass.config_entries.async_update_entry(entry, unique_id=unique_id) # For recovering from bug where we incorrectly assumed homekit ID = bridge ID # Remove this logic after Home Assistant 2022.4 elif entry.unique_id != unique_id: # Find entries with this unique ID other_entry = next( (entry for entry in hass.config_entries.async_entries(DOMAIN) if entry.unique_id == unique_id), None, ) if other_entry is None: # If no other entry, update unique ID of this entry ID. hass.config_entries.async_update_entry(entry, unique_id=unique_id) elif other_entry.source == config_entries.SOURCE_IGNORE: # There is another entry but it is ignored, delete that one and update this one hass.async_create_task( hass.config_entries.async_remove(other_entry.entry_id)) hass.config_entries.async_update_entry(entry, unique_id=unique_id) else: # There is another entry that already has the right unique ID. Delete this entry hass.async_create_task( hass.config_entries.async_remove(entry.entry_id)) return False # add bridge device to device registry device_registry = dr.async_get(hass) if bridge.api_version == 1: device_registry.async_get_or_create( config_entry_id=entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, api.config.mac_address)}, identifiers={(DOMAIN, api.config.bridge_id)}, manufacturer="Signify", name=api.config.name, model=api.config.model_id, sw_version=api.config.software_version, ) # create persistent notification if we found a bridge version with security vulnerability if (api.config.model_id == "BSB002" and api.config.software_version < "1935144040"): persistent_notification.async_create( hass, "Your Hue hub has a known security vulnerability ([CVE-2020-6007] " "(https://cve.circl.lu/cve/CVE-2020-6007)). " "Go to the Hue app and check for software updates.", "Signify Hue", "hue_hub_firmware", ) else: device_registry.async_get_or_create( config_entry_id=entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, api.config.mac_address)}, identifiers={ (DOMAIN, api.config.bridge_id), (DOMAIN, api.config.bridge_device.id), }, manufacturer=api.config.bridge_device.product_data. manufacturer_name, name=api.config.name, model=api.config.model_id, sw_version=api.config.software_version, ) return True
def user_message(self, identifier: str, title: str, message: str) -> None: """Create a message for user to UI.""" persistent_notification.async_create(self._hass, message, title, identifier)
async def process_wrong_login(request: Request) -> None: """Process a wrong login attempt. Increase failed login attempts counter for remote IP address. Add ip ban entry if failed login attempts exceeds threshold. """ hass = request.app["hass"] remote_addr = ip_address(request.remote) remote_host = request.remote with suppress(herror): remote_host, _, _ = await hass.async_add_executor_job( gethostbyaddr, request.remote) base_msg = f"Login attempt or request with invalid authentication from {remote_host} ({remote_addr})." # The user-agent is unsanitized input so we only include it in the log user_agent = request.headers.get("user-agent") log_msg = f"{base_msg} ({user_agent})" notification_msg = f"{base_msg} See the log for details." _LOGGER.warning(log_msg) persistent_notification.async_create(hass, notification_msg, "Login attempt failed", NOTIFICATION_ID_LOGIN) # Check if ban middleware is loaded if KEY_BANNED_IPS not in request.app or request.app[ KEY_LOGIN_THRESHOLD] < 1: return request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] += 1 # Supervisor IP should never be banned if "hassio" in hass.config.components: # pylint: disable=import-outside-toplevel from homeassistant.components import hassio if hassio.get_supervisor_ip() == str(remote_addr): return if (request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] >= request.app[KEY_LOGIN_THRESHOLD]): new_ban = IpBan(remote_addr) request.app[KEY_BANNED_IPS].append(new_ban) await hass.async_add_executor_job(update_ip_bans_config, hass.config.path(IP_BANS_FILE), new_ban) _LOGGER.warning("Banned IP %s for too many login attempts", remote_addr) persistent_notification.async_create( hass, f"Too many login attempts from {remote_addr}", "Banning IP address", NOTIFICATION_ID_BAN, )
def handle(request): """Handle incoming request.""" remote_addr = view.hass.http.get_real_ip(request) # Auth code verbose on purpose authenticated = False if view.hass.http.api_password is None: authenticated = True elif view.hass.http.is_trusted_ip(remote_addr): authenticated = True elif hmac.compare_digest(request.headers.get(HTTP_HEADER_HA_AUTH, ''), view.hass.http.api_password): # A valid auth header has been set authenticated = True elif hmac.compare_digest(request.GET.get(DATA_API_PASSWORD, ''), view.hass.http.api_password): authenticated = True if view.requires_auth and not authenticated: _LOGGER.warning( 'Login attempt or request with an invalid ' 'password from %s', remote_addr) persistent_notification.async_create( view.hass, 'Invalid password used from {}'.format(remote_addr), 'Login attempt failed', NOTIFICATION_ID_LOGIN) raise HTTPUnauthorized() request.authenticated = authenticated _LOGGER.info('Serving %s to %s (auth: %s)', request.path, remote_addr, authenticated) assert asyncio.iscoroutinefunction(handler) or is_callback(handler), \ "Handler should be a coroutine or a callback." result = handler(request, **request.match_info) if asyncio.iscoroutine(result): result = yield from result if isinstance(result, web.StreamResponse): # 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 if isinstance(result, str): result = result.encode('utf-8') elif result is None: result = b'' elif not isinstance(result, bytes): assert False, ('Result should be None, string, bytes or Response. ' 'Got: {}').format(result) return web.Response(body=result, status=status_code)
def handle(request): """Handle incoming request.""" if not view.hass.is_running: return web.Response(status=503) remote_addr = view.hass.http.get_real_ip(request) # Auth code verbose on purpose authenticated = False if view.hass.http.api_password is None: authenticated = True elif view.hass.http.is_trusted_ip(remote_addr): authenticated = True elif hmac.compare_digest(request.headers.get(HTTP_HEADER_HA_AUTH, ''), view.hass.http.api_password): # A valid auth header has been set authenticated = True elif hmac.compare_digest(request.GET.get(DATA_API_PASSWORD, ''), view.hass.http.api_password): authenticated = True if view.requires_auth and not authenticated: _LOGGER.warning('Login attempt or request with an invalid ' 'password from %s', remote_addr) persistent_notification.async_create( view.hass, 'Invalid password used from {}'.format(remote_addr), 'Login attempt failed', NOTIFICATION_ID_LOGIN) raise HTTPUnauthorized() request.authenticated = authenticated _LOGGER.info('Serving %s to %s (auth: %s)', request.path, remote_addr, authenticated) assert asyncio.iscoroutinefunction(handler) or is_callback(handler), \ "Handler should be a coroutine or a callback." result = handler(request, **request.match_info) if asyncio.iscoroutine(result): result = yield from result if isinstance(result, web.StreamResponse): # 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 if isinstance(result, str): result = result.encode('utf-8') elif result is None: result = b'' elif not isinstance(result, bytes): assert False, ('Result should be None, string, bytes or Response. ' 'Got: {}').format(result) return web.Response(body=result, status=status_code)
async def set_property_new(self, field="", params="", multiparams: list = []): try: if not self._cloud_write: if not multiparams: result = await self._try_command( f"Setting property for {self._name} failed.", self._device.set_property, field, params, ) if result: if field in self._state_attrs: self._state_attrs[field] = params return True else: result = await self._try_command( f"Setting property for {self._name} failed.", self._device.send, "set_properties", multiparams, ) if result: return True else: _LOGGER.info(f"Control {self._name} by cloud.") if not multiparams: did = self._cloud.get("did") spiid = self._mapping.get(field) or {} if not (spiid := self._mapping.get(field)): _LOGGER.error( f"Cannot control {self._name} by cloud because can't find {field} siid and piid from {self._mapping}" ) return False p = {**{'did': did, 'value': params}, **spiid} p = {'params': [p]} pp = json.dumps(p, separators=(',', ':')) _LOGGER.info(f"Control {self._name} params: {pp}") results = await self._cloud_instance.set_props( pp, self._cloud.get("server_location")) if results: if r := results.get('result'): for item in r: if item['code'] == 1: self._delay_update = LONG_DELAY elif item['code'] == -704042011: if self._available == True or self._available == None: if OFFLINE_NOTIFY: persistent_notification.async_create( self._hass, f"请注意,云端接入设备 **{self._name}** 已离线。", "Xiaomi MIoT - 设备离线") else: _LOGGER.warn( f"请注意,云端接入设备 **{self._name}** 已离线。" ) self._available = False self._skip_update = True return False elif item['code'] != 0: _LOGGER.error( f"Control {self._name} by cloud failed: {r}" ) return False if field in self._state_attrs: self._state_attrs[field] = params self._skip_update = True return True return False else:
async def async_update(self): """Fetch state from the device.""" # On state change some devices doesn't provide the new state immediately. if self._update_instant is False or self._skip_update: self._skip_update = False self._delay_update = 0 return if self._delay_update != 0: await asyncio.sleep(self._delay_update) self._delay_update = 0 try: if not self._cloud: response = await self.hass.async_add_job( self._device.get_properties_for_mapping) self._available = True statedict = {} count4004 = 0 for r in response: if 'a_l_' in r['did']: continue if r['code'] == 0: try: f = self._ctrl_params_new[r['did']]['value_ratio'] statedict[r['did']] = round(r['value'] * f, 3) except (KeyError, TypeError, IndexError): if (('status' in r['did'] and 'switch_status' not in r['did']) \ or 'fault' in r['did']) \ and type(r['value']) == int: if r['did'] in self._ctrl_params_new: if s := self.get_key_by_value( self._ctrl_params_new[r['did']], r['value']): statedict[r['did']] = s continue statedict[r['did']] = r['value'] elif r['code'] == 9999: persistent_notification.async_create( self._hass, f"您添加的设备: **{self._name}** ,\n" f"在获取个状态时,\n" f"返回 **-9999** 错误。\n" "请考虑通过云端接入此设备来解决此问题。", "设备不支持本地接入") else: statedict[r['did']] = None if r['code'] == -4004: count4004 += 1 else: _LOGGER.info( "Error getting %s 's property '%s' (code: %s)", self._name, r['did'], r['code']) if count4004 == len(response): self._assumed_state = True self._skip_update = True # _LOGGER.warn("设备不支持状态反馈") if time.time() - self._last_notified > NOTIFY_INTERVAL: persistent_notification.async_create( self._hass, f"您添加的设备: **{self._name}** ,\n" f"在获取 {count4004} 个状态时,\n" f"全部返回 **-4004** 错误。\n" "请考虑通过云端接入此设备来解决此问题。", "设备可能不受支持") self._last_notified = time.time()
async def _async_learn_rf_command(self, command): """Learn a radiofrequency command.""" device = self._device try: await device.async_request(device.api.sweep_frequency) except (BroadlinkException, OSError) as err: _LOGGER.debug("Failed to sweep frequency: %s", err) raise persistent_notification.async_create( self.hass, f"Press and hold the '{command}' button.", title="Sweep frequency", notification_id="sweep_frequency", ) try: start_time = dt.utcnow() while (dt.utcnow() - start_time) < LEARNING_TIMEOUT: await asyncio.sleep(1) found = await device.async_request(device.api.check_frequency) if found: break else: await device.async_request(device.api.cancel_sweep_frequency) raise TimeoutError( "No radiofrequency found within " f"{LEARNING_TIMEOUT.total_seconds()} seconds" ) finally: persistent_notification.async_dismiss( self.hass, notification_id="sweep_frequency" ) await asyncio.sleep(1) try: await device.async_request(device.api.find_rf_packet) except (BroadlinkException, OSError) as err: _LOGGER.debug("Failed to enter learning mode: %s", err) raise persistent_notification.async_create( self.hass, f"Press the '{command}' button again.", title="Learn command", notification_id="learn_command", ) try: start_time = dt.utcnow() while (dt.utcnow() - start_time) < LEARNING_TIMEOUT: await asyncio.sleep(1) try: code = await device.async_request(device.api.check_data) except (ReadError, StorageError): continue return b64encode(code).decode("utf8") raise TimeoutError( "No radiofrequency code received within " f"{LEARNING_TIMEOUT.total_seconds()} seconds" ) finally: persistent_notification.async_dismiss( self.hass, notification_id="learn_command" )
return None return res.error_str @callback def async_notify_setup_error(hass: HomeAssistant, component: str, display_link: str | None = None) -> None: """Print a persistent notification. This method must be run in the event loop. """ # pylint: disable=import-outside-toplevel from homeassistant.components import persistent_notification if (errors := hass.data.get(DATA_PERSISTENT_ERRORS)) is None: errors = hass.data[DATA_PERSISTENT_ERRORS] = {} errors[component] = errors.get(component) or display_link message = "The following integrations and platforms could not be set up:\n\n" for name, link in errors.items(): part = f"[{name}]({link})" if link else name message += f" - {part}\n" message += "\nPlease check your config and [logs](/config/logs)." persistent_notification.async_create(hass, message, "Invalid config", "invalid_config")
async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the fan from config.""" if DATA_KEY not in hass.data: hass.data[DATA_KEY] = {} host = config.get(CONF_HOST) token = config.get(CONF_TOKEN) mapping = config.get(CONF_MAPPING) params = config.get(CONF_CONTROL_PARAMS) mappingnew = {} main_mi_type = None other_mi_type = [] for t in MAP[TYPE]: if mapping.get(t): other_mi_type.append(t) if 'main' in (params.get(t) or ""): main_mi_type = t try: other_mi_type.remove(main_mi_type) except: pass if main_mi_type or type(params) == OrderedDict: for k, v in mapping.items(): for kk, vv in v.items(): mappingnew[f"{k[:10]}_{kk}"] = vv if 'a_l' not in mapping.keys(): persistent_notification.async_create( hass, f"为了支持更多种类小爱,配置参数有变动,\n" f"请删除您的 **{config.get(CONF_NAME)}** 重新配置。谢谢\n", "Xiaomi MIoT") _LOGGER.info("Initializing %s with host %s (token %s...)", config.get(CONF_NAME), host, token[:5]) try: if type(params) == OrderedDict: miio_device = MiotDevice(ip=host, token=token, mapping=mapping) else: miio_device = MiotDevice(ip=host, token=token, mapping=mappingnew) device_info = dev_info(host, token, "", "") device = MiotMediaPlayer(miio_device, config, device_info, hass, main_mi_type) except DeviceException as de: _LOGGER.warn(de) raise PlatformNotReady _LOGGER.info(f"{main_mi_type} is the main device of {host}.") hass.data[DOMAIN]['miot_main_entity'][host] = device hass.data[DOMAIN]['entities'][device.unique_id] = device async_add_devices([device], update_before_add=True) @asyncio.coroutine def async_service_handler(service): """Map services to methods on XiaomiMiioDevice.""" method = SERVICE_TO_METHOD.get(service.service) params = { key: value for key, value in service.data.items() if key != ATTR_ENTITY_ID } entity_ids = service.data.get(ATTR_ENTITY_ID) if entity_ids: devices = [ device for device in hass.data[DOMAIN]['entities'].values() if device.entity_id in entity_ids ] else: # devices = hass.data[DOMAIN]['entities'].values() _LOGGER.error("No entity_id specified.") update_tasks = [] for device in devices: yield from getattr(device, method["method"])(**params) update_tasks.append(device.async_update_ha_state(True)) if update_tasks: yield from asyncio.wait(update_tasks, loop=hass.loop) for service in SERVICE_TO_METHOD: schema = SERVICE_TO_METHOD[service].get("schema", SERVICE_SCHEMA) hass.services.async_register(DOMAIN, service, async_service_handler, schema=schema) else: _LOGGER.error("media player只能作为主设备!") pass
async def async_update(self): """Fetch state from the device.""" # On state change some devices doesn't provide the new state immediately. if self._update_instant is False or self._skip_update: self._skip_update = False return if self._delay_update != 0: await asyncio.sleep(self._delay_update) self._delay_update = 0 try: if not self._cloud: response = await self.hass.async_add_job( self._device.get_properties_for_mapping) self._available = True statedict = {} count4004 = 0 for r in response: if r['code'] == 0: try: f = self._ctrl_params_new[r['did']]['value_ratio'] statedict[r['did']] = round(r['value'] * f, 3) except (KeyError, TypeError, IndexError): statedict[r['did']] = r['value'] elif r['code'] == 9999: persistent_notification.async_create( self._hass, f"您添加的设备: **{self._name}** ,\n" f"在获取个状态时,\n" f"返回 **-9999** 错误。\n" "请考虑通过云端接入此设备来解决此问题。", "设备不支持本地接入") else: statedict[r['did']] = None if r['code'] == -4004: count4004 += 1 else: _LOGGER.info( "Error getting %s 's property '%s' (code: %s)", self._name, r['did'], r['code']) if count4004 == len(response): self._assumed_state = True self._skip_update = True # _LOGGER.warn("设备不支持状态反馈") if not self._notified: persistent_notification.async_create( self._hass, f"您添加的设备: **{self._name}** ,\n" f"在获取 {count4004} 个状态时,\n" f"全部返回 **-4004** 错误。\n" "请考虑通过云端接入此设备来解决此问题。", "设备可能不受支持") self._notified = True else: _LOGGER.info(f"{self._name} is updating from cloud.") data1 = {} data1['datasource'] = 1 data1['params'] = [] for value in self._mapping.values(): if 'aiid' not in value: data1['params'].append({ **{ 'did': self._cloud.get("did") }, **value }) data2 = json.dumps(data1, separators=(',', ':')) a = await self._cloud_instance.get_props( data2, self._cloud.get("server_location")) dict1 = {} statedict = {} if a: self._available = True for item in a['result']: if dict1.get(item['siid']): dict1[item['siid']][item['piid']] = item.get( 'value') else: dict1[item['siid']] = {} dict1[item['siid']][item['piid']] = item.get( 'value') for key, value in self._mapping.items(): try: statedict[key] = dict1[value['siid']][ value['piid']] except KeyError: statedict[key] = None else: pass self._state_attrs.update(statedict) await self.publish_updates() except DeviceException as ex: self._available = False _LOGGER.error("Got exception while fetching %s 's state: %s", self._name, ex)
async def async_step_localinfo(self, user_input=None): # 2. 手动接入,本地通信信息 """Handle a flow initialized by the user.""" errors = {} # Check if already configured # await self.async_set_unique_id(DOMAIN) # self._abort_if_unique_id_configured() if user_input is not None: self._name = user_input[CONF_NAME] self._host = user_input[CONF_HOST] if user_input[CONF_TOKEN] == '0': user_input[CONF_TOKEN] = '0' * 32 self._token = user_input[CONF_TOKEN] self._input2 = {**self._input2, **user_input} device = MiioDevice(self._host, self._token) try: self._info = device.info() except DeviceException: errors['base'] = 'cannot_connect' # except ValueError: # errors['base'] = 'value_error' if self._info is not None: unique_id = format_mac(self._info.mac_address) # await self.async_set_unique_id(unique_id) for entry in self._async_current_entries(): if entry.unique_id == unique_id: persistent_notification.async_create( self.hass, f"您新添加的设备: **{self._name}** ,\n" f"其 MAC 地址与现有的某个设备相同。\n" f"只是通知,不会造成任何影响。", "设备可能重复") break self._abort_if_unique_id_configured() d = self._info.raw self._model = d['model'] device_info = (f"Model: {d['model']}\n" f"Firmware: {d['fw_ver']}\n" f"MAC: {d['mac']}\n") self._info = await guess_mp_from_model(self.hass, self._model) if self._info and self._info.get('mapping') != "{}": device_info += "\n已经自动发现配置参数。\n如无特殊需要,无需修改下列内容。\n" devtype_default = self._info.get('device_type') mp = self._info.get('mapping') prm = self._info.get('params') mapping_default = mp params_default = prm else: device_info += f"很抱歉,未能自动发现配置参数。但这不代表您的设备不受支持。\n您可以[手工编写配置](https://github.com/ha0y/xiaomi_miot_raw/#文件配置法),或者将型号 **{self._model}** 报告给作者。" devtype_default = ['switch'] mapping_default = '{"switch":{"switch_status":{"siid":2,"piid":1}}}' params_default = '{"switch":{"switch_status":{"power_on":true,"power_off":false}}}' if not self._non_interactive: return self.async_show_form( step_id="devinfo", data_schema=vol.Schema( { vol.Required('devtype', default=devtype_default): cv.multi_select(SUPPORTED_DOMAINS), vol.Required(CONF_MAPPING, default=mapping_default): str, vol.Required(CONF_CONTROL_PARAMS, default=params_default): str, vol.Optional('cloud_read'): bool, vol.Optional('cloud_write'): bool, }), description_placeholders={"device_info": device_info}, errors=errors, ) else: return await self.async_step_devinfo({ 'devtype': devtype_default, CONF_MAPPING: mapping_default, CONF_CONTROL_PARAMS: params_default, 'cloud_read': True, 'cloud_write': True, }) else: return await self.async_step_xiaoai( {CONF_MODEL: self._model} if self._model else None) return self.async_show_form( step_id="localinfo", data_schema=vol.Schema({ vol.Required(CONF_NAME): str, vol.Required(CONF_HOST, default='192.168.'): str, vol.Required(CONF_TOKEN): str, }), # description_placeholders={"device_info": "device_info"}, errors=errors, )