docs: def run_cli(cls, method, igd_args: OrderedDict, lan_address: str = '', gateway_address: str = '', timeout: int = 30,
                  interface_name: str = 'default', unicast: bool = True, kwargs: dict = None, loop=None) -> None:
          """
          :param method: the command name
          :param igd_args: ordered case sensitive M-SEARCH headers, if provided all headers to be used must be provided
          :param lan_address: the ip address of the local interface
          :param gateway_address: the ip address of the gateway
          :param timeout: timeout, in seconds
          :param interface_name: name of the network interface, the default is aliased to 'default'
          :param kwargs: keyword arguments for the command
          :param loop: EventLoop, used for testing
          """
          kwargs = kwargs or {}
          igd_args = igd_args
          timeout = int(timeout)
          loop = loop or asyncio.get_event_loop_policy().get_event_loop()
          fut: asyncio.Future = asyncio.Future()
  
          async def wrapper():  # wrap the upnp setup and call of the command in a coroutine
  
              if method == 'm_search':  # if we're only m_searching don't do any device discovery
                  fn = lambda *_a, **_kw: cls.m_search(
                      lan_address, gateway_address, timeout, igd_args, unicast, interface_name, loop
                  )
              else:  # automatically discover the gateway
                  try:
                      u = await cls.discover(
                          lan_address, gateway_address, timeout, igd_args, interface_name, loop=loop
                      )
                  except UPnPError as err:
                      fut.set_exception(err)
                      return
                  if hasattr(u, method) and hasattr(getattr(u, method), "_cli"):
                      fn = getattr(u, method)
                  else:
                      fut.set_exception(UPnPError("\"%s\" is not a recognized command" % method))
                      return
              try:  # call the command
                  result = await fn(**{k: fn.__annotations__[k](v) for k, v in kwargs.items()})
                  fut.set_result(result)
              except UPnPError as err:
                  fut.set_exception(err)
  
              except Exception as err:
                  log.exception("uncaught error")
                  fut.set_exception(UPnPError("uncaught error: %s" % str(err)))
  
          if not hasattr(UPnP, method) or not hasattr(getattr(UPnP, method), "_cli"):
              fut.set_exception(UPnPError("\"%s\" is not a recognized command" % method))
          else:
              loop.run_until_complete(wrapper())
          try:
              result = fut.result()
          except UPnPError as err:
              print("aioupnp encountered an error: %s" % str(err))
              return
  
          if isinstance(result, (list, tuple, dict)):
              print(json.dumps(result, indent=2, default=_encode))
          else:
              print(result)