Esempio n. 1
0
    def __init__(self, config):
        """Initialize the scanner."""
        self.last_results = []
        self.host = config[CONF_HOST]
        self.username = config[CONF_USERNAME]
        self.password = config[CONF_PASSWORD]
        self.success_init = True

        # Establish a connection to the FRITZ!Box.
        try:
            self.fritz_box = FritzHosts(address=self.host,
                                        user=self.username,
                                        password=self.password)
        except (ValueError, TypeError):
            self.fritz_box = None

        # At this point it is difficult to tell if a connection is established.
        # So just check for null objects.
        if self.fritz_box is None or not self.fritz_box.modelname:
            self.success_init = False

        if self.success_init:
            _LOGGER.info("Successfully connected to %s",
                         self.fritz_box.modelname)
            self._update_info()
        else:
            _LOGGER.error(
                "Failed to establish connection to FRITZ!Box with IP: %s",
                self.host)
Esempio n. 2
0
 async def _async_update_data(self) -> None:
     """Update FritzboxTools data."""
     try:
         self.fritz_hosts = FritzHosts(fc=self.connection)
         await self.async_scan_devices()
     except (FritzSecurityError, FritzConnectionException) as ex:
         raise update_coordinator.UpdateFailed from ex
Esempio n. 3
0
    def setup(self) -> None:
        """Set up FritzboxTools class."""
        self.connection = FritzConnection(
            address=self.host,
            port=self.port,
            user=self.username,
            password=self.password,
            timeout=60.0,
            pool_maxsize=30,
        )

        if not self.connection:
            _LOGGER.error("Unable to establish a connection with %s", self.host)
            return

        self.fritz_hosts = FritzHosts(fc=self.connection)
        self.fritz_status = FritzStatus(fc=self.connection)
        info = self.connection.call_action("DeviceInfo:1", "GetInfo")
        if not self._unique_id:
            self._unique_id = info["NewSerialNumber"]

        self._model = info.get("NewModelName")
        self._current_firmware = info.get("NewSoftwareVersion")

        self._update_available, self._latest_firmware = self._update_device_info()
        self.device_is_router = "WANIPConn1" in self.connection.services
Esempio n. 4
0
    async def async_start(self, options):
        """Start FritzHosts connection."""
        self.fritz_hosts = FritzHosts(fc=self.connection)
        self._options = options
        await self.opp.async_add_executor_job(self.scan_devices)

        self._cancel_scan = async_track_time_interval(
            self.opp, self.scan_devices, timedelta(seconds=TRACKER_SCAN_INTERVAL)
        )
Esempio n. 5
0
    async def async_start(self, options: MappingProxyType[str, Any]) -> None:
        """Start FritzHosts connection."""
        self.fritz_hosts = FritzHosts(fc=self.connection)
        self._options = options
        await self.hass.async_add_executor_job(self.scan_devices)

        self._cancel_scan = async_track_time_interval(
            self.hass, self.scan_devices,
            timedelta(seconds=TRACKER_SCAN_INTERVAL))
Esempio n. 6
0
def hosts():
    active_hosts = []
    fh = FritzHosts(address=ADDRESS, password=PASSWORD)
    hosts = fh.get_hosts_info()
    for index, host in enumerate(hosts, start=1):
        # print(f"{host['name']:<28} {host['status']}")
        if host['status']:
            if host['name'] in SCAN_HOSTS:
                active_hosts.append(host['name'])

    return active_hosts
Esempio n. 7
0
def getHOSTStatus():
    fh = FritzHosts(fc)
    hosts = fh.get_hosts_info()
    activehosts = fh.get_active_hosts()
    print("**********************************\n")
    print("Overview known hosts:")
    print("Following %s hosts are known!\n" % (len(hosts)))
    print("Following %s hosts are active in your network:" %
          (len(activehosts)))
    print("IP-Address         Name                      Status")
    print("---------------------------------------------------")

    for i in range(0, len(hosts) - 1):
        if hosts[i]["status"] == True:
            ipaddress = hosts[i]["ip"]
            hostname = hosts[i]["name"]
            hoststatus = hosts[i]["status"]
            print(f"{ipaddress:18} {hostname:25} - {hoststatus}")
Esempio n. 8
0
 def _poll_loop(self):
     self._current_data = FritzBoxData()
     last_hosts: Optional[datetime] = None
     while self._running:
         try:
             self._current_data.status = FritzStatus(address=self._address, password=self._password)
             if not last_hosts or (datetime.now() - last_hosts) / timedelta(seconds=10) >= 1:
                 self.current_data.hosts = FritzHosts(address=self._address, password=self._password)
                 self.current_data.wlan = FritzWLAN(address=self._address, password=self._password)
                 last_hosts = datetime.now()
         except IOError as e:
             log.warning(f"Failed to get FritzBox data: {e}")
         self._notify_listeners()
         self._queue.get()
Esempio n. 9
0
def check_hosts(status):
    home = False
    hosts = FritzHosts(fritz).get_active_hosts()
    # Read data from Fritz!Box with fritzconnection
    # check if given MAC addresses stored in .env are online
    # This could be a bit more readable though...
    for host in hosts:
        mac = host.get('mac')
        if mac in maclist:
            if status != "PAUSE":
                logger.info("Found {} - {}".format(mac, host.get('name')))
            home = True

    return home
