Пример #1
0
def cli() -> None:
    """Parse command line options, provide entry point for console scripts."""
    arguments = sys.argv[1:]
    parser = argparse.ArgumentParser(
        description="Emulate Belkin Wemo devices " "for use with Amaazon Echo"
    )
    parser.add_argument(
        "-v",
        "--verbose",
        help="increase verbosity (may " "repeat up to -vvv)",
        action="count",
        default=0,
    )
    parser.add_argument("-c", "--config", help="specify alternate config file")
    parser.add_argument(
        "-V", "--version", action="version", version=__version__
    )
    args = parser.parse_args(arguments)

    # args.verbose defaults to 0
    # 40 - 10 * 0 = 40 == logging.ERROR
    verbosity = max(40 - 10 * args.verbose, 10)
    logger.setLevel(verbosity)

    main(config_path_str=args.config, verbosity=verbosity)
Пример #2
0
def main(config_path_str: str = None, verbosity: int = 20) -> None:
    """Run the main fauxmo process.

    Spawns a UDP server to handle the Echo's UPnP / SSDP device discovery
    process as well as multiple TCP servers to respond to the Echo's device
    setup requests and handle its process for turning devices on and off.

    Args:
        config_path_str: Path to config file. If not given will search for
                         `config.json` in cwd, `~/.fauxmo/`, and
                         `/etc/fauxmo/`.
        verbosity: Logging verbosity, defaults to 20
    """
    logger.setLevel(verbosity)
    logger.info(f"Fauxmo version {__version__}")
    logger.debug(sys.version)

    if config_path_str:
        config_path = pathlib.Path(config_path_str)
    else:
        for config_dir in ('.', "~/.fauxmo", "/etc/fauxmo"):
            config_path = pathlib.Path(config_dir) / 'config.json'
            if config_path.is_file():
                logger.info(f"Using config: {config_path}")
                break

    try:
        config = json.loads(config_path.read_text())
    except FileNotFoundError:
        logger.error("Could not find config file in default search path. Try "
                     "specifying your file with `-c`.\n")
        raise

    # Every config should include a FAUXMO section
    fauxmo_config = config.get("FAUXMO")
    fauxmo_ip = get_local_ip(fauxmo_config.get("ip_address"))

    ssdp_server = SSDPServer()
    servers = []

    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)

    if verbosity < 20:
        loop.set_debug(True)
        logging.getLogger('asyncio').setLevel(logging.DEBUG)

    try:
        plugins = config['PLUGINS']
    except KeyError:
        # Give a meaningful message without a nasty traceback if it looks like
        # user is running a pre-v0.4.0 config.
        errmsg = ("`PLUGINS` key not found in your config.\n"
                  "You may be trying to use an outdated config.\n"
                  "If so, please review <https://github.com/n8henrie/fauxmo> "
                  "and update your config for Fauxmo >= v0.4.0.")
        print(errmsg)
        sys.exit(1)

    for plugin in plugins:

        modname = f"{__package__}.plugins.{plugin.lower()}"
        try:
            module = importlib.import_module(modname)

        # Will fail until https://github.com/python/typeshed/pull/1083 merged
        # and included in the next mypy release
        except ModuleNotFoundError:  # type: ignore
            path_str = config['PLUGINS'][plugin]['path']
            module = module_from_file(modname, path_str)

        PluginClass = getattr(module, plugin)  # noqa
        if not issubclass(PluginClass, FauxmoPlugin):
            raise TypeError(f"Plugins must inherit from {repr(FauxmoPlugin)}")

        # Pass along variables defined at the plugin level that don't change
        # per device
        plugin_vars = {
            k: v
            for k, v in config['PLUGINS'][plugin].items()
            if k not in {"DEVICES", "path"}
        }
        logger.debug(f"plugin_vars: {repr(plugin_vars)}")

        for device in config['PLUGINS'][plugin]['DEVICES']:
            logger.debug(f"device config: {repr(device)}")

            # Ensure port is `int`, set it if not given (`None`) or 0
            device["port"] = int(device.get('port', 0)) or find_unused_port()

            try:
                plugin = PluginClass(**plugin_vars, **device)
            except TypeError:
                logger.error(f"Error in plugin {repr(PluginClass)}")
                raise

            fauxmo = partial(Fauxmo, name=plugin.name, plugin=plugin)
            coro = loop.create_server(fauxmo, host=fauxmo_ip, port=plugin.port)
            server = loop.run_until_complete(coro)
            servers.append(server)

            ssdp_server.add_device(plugin.name, fauxmo_ip, plugin.port)

            logger.debug(f"Started fauxmo device: {repr(fauxmo.keywords)}")

    logger.info("Starting UDP server")

    # mypy will fail until https://github.com/python/typeshed/pull/1084 merged,
    # pulled into mypy, and new mypy released
    listen = loop.create_datagram_endpoint(
        lambda: ssdp_server,  # type: ignore
        sock=make_udp_sock())
    transport, _ = loop.run_until_complete(listen)  # type: ignore

    for signame in ('SIGINT', 'SIGTERM'):
        try:
            loop.add_signal_handler(getattr(signal, signame), loop.stop)

        # Workaround for Windows (https://github.com/n8henrie/fauxmo/issues/21)
        except NotImplementedError:
            if sys.platform == 'win32':
                pass
            else:
                raise

    loop.run_forever()

    # Will not reach this part unless SIGINT or SIGTERM triggers `loop.stop()`
    logger.debug("Shutdown starting...")
    transport.close()
    for idx, server in enumerate(servers):
        logger.debug(f"Shutting down server {idx}...")
        server.close()
        loop.run_until_complete(server.wait_closed())
    loop.close()
