Beispiel #1
0
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)
Beispiel #2
0
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')
Beispiel #3
0
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)
Beispiel #4
0
 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')
Beispiel #6
0
    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)
Beispiel #7
0
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}",
    )
Beispiel #9
0
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)
Beispiel #10
0
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
Beispiel #12
0
    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()
Beispiel #13
0
    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()
Beispiel #14
0
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
Beispiel #15
0
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)
Beispiel #16
0
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")
Beispiel #17
0
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}",
    )
Beispiel #18
0
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)
Beispiel #19
0
 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)
Beispiel #20
0
    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()
Beispiel #21
0
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)
Beispiel #22
0
    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"
            )
Beispiel #23
0
    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()
Beispiel #24
0
                    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)
Beispiel #25
0
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,
        )
Beispiel #27
0
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
Beispiel #28
0
 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)
Beispiel #29
0
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,
        )
Beispiel #30
0
    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)
Beispiel #31
0
    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)
Beispiel #32
0
 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:
Beispiel #33
0
    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()
Beispiel #34
0
    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"
            )
Beispiel #35
0
        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")
Beispiel #36
0
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)
Beispiel #38
0
    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,
        )