Esempio n. 10
0
class FritzBoxTools(update_coordinator.DataUpdateCoordinator):
    """FrtizBoxTools class."""
    def __init__(
        self,
        hass: HomeAssistant,
        password: str,
        username: str = DEFAULT_USERNAME,
        host: str = DEFAULT_HOST,
        port: int = DEFAULT_PORT,
    ) -> None:
        """Initialize FritzboxTools class."""
        super().__init__(
            hass=hass,
            logger=_LOGGER,
            name=f"{DOMAIN}-{host}-coordinator",
            update_interval=timedelta(seconds=30),
        )

        self._devices: dict[str, FritzDevice] = {}
        self._options: MappingProxyType[str, Any] | None = None
        self._unique_id: str | None = None
        self.connection: FritzConnection = None
        self.fritz_hosts: FritzHosts = None
        self.fritz_status: FritzStatus = None
        self.hass = hass
        self.host = host
        self.password = password
        self.port = port
        self.username = username
        self._mac: str | None = None
        self._model: str | None = None
        self._current_firmware: str | None = None
        self._latest_firmware: str | None = None
        self._update_available: bool = False

    async def async_setup(self,
                          options: MappingProxyType[str, Any] | None = None
                          ) -> None:
        """Wrap up FritzboxTools class setup."""
        self._options = options
        await self.hass.async_add_executor_job(self.setup)

    def setup(self) -> None:
        """Set up FritzboxTools class."""
        self.connection = FritzConnection(
            address=self.host,
            port=self.port,
            user=self.username,
            password=self.password,
            timeout=60.0,
            pool_maxsize=30,
        )

        if not self.connection:
            _LOGGER.error("Unable to establish a connection with %s",
                          self.host)
            return

        self.fritz_status = FritzStatus(fc=self.connection)
        info = self.connection.call_action("DeviceInfo:1", "GetInfo")
        if not self._unique_id:
            self._unique_id = info["NewSerialNumber"]

        self._model = info.get("NewModelName")
        self._current_firmware = info.get("NewSoftwareVersion")

        self._update_available, self._latest_firmware = self._update_device_info(
        )

    @callback
    async def _async_update_data(self) -> None:
        """Update FritzboxTools data."""
        try:
            self.fritz_hosts = FritzHosts(fc=self.connection)
            await self.async_scan_devices()
        except (FritzSecurityError, FritzConnectionException) as ex:
            raise update_coordinator.UpdateFailed from ex

    @property
    def unique_id(self) -> str:
        """Return unique id."""
        if not self._unique_id:
            raise ClassSetupMissing()
        return self._unique_id

    @property
    def model(self) -> str:
        """Return device model."""
        if not self._model:
            raise ClassSetupMissing()
        return self._model

    @property
    def current_firmware(self) -> str:
        """Return current SW version."""
        if not self._current_firmware:
            raise ClassSetupMissing()
        return self._current_firmware

    @property
    def latest_firmware(self) -> str | None:
        """Return latest SW version."""
        return self._latest_firmware

    @property
    def update_available(self) -> bool:
        """Return if new SW version is available."""
        return self._update_available

    @property
    def mac(self) -> str:
        """Return device Mac address."""
        if not self._unique_id:
            raise ClassSetupMissing()
        return self._unique_id

    @property
    def devices(self) -> dict[str, FritzDevice]:
        """Return devices."""
        return self._devices

    @property
    def signal_device_new(self) -> str:
        """Event specific per FRITZ!Box entry to signal new device."""
        return f"{DOMAIN}-device-new-{self._unique_id}"

    @property
    def signal_device_update(self) -> str:
        """Event specific per FRITZ!Box entry to signal updates in devices."""
        return f"{DOMAIN}-device-update-{self._unique_id}"

    def _update_hosts_info(self) -> list[HostInfo]:
        """Retrieve latest hosts information from the FRITZ!Box."""
        try:
            return self.fritz_hosts.get_hosts_info(
            )  # type: ignore [no-any-return]
        except Exception as ex:  # pylint: disable=[broad-except]
            if not self.hass.is_stopping:
                raise HomeAssistantError("Error refreshing hosts info") from ex
        return []

    def _update_device_info(self) -> tuple[bool, str | None]:
        """Retrieve latest device information from the FRITZ!Box."""
        version = self.connection.call_action(
            "UserInterface1", "GetInfo").get("NewX_AVM-DE_Version")
        return bool(version), version

    async def async_scan_devices(self, now: datetime | None = None) -> None:
        """Wrap up FritzboxTools class scan."""
        await self.hass.async_add_executor_job(self.scan_devices, now)

    def scan_devices(self, now: datetime | None = None) -> None:
        """Scan for new devices and return a list of found device ids."""
        _LOGGER.debug("Checking devices for FRITZ!Box router %s", self.host)

        _default_consider_home = DEFAULT_CONSIDER_HOME.total_seconds()
        if self._options:
            consider_home = self._options.get(CONF_CONSIDER_HOME,
                                              _default_consider_home)
        else:
            consider_home = _default_consider_home

        new_device = False
        for known_host in self._update_hosts_info():
            if not known_host.get("mac"):
                continue

            dev_mac = known_host["mac"]
            dev_name = known_host["name"]
            dev_ip = known_host["ip"]
            dev_home = known_host["status"]
            dev_wan_access = True
            if dev_ip:
                wan_access = self.connection.call_action(
                    "X_AVM-DE_HostFilter:1",
                    "GetWANAccessByIP",
                    NewIPv4Address=dev_ip,
                )
                if wan_access:
                    dev_wan_access = not wan_access.get("NewDisallow")

            dev_info = Device(dev_mac, dev_ip, dev_name, dev_wan_access)

            if dev_mac in self._devices:
                self._devices[dev_mac].update(dev_info, dev_home,
                                              consider_home)
            else:
                device = FritzDevice(dev_mac, dev_name)
                device.update(dev_info, dev_home, consider_home)
                self._devices[dev_mac] = device
                new_device = True

        dispatcher_send(self.hass, self.signal_device_update)
        if new_device:
            dispatcher_send(self.hass, self.signal_device_new)

        _LOGGER.debug("Checking host info for FRITZ!Box router %s", self.host)
        self._update_available, self._latest_firmware = self._update_device_info(
        )

    async def async_trigger_firmware_update(self) -> bool:
        """Trigger firmware update."""
        results = await self.hass.async_add_executor_job(
            self.connection.call_action, "UserInterface:1",
            "X_AVM-DE_DoUpdate")
        return cast(bool, results["NewX_AVM-DE_UpdateState"])

    async def async_trigger_reboot(self) -> None:
        """Trigger device reboot."""
        await self.hass.async_add_executor_job(self.connection.call_action,
                                               "DeviceConfig1", "Reboot")

    async def async_trigger_reconnect(self) -> None:
        """Trigger device reconnect."""
        await self.hass.async_add_executor_job(self.connection.call_action,
                                               "WANIPConn1",
                                               "ForceTermination")

    async def service_fritzbox(self, service_call: ServiceCall,
                               config_entry: ConfigEntry) -> None:
        """Define FRITZ!Box services."""
        _LOGGER.debug("FRITZ!Box router: %s", service_call.service)

        if not self.connection:
            raise HomeAssistantError("Unable to establish a connection")

        try:
            if service_call.service == SERVICE_REBOOT:
                _LOGGER.warning(
                    'Service "fritz.reboot" is deprecated, please use the corresponding button entity instead'
                )
                await self.hass.async_add_executor_job(
                    self.connection.call_action, "DeviceConfig1", "Reboot")
                return

            if service_call.service == SERVICE_RECONNECT:
                _LOGGER.warning(
                    'Service "fritz.reconnect" is deprecated, please use the corresponding button entity instead'
                )
                await self.hass.async_add_executor_job(
                    self.connection.call_action,
                    "WANIPConn1",
                    "ForceTermination",
                )
                return

            if service_call.service == SERVICE_CLEANUP:
                device_hosts_list: list = await self.hass.async_add_executor_job(
                    self.fritz_hosts.get_hosts_info)

        except (FritzServiceError, FritzActionError) as ex:
            raise HomeAssistantError("Service or parameter unknown") from ex
        except FritzConnectionException as ex:
            raise HomeAssistantError("Service not supported") from ex

        entity_reg: EntityRegistry = (
            await self.hass.helpers.entity_registry.async_get_registry())

        ha_entity_reg_list: list[
            RegistryEntry] = self.hass.helpers.entity_registry.async_entries_for_config_entry(
                entity_reg, config_entry.entry_id)
        entities_removed: bool = False

        device_hosts_macs = {device["mac"] for device in device_hosts_list}

        for entry in ha_entity_reg_list:
            if (not _cleanup_entity_filter(entry)
                    or entry.unique_id.split("_")[0] in device_hosts_macs):
                continue
            _LOGGER.info("Removing entity: %s", entry.name
                         or entry.original_name)
            entity_reg.async_remove(entry.entity_id)
            entities_removed = True

        if entities_removed:
            self._async_remove_empty_devices(entity_reg, config_entry)

    @callback
    def _async_remove_empty_devices(self, entity_reg: EntityRegistry,
                                    config_entry: ConfigEntry) -> None:
        """Remove devices with no entities."""

        device_reg = async_get(self.hass)
        device_list = async_entries_for_config_entry(device_reg,
                                                     config_entry.entry_id)
        for device_entry in device_list:
            if not async_entries_for_device(
                    entity_reg,
                    device_entry.id,
                    include_disabled_entities=True,
            ):
                _LOGGER.info("Removing device: %s", device_entry.name)
                device_reg.async_remove_device(device_entry.id)