Пример #3
0
def main(config_path=None, verbosity=20):
    """Runs the main fauxmo process

    Spawns a UDP server to handle the Echo's UPnP / SSDP device discovery
    process as well as multiple TCP servers to respond to the Echo's device
    setup requests and handle its process for turning devices on and off.

    Kwargs:
        config_path (str): Path to config file. If not given will search for
                           `config.json` in cwd, `~/.fauxmo/`, and
                           `/etc/fauxmo/`.
        verbosity (int): Logging verbosity, defaults to 20
    """

    logger.setLevel(verbosity)

    logger.debug(sys.version)

    if not config_path:
        config_dirs = ['.', os.path.expanduser("~/.fauxmo"), "/etc/fauxmo"]
        for config_dir in config_dirs:
            config_path = os.path.join(config_dir, 'config.json')
            if os.path.isfile(config_path):
                logger.info("Using config: {}".format(config_path))
                break

    try:
        with open(config_path) as config_file:
            config = json.load(config_file)
    except FileNotFoundError:
        logger.error("Could not find config file in default search path. "
                     "Try specifying your file with `-c` flag.\n")
        raise

    # Every config should include a FAUXMO section
    fauxmo_config = config.get("FAUXMO")
    fauxmo_ip = get_local_ip(fauxmo_config.get("ip_address"))

    ssdp_server = SSDPServer()
    servers = []

    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    loop.set_debug(True)

    # Initialize Fauxmo devices
    for device in config.get('DEVICES'):
        name = device.get('description')
        port = int(device.get("port"))
        action_handler = RESTAPIHandler(**device.get("handler"))

        fauxmo = partial(Fauxmo, name=name, action_handler=action_handler)
        coro = loop.create_server(fauxmo, host=fauxmo_ip, port=port)
        server = loop.run_until_complete(coro)
        servers.append(server)

        ssdp_server.add_device(name, fauxmo_ip, port)

        logger.debug(fauxmo.keywords)

    # Initialize Home Assistant devices if config exists and enable is True
    if config.get("HOMEASSISTANT", {}).get("enable") is True:
        hass_config = config.get("HOMEASSISTANT")

        hass_host = hass_config.get("host")
        hass_password = hass_config.get("password")
        hass_port = hass_config.get("port")

        for device in hass_config.get('DEVICES'):
            name = device.get('description')
            device_port = device.get("port")
            entity = device.get("entity_id")
            action_handler = HassAPIHandler(host=hass_host,
                                            password=hass_password,
                                            entity=entity,
                                            port=hass_port)
            fauxmo = partial(Fauxmo, name=name, action_handler=action_handler)
            coro = loop.create_server(fauxmo, host=fauxmo_ip, port=device_port)
            server = loop.run_until_complete(coro)
            servers.append(server)

            ssdp_server.add_device(name, fauxmo_ip, device_port)

            logger.debug(fauxmo.keywords)

    logger.info("Starting UDP server")

    listen = loop.create_datagram_endpoint(lambda: ssdp_server,
                                           local_addr=('0.0.0.0', 1900),
                                           family=socket.AF_INET)
    transport, protocol = loop.run_until_complete(listen)

    for signame in ('SIGINT', 'SIGTERM'):
        loop.add_signal_handler(getattr(signal, signame), loop.stop)

    loop.run_forever()

    # Will not reach this part unless SIGINT or SIGTERM triggers `loop.stop()`
    logger.debug("Shutdown starting...")
    transport.close()
    for idx, server in enumerate(servers):
        logger.debug("Shutting down server {}...".format(idx))
        server.close()
        loop.run_until_complete(server.wait_closed())
    loop.close()