Esempio n. 11
0
class FritzBoxTools:
    """FrtizBoxTools class."""
    def __init__(
        self,
        hass,
        password,
        username=DEFAULT_USERNAME,
        host=DEFAULT_HOST,
        port=DEFAULT_PORT,
    ):
        """Initialize FritzboxTools class."""
        self._cancel_scan = None
        self._device_info = None
        self._devices: dict[str, Any] = {}
        self._unique_id = None
        self.connection = None
        self.fritzhosts = None
        self.fritzstatus = None
        self.hass = hass
        self.host = host
        self.password = password
        self.port = port
        self.username = username

    async def async_setup(self):
        """Wrap up FritzboxTools class setup."""
        return await self.hass.async_add_executor_job(self.setup)

    def setup(self):
        """Set up FritzboxTools class."""
        self.connection = FritzConnection(
            address=self.host,
            port=self.port,
            user=self.username,
            password=self.password,
            timeout=60.0,
        )

        self.fritzstatus = FritzStatus(fc=self.connection)
        if self._unique_id is None:
            self._unique_id = self.connection.call_action(
                "DeviceInfo:1", "GetInfo")["NewSerialNumber"]

        self._device_info = self._fetch_device_info()

    async def async_start(self):
        """Start FritzHosts connection."""
        self.fritzhosts = FritzHosts(fc=self.connection)

        await self.hass.async_add_executor_job(self.scan_devices)

        self._cancel_scan = async_track_time_interval(
            self.hass, self.scan_devices,
            timedelta(seconds=TRACKER_SCAN_INTERVAL))

    @callback
    def async_unload(self):
        """Unload FritzboxTools class."""
        _LOGGER.debug("Unloading FRITZ!Box router integration")
        if self._cancel_scan is not None:
            self._cancel_scan()
            self._cancel_scan = None

    @property
    def unique_id(self):
        """Return unique id."""
        return self._unique_id

    @property
    def fritzbox_model(self):
        """Return model."""
        return self._device_info["model"].replace("FRITZ!Box ", "")

    @property
    def device_info(self):
        """Return device info."""
        return self._device_info

    @property
    def devices(self) -> dict[str, Any]:
        """Return devices."""
        return self._devices

    @property
    def signal_device_new(self) -> str:
        """Event specific per FRITZ!Box entry to signal new device."""
        return f"{DOMAIN}-device-new-{self._unique_id}"

    @property
    def signal_device_update(self) -> str:
        """Event specific per FRITZ!Box entry to signal updates in devices."""
        return f"{DOMAIN}-device-update-{self._unique_id}"

    def _update_info(self):
        """Retrieve latest information from the FRITZ!Box."""
        return self.fritzhosts.get_hosts_info()

    def scan_devices(self, now: datetime | None = None) -> None:
        """Scan for new devices and return a list of found device ids."""
        _LOGGER.debug("Checking devices for FRITZ!Box router %s", self.host)

        new_device = False
        for known_host in self._update_info():
            if not known_host.get("mac"):
                continue

            dev_mac = known_host["mac"]
            dev_name = known_host["name"]
            dev_ip = known_host["ip"]
            dev_home = known_host["status"]

            dev_info = Device(dev_mac, dev_ip, dev_name)

            if dev_mac in self._devices:
                self._devices[dev_mac].update(dev_info, dev_home)
            else:
                device = FritzDevice(dev_mac)
                device.update(dev_info, dev_home)
                self._devices[dev_mac] = device
                new_device = True

        async_dispatcher_send(self.hass, self.signal_device_update)
        if new_device:
            async_dispatcher_send(self.hass, self.signal_device_new)

    def _fetch_device_info(self):
        """Fetch device info."""
        info = self.connection.call_action("DeviceInfo:1", "GetInfo")

        dev_info = {}
        dev_info["identifiers"] = {
            # Serial numbers are unique identifiers within a specific domain
            (DOMAIN, self.unique_id)
        }
        dev_info["manufacturer"] = "AVM"

        if dev_name := info.get("NewName"):
            dev_info["name"] = dev_name
        if dev_model := info.get("NewModelName"):
            dev_info["model"] = dev_model
Esempio n. 12
0
class FritzBoxTools:
    """FrtizBoxTools class."""
    def __init__(
        self,
        hass: HomeAssistant,
        password: str,
        username: str = DEFAULT_USERNAME,
        host: str = DEFAULT_HOST,
        port: int = DEFAULT_PORT,
    ) -> None:
        """Initialize FritzboxTools class."""
        self._cancel_scan: CALLBACK_TYPE | None = None
        self._devices: dict[str, Any] = {}
        self._options: MappingProxyType[str, Any] | None = None
        self._unique_id: str | None = None
        self.connection: FritzConnection = None
        self.fritz_hosts: FritzHosts = None
        self.fritz_profiles: dict[str, FritzProfileSwitch] = {}
        self.fritz_status: FritzStatus = None
        self.hass = hass
        self.host = host
        self.password = password
        self.port = port
        self.username = username
        self._mac: str | None = None
        self._model: str | None = None
        self._sw_version: str | None = None

    async def async_setup(self) -> None:
        """Wrap up FritzboxTools class setup."""
        await self.hass.async_add_executor_job(self.setup)

    def setup(self) -> None:
        """Set up FritzboxTools class."""
        self.connection = FritzConnection(
            address=self.host,
            port=self.port,
            user=self.username,
            password=self.password,
            timeout=60.0,
        )

        self.fritz_status = FritzStatus(fc=self.connection)
        info = self.connection.call_action("DeviceInfo:1", "GetInfo")
        if not self._unique_id:
            self._unique_id = info["NewSerialNumber"]

        self._model = info.get("NewModelName")
        self._sw_version = info.get("NewSoftwareVersion")

        self.fritz_profiles = {
            profile: FritzProfileSwitch("http://" + self.host, self.username,
                                        self.password, profile)
            for profile in get_all_profiles(self.host, self.username,
                                            self.password)
        }

    async def async_start(self, options: MappingProxyType[str, Any]) -> None:
        """Start FritzHosts connection."""
        self.fritz_hosts = FritzHosts(fc=self.connection)
        self._options = options
        await self.hass.async_add_executor_job(self.scan_devices)

        self._cancel_scan = async_track_time_interval(
            self.hass, self.scan_devices,
            timedelta(seconds=TRACKER_SCAN_INTERVAL))

    @callback
    def async_unload(self) -> None:
        """Unload FritzboxTools class."""
        _LOGGER.debug("Unloading FRITZ!Box router integration")
        if self._cancel_scan is not None:
            self._cancel_scan()
            self._cancel_scan = None

    @property
    def unique_id(self) -> str:
        """Return unique id."""
        if not self._unique_id:
            raise ClassSetupMissing()
        return self._unique_id

    @property
    def model(self) -> str:
        """Return device model."""
        if not self._model:
            raise ClassSetupMissing()
        return self._model

    @property
    def sw_version(self) -> str:
        """Return SW version."""
        if not self._sw_version:
            raise ClassSetupMissing()
        return self._sw_version

    @property
    def mac(self) -> str:
        """Return device Mac address."""
        if not self._unique_id:
            raise ClassSetupMissing()
        return self._unique_id

    @property
    def devices(self) -> dict[str, Any]:
        """Return devices."""
        return self._devices

    @property
    def signal_device_new(self) -> str:
        """Event specific per FRITZ!Box entry to signal new device."""
        return f"{DOMAIN}-device-new-{self._unique_id}"

    @property
    def signal_device_update(self) -> str:
        """Event specific per FRITZ!Box entry to signal updates in devices."""
        return f"{DOMAIN}-device-update-{self._unique_id}"

    def _update_info(self) -> list[HostInfo]:
        """Retrieve latest information from the FRITZ!Box."""
        return self.fritz_hosts.get_hosts_info()

    def scan_devices(self, now: datetime | None = None) -> None:
        """Scan for new devices and return a list of found device ids."""
        _LOGGER.debug("Checking devices for FRITZ!Box router %s", self.host)

        if self._options:
            consider_home = self._options.get(
                CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME.total_seconds())
        else:
            consider_home = DEFAULT_CONSIDER_HOME

        new_device = False
        for known_host in self._update_info():
            if not known_host.get("mac"):
                continue

            dev_mac = known_host["mac"]
            dev_name = known_host["name"]
            dev_ip = known_host["ip"]
            dev_home = known_host["status"]

            dev_info = Device(dev_mac, dev_ip, dev_name)

            if dev_mac in self._devices:
                self._devices[dev_mac].update(dev_info, dev_home,
                                              consider_home)
            else:
                device = FritzDevice(dev_mac, dev_name)
                device.update(dev_info, dev_home, consider_home)
                self._devices[dev_mac] = device
                new_device = True

        dispatcher_send(self.hass, self.signal_device_update)
        if new_device:
            dispatcher_send(self.hass, self.signal_device_new)

    async def service_fritzbox(self, service: str) -> None:
        """Define FRITZ!Box services."""
        _LOGGER.debug("FRITZ!Box router: %s", service)

        if not self.connection:
            raise HomeAssistantError("Unable to establish a connection")

        try:
            if service == SERVICE_REBOOT:
                await self.hass.async_add_executor_job(
                    self.connection.call_action, "DeviceConfig1", "Reboot")
            elif service == SERVICE_RECONNECT:
                await self.hass.async_add_executor_job(
                    self.connection.call_action,
                    "WANIPConn1",
                    "ForceTermination",
                )
        except (FritzServiceError, FritzActionError) as ex:
            raise HomeAssistantError("Service or parameter unknown") from ex
        except FritzConnectionException as ex:
            raise HomeAssistantError("Service not supported") from ex
Esempio n. 13
0
def report_hosts():
    fh = FritzHosts(address=args.ip, password=args.password)
    hosts = fh.get_active_hosts()
    return sorted((host["name"] for host in hosts), key=str.lower)
Esempio n. 14
0
#!/usr/bin/env python3
# faster to just check status of phone than all hosts

from fritzconnection.lib.fritzhosts import FritzHosts

import config

fh = FritzHosts(password=config.fritz_pwd)
he = fh.get_specific_host_entry(config.phone_mac)
print(he['NewActive'])
Esempio n. 15
0
class FritzBoxTools(update_coordinator.DataUpdateCoordinator):
    """FritzBoxTools class."""

    def __init__(
        self,
        hass: HomeAssistant,
        password: str,
        username: str = DEFAULT_USERNAME,
        host: str = DEFAULT_HOST,
        port: int = DEFAULT_PORT,
    ) -> None:
        """Initialize FritzboxTools class."""
        super().__init__(
            hass=hass,
            logger=_LOGGER,
            name=f"{DOMAIN}-{host}-coordinator",
            update_interval=timedelta(seconds=30),
        )

        self._devices: dict[str, FritzDevice] = {}
        self._options: MappingProxyType[str, Any] | None = None
        self._unique_id: str | None = None
        self.connection: FritzConnection = None
        self.fritz_hosts: FritzHosts = None
        self.fritz_status: FritzStatus = None
        self.hass = hass
        self.host = host
        self.mesh_role = MeshRoles.NONE
        self.device_is_router: bool = True
        self.password = password
        self.port = port
        self.username = username
        self._model: str | None = None
        self._current_firmware: str | None = None
        self._latest_firmware: str | None = None
        self._update_available: bool = False

    async def async_setup(
        self, options: MappingProxyType[str, Any] | None = None
    ) -> None:
        """Wrap up FritzboxTools class setup."""
        self._options = options
        await self.hass.async_add_executor_job(self.setup)

    def setup(self) -> None:
        """Set up FritzboxTools class."""
        self.connection = FritzConnection(
            address=self.host,
            port=self.port,
            user=self.username,
            password=self.password,
            timeout=60.0,
            pool_maxsize=30,
        )

        if not self.connection:
            _LOGGER.error("Unable to establish a connection with %s", self.host)
            return

        self.fritz_hosts = FritzHosts(fc=self.connection)
        self.fritz_status = FritzStatus(fc=self.connection)
        info = self.connection.call_action("DeviceInfo:1", "GetInfo")
        if not self._unique_id:
            self._unique_id = info["NewSerialNumber"]

        self._model = info.get("NewModelName")
        self._current_firmware = info.get("NewSoftwareVersion")

        self._update_available, self._latest_firmware = self._update_device_info()
        self.device_is_router = "WANIPConn1" in self.connection.services

    @callback
    async def _async_update_data(self) -> None:
        """Update FritzboxTools data."""
        try:
            await self.async_scan_devices()
        except (FritzSecurityError, FritzConnectionException) as ex:
            raise update_coordinator.UpdateFailed from ex

    @property
    def unique_id(self) -> str:
        """Return unique id."""
        if not self._unique_id:
            raise ClassSetupMissing()
        return self._unique_id

    @property
    def model(self) -> str:
        """Return device model."""
        if not self._model:
            raise ClassSetupMissing()
        return self._model

    @property
    def current_firmware(self) -> str:
        """Return current SW version."""
        if not self._current_firmware:
            raise ClassSetupMissing()
        return self._current_firmware

    @property
    def latest_firmware(self) -> str | None:
        """Return latest SW version."""
        return self._latest_firmware

    @property
    def update_available(self) -> bool:
        """Return if new SW version is available."""
        return self._update_available

    @property
    def mac(self) -> str:
        """Return device Mac address."""
        if not self._unique_id:
            raise ClassSetupMissing()
        return dr.format_mac(self._unique_id)

    @property
    def devices(self) -> dict[str, FritzDevice]:
        """Return devices."""
        return self._devices

    @property
    def signal_device_new(self) -> str:
        """Event specific per FRITZ!Box entry to signal new device."""
        return f"{DOMAIN}-device-new-{self._unique_id}"

    @property
    def signal_device_update(self) -> str:
        """Event specific per FRITZ!Box entry to signal updates in devices."""
        return f"{DOMAIN}-device-update-{self._unique_id}"

    def _update_hosts_info(self) -> list[HostInfo]:
        """Retrieve latest hosts information from the FRITZ!Box."""
        try:
            return self.fritz_hosts.get_hosts_info()  # type: ignore [no-any-return]
        except Exception as ex:  # pylint: disable=[broad-except]
            if not self.hass.is_stopping:
                raise HomeAssistantError("Error refreshing hosts info") from ex
        return []

    def _update_device_info(self) -> tuple[bool, str | None]:
        """Retrieve latest device information from the FRITZ!Box."""
        version = self.connection.call_action("UserInterface1", "GetInfo").get(
            "NewX_AVM-DE_Version"
        )
        return bool(version), version

    async def async_scan_devices(self, now: datetime | None = None) -> None:
        """Wrap up FritzboxTools class scan."""
        await self.hass.async_add_executor_job(self.scan_devices, now)

    def scan_devices(self, now: datetime | None = None) -> None:
        """Scan for new devices and return a list of found device ids."""

        _LOGGER.debug("Checking host info for FRITZ!Box device %s", self.host)
        self._update_available, self._latest_firmware = self._update_device_info()

        try:
            topology = self.fritz_hosts.get_mesh_topology()
        except FritzActionError:
            self.mesh_role = MeshRoles.SLAVE
            return

        _LOGGER.debug("Checking devices for FRITZ!Box device %s", self.host)
        _default_consider_home = DEFAULT_CONSIDER_HOME.total_seconds()
        if self._options:
            consider_home = self._options.get(
                CONF_CONSIDER_HOME, _default_consider_home
            )
        else:
            consider_home = _default_consider_home

        new_device = False
        hosts = {}
        for host in self._update_hosts_info():
            if not host.get("mac"):
                continue

            hosts[host["mac"]] = Device(
                name=host["name"],
                connected=host["status"],
                connected_to="",
                connection_type="",
                ip_address=host["ip"],
                ssid=None,
                wan_access=False,
            )

        mesh_intf = {}
        # first get all meshed devices
        for node in topology["nodes"]:
            if not node["is_meshed"]:
                continue

            for interf in node["node_interfaces"]:
                int_mac = interf["mac_address"]
                mesh_intf[interf["uid"]] = Interface(
                    device=node["device_name"],
                    mac=int_mac,
                    op_mode=interf.get("op_mode", ""),
                    ssid=interf.get("ssid", ""),
                    type=interf["type"],
                )
                if dr.format_mac(int_mac) == self.mac:
                    self.mesh_role = MeshRoles(node["mesh_role"])

        # second get all client devices
        for node in topology["nodes"]:
            if node["is_meshed"]:
                continue

            for interf in node["node_interfaces"]:
                dev_mac = interf["mac_address"]
                for link in interf["node_links"]:
                    intf = mesh_intf.get(link["node_interface_1_uid"])
                    if (
                        intf is not None
                        and link["state"] == "CONNECTED"
                        and dev_mac in hosts
                    ):
                        dev_info: Device = hosts[dev_mac]
                        if intf["op_mode"] != "AP_GUEST":
                            dev_info.wan_access = not self.connection.call_action(
                                "X_AVM-DE_HostFilter:1",
                                "GetWANAccessByIP",
                                NewIPv4Address=dev_info.ip_address,
                            ).get("NewDisallow")

                        dev_info.connected_to = intf["device"]
                        dev_info.connection_type = intf["type"]
                        dev_info.ssid = intf.get("ssid")

                        if dev_mac in self._devices:
                            self._devices[dev_mac].update(dev_info, consider_home)
                        else:
                            device = FritzDevice(dev_mac, dev_info.name)
                            device.update(dev_info, consider_home)
                            self._devices[dev_mac] = device
                            new_device = True

        dispatcher_send(self.hass, self.signal_device_update)
        if new_device:
            dispatcher_send(self.hass, self.signal_device_new)

    async def async_trigger_firmware_update(self) -> bool:
        """Trigger firmware update."""
        results = await self.hass.async_add_executor_job(
            self.connection.call_action, "UserInterface:1", "X_AVM-DE_DoUpdate"
        )
        return cast(bool, results["NewX_AVM-DE_UpdateState"])

    async def async_trigger_reboot(self) -> None:
        """Trigger device reboot."""
        await self.hass.async_add_executor_job(self.connection.reboot)

    async def async_trigger_reconnect(self) -> None:
        """Trigger device reconnect."""
        await self.hass.async_add_executor_job(self.connection.reconnect)

    async def async_trigger_cleanup(
        self, config_entry: ConfigEntry | None = None
    ) -> None:
        """Trigger device trackers cleanup."""
        device_hosts_list = await self.hass.async_add_executor_job(
            self.fritz_hosts.get_hosts_info
        )
        entity_reg: er.EntityRegistry = er.async_get(self.hass)

        if config_entry is None:
            if self.config_entry is None:
                return
            config_entry = self.config_entry

        ha_entity_reg_list: list[er.RegistryEntry] = er.async_entries_for_config_entry(
            entity_reg, config_entry.entry_id
        )
        entities_removed: bool = False

        device_hosts_macs = set()
        device_hosts_names = set()
        for device in device_hosts_list:
            device_hosts_macs.add(device["mac"])
            device_hosts_names.add(device["name"])

        for entry in ha_entity_reg_list:
            if entry.original_name is None:
                continue
            entry_name = entry.name or entry.original_name
            entry_host = entry_name.split(" ")[0]
            entry_mac = entry.unique_id.split("_")[0]

            if not _cleanup_entity_filter(entry) or (
                entry_mac in device_hosts_macs and entry_host in device_hosts_names
            ):
                _LOGGER.debug(
                    "Skipping entity %s [mac=%s, host=%s]",
                    entry_name,
                    entry_mac,
                    entry_host,
                )
                continue
            _LOGGER.info("Removing entity: %s", entry_name)
            entity_reg.async_remove(entry.entity_id)
            entities_removed = True

        if entities_removed:
            self._async_remove_empty_devices(entity_reg, config_entry)

    @callback
    def _async_remove_empty_devices(
        self, entity_reg: er.EntityRegistry, config_entry: ConfigEntry
    ) -> None:
        """Remove devices with no entities."""

        device_reg = dr.async_get(self.hass)
        device_list = dr.async_entries_for_config_entry(
            device_reg, config_entry.entry_id
        )
        for device_entry in device_list:
            if not er.async_entries_for_device(
                entity_reg,
                device_entry.id,
                include_disabled_entities=True,
            ):
                _LOGGER.info("Removing device: %s", device_entry.name)
                device_reg.async_remove_device(device_entry.id)

    async def service_fritzbox(
        self, service_call: ServiceCall, config_entry: ConfigEntry
    ) -> None:
        """Define FRITZ!Box services."""
        _LOGGER.debug("FRITZ!Box service: %s", service_call.service)

        if not self.connection:
            raise HomeAssistantError("Unable to establish a connection")

        try:
            if service_call.service == SERVICE_REBOOT:
                _LOGGER.warning(
                    'Service "fritz.reboot" is deprecated, please use the corresponding button entity instead'
                )
                await self.async_trigger_reboot()
                return

            if service_call.service == SERVICE_RECONNECT:
                _LOGGER.warning(
                    'Service "fritz.reconnect" is deprecated, please use the corresponding button entity instead'
                )
                await self.async_trigger_reconnect()
                return

            if service_call.service == SERVICE_CLEANUP:
                _LOGGER.warning(
                    'Service "fritz.cleanup" is deprecated, please use the corresponding button entity instead'
                )
                await self.async_trigger_cleanup(config_entry)
                return

        except (FritzServiceError, FritzActionError) as ex:
            raise HomeAssistantError("Service or parameter unknown") from ex
        except FritzConnectionException as ex:
            raise HomeAssistantError("Service not supported") from ex
Esempio n. 16
0
class FritzBoxScanner(DeviceScanner):
    """This class queries a FRITZ!Box router."""
    def __init__(self, config):
        """Initialize the scanner."""
        self.last_results = []
        self.host = config[CONF_HOST]
        self.username = config[CONF_USERNAME]
        self.password = config[CONF_PASSWORD]
        self.success_init = True

        # Establish a connection to the FRITZ!Box.
        try:
            self.fritz_box = FritzHosts(address=self.host,
                                        user=self.username,
                                        password=self.password)
        except (ValueError, TypeError):
            self.fritz_box = None

        # At this point it is difficult to tell if a connection is established.
        # So just check for null objects.
        if self.fritz_box is None or not self.fritz_box.modelname:
            self.success_init = False

        if self.success_init:
            _LOGGER.info("Successfully connected to %s",
                         self.fritz_box.modelname)
            self._update_info()
        else:
            _LOGGER.error(
                "Failed to establish connection to FRITZ!Box with IP: %s",
                self.host)

    def scan_devices(self):
        """Scan for new devices and return a list of found device ids."""
        self._update_info()
        active_hosts = []
        for known_host in self.last_results:
            if known_host["status"] and known_host.get("mac"):
                active_hosts.append(known_host["mac"])
        return active_hosts

    def get_device_name(self, device):
        """Return the name of the given device or None if is not known."""
        ret = self.fritz_box.get_specific_host_entry(device).get("NewHostName")
        if ret == {}:
            return None
        return ret

    def get_extra_attributes(self, device):
        """Return the attributes (ip, mac) of the given device or None if is not known."""
        ip_device = self.fritz_box.get_specific_host_entry(device).get(
            "NewIPAddress")

        if not ip_device:
            return {}
        return {"ip": ip_device, "mac": device}

    def _update_info(self):
        """Retrieve latest information from the FRITZ!Box."""
        if not self.success_init:
            return False

        _LOGGER.debug("Scanning")
        self.last_results = self.fritz_box.get_hosts_info()
        return True
Esempio n. 17
0

def get_arguments():
    parser = argparse.ArgumentParser(description="Get Devices from the WLAN")
    parser.add_argument('-i', '--ip', help='The IP of the FritzBox', required=True)
    parser.add_argument('-p', '--password', help='The Password of FritzBox', required=True)

    arguments = parser.parse_args()
    return arguments


def get_known_devices(f_h):
    result = ''
    hosts = f_h.get_hosts_info()
    for index, host in enumerate(hosts, start=1):
        ip = host['ip'] if host['ip'] else '-'
        name = host['name']
        mac = host['mac'] if host['mac'] else '-'
        status = 'online' if host['status'] else 'offline'
        result += name + ',' + ip + ',' + mac + ',' + status + "\n"
    result = result.rstrip("\n")
    return result


if __name__ == '__main__':
    args = get_arguments()
    fh = FritzHosts(address=args.ip, password=args.password)
    output = ""

    print(get_known_devices(fh))
Esempio n. 18
0
# installation
# pip3 install fritzconnection

from InfluxUploader import InfluxUploader
from fritzconnection.lib.fritzhosts import FritzHosts
from fritzconnection import FritzConnection

influx = InfluxUploader(verbose=False)
my_measurement = 'FritzHosts'

# fc = FritzConnection(address='192.168.178.1')
# print(fc)  # print router model informations

fh = FritzHosts(address='192.168.178.1',
                user='******',
                password='******')

# d = fh.get_specific_host_entry_by_ip('192.168.178.112')
# for key, value in d.items():
#     print(f"{key} = {value}")

l_mac_blacklist = []
l_mac_blacklist.append('E0:C7:67:D9:AE:C4')  # FR
l_mac_blacklist.append('24:1B:7A:AE:DD:84')  # FR
# l_mac_blacklist.append('B8:27:EB:05:7C:7E')  # raspi3

hosts = fh.get_hosts_info()
for index, host in enumerate(hosts, start=1):
    status = 'active' if host['status'] else '-'
    ip = host['ip'] if host['ip'] else '-'
with open("conf", "r") as config:
    key = config.readline().split(":", 1)[-1].strip()
    allowed_users = [
        int(x) for x in config.readline().split(':', 1)[-1].strip().split(',')
        if len(x) > 0
    ]
    router_ip = config.readline().split(':', 1)[-1].strip()
    router_pw = config.readline().split(':', 1)[-1].strip()
    db_vals = config.readline().split(':', 1)[-1].strip()
    user, user_pw, location, port, database = db_vals.split(',')
    port = int(port)

router_connection = FritzConnection(address=router_ip, password=router_pw)
router_wlan = FritzWLAN(router_connection)
router_host = FritzHosts(router_connection)
router_status = FritzStatus(router_connection)

router_model = router_status.modelname

metadata = MetaData()
db_engine = create_engine(
    f'mysql+pymysql://{user}:{user_pw}@{location}:{port}/{database}')
metadata.reflect(db_engine, only=['stats'])
Base = automap_base(metadata=metadata)
Base.prepare()
db_statistics_class = Base.classes.stats
Session = sessionmaker(bind=db_engine)
db_session = Session()