From 31814b51226b6f3ad45c22aa67a8fc93244f34a7 Mon Sep 17 00:00:00 2001 From: Ivan Kravets Date: Mon, 18 Dec 2017 21:31:49 +0200 Subject: [PATCH] Extend "pio device list" command with new options to list logical devices and multicast DNS services // Resolve #463 --- HISTORY.rst | 16 +++-- docs | 2 +- platformio/builder/tools/pioupload.py | 16 ++--- platformio/commands/device.py | 72 +++++++++++++++++--- platformio/util.py | 97 ++++++++++++++++++++++----- setup.py | 3 +- 6 files changed, 166 insertions(+), 40 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 71849c69..8eddb985 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -15,16 +15,24 @@ PlatformIO 3.0 * Allowed to depend on development platform using VSC URL (Git, Mercurial and Subversion) in `Project Configuration File "platformio.ini" `__ Dropped support for ``*_stage`` dev/platforms. Use VCS URL instead. -* Improvements to `Library Dependency Finder (LDF) `__: +* New options for `platformio device list `__ + command: - - Search for libraries used in test + - ``--serial`` list available serial ports (default) + - ``--logical`` list logical devices + - ``--mdns`` discover multicast DNS services + (`issue #463 `_) + +* `Library Dependency Finder (LDF) `__: + + - Search for dependencies used in `PIO Unit Testing `__ (`issue #953 `_) - Parse library source file in pair with a header when they have the same name (`issue #1175 `_) - Handle library dependencies defined as VCS or SemVer in - `Project Configuration File "platformio.ini" `__ + `Project Configuration File "platformio.ini" `__ (`issue #1155 `_) - - Added option to configure library `Compatible Mode `__ + - Added option to configure library `Compatible Mode `__ using `library.json `__ * Fixed platforms, packages, and libraries updating behind proxy diff --git a/docs b/docs index a9a8554e..0c984c49 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit a9a8554ed0343d6a12ff750ee41feef15d875bc2 +Subproject commit 0c984c49f2ad98ba37a00c5ab66773df613582ba diff --git a/platformio/builder/tools/pioupload.py b/platformio/builder/tools/pioupload.py index 36fbda8c..2d75c6a9 100644 --- a/platformio/builder/tools/pioupload.py +++ b/platformio/builder/tools/pioupload.py @@ -58,7 +58,7 @@ def WaitForNewSerialPort(env, before): elapsed = 0 before = [p['port'] for p in before] while elapsed < 5 and new_port is None: - now = [p['port'] for p in util.get_serialports()] + now = [p['port'] for p in util.get_serial_ports()] for p in now: if p not in before: new_port = p @@ -107,18 +107,18 @@ def AutodetectUploadPort(*args, **kwargs): # pylint: disable=unused-argument def _look_for_mbed_disk(): msdlabels = ("mbed", "nucleo", "frdm", "microbit") - for item in util.get_logicaldisks(): - if item['disk'].startswith("/net") or not _is_match_pattern( - item['disk']): + for item in util.get_logical_devices(): + if item['device'].startswith("/net") or not _is_match_pattern( + item['device']): continue mbed_pages = [ - join(item['disk'], n) for n in ("mbed.htm", "mbed.html") + join(item['device'], n) for n in ("mbed.htm", "mbed.html") ] if any([isfile(p) for p in mbed_pages]): - return item['disk'] + return item['device'] if item['name'] \ and any([l in item['name'].lower() for l in msdlabels]): - return item['disk'] + return item['device'] return None def _look_for_serial_port(): @@ -127,7 +127,7 @@ def AutodetectUploadPort(*args, **kwargs): # pylint: disable=unused-argument upload_protocol = env.subst("$UPLOAD_PROTOCOL") if "BOARD" in env and "build.hwids" in env.BoardConfig(): board_hwids = env.BoardConfig().get("build.hwids") - for item in util.get_serialports(filter_hwid=True): + for item in util.get_serial_ports(filter_hwid=True): if not _is_match_pattern(item['port']): continue port = item['port'] diff --git a/platformio/commands/device.py b/platformio/commands/device.py index f4094cc3..5d71c223 100644 --- a/platformio/commands/device.py +++ b/platformio/commands/device.py @@ -28,17 +28,69 @@ def cli(): @cli.command("list", short_help="List devices") +@click.option("--serial", is_flag=True, help="List serial ports, default") +@click.option("--logical", is_flag=True, help="List logical devices") +@click.option("--mdns", is_flag=True, help="List multicast DNS services") @click.option("--json-output", is_flag=True) -def device_list(json_output): - if json_output: - return click.echo(json.dumps(util.get_serialports())) +def device_list( # pylint: disable=too-many-branches + serial, logical, mdns, json_output): + if not logical and not mdns: + serial = True + data = {} + if serial: + data['serial'] = util.get_serial_ports() + if logical: + data['logical'] = util.get_logical_devices() + if mdns: + data['mdns'] = util.get_mdns_services() - for item in util.get_serialports(): - click.secho(item['port'], fg="cyan") - click.echo("-" * len(item['port'])) - click.echo("Hardware ID: %s" % item['hwid']) - click.echo("Description: %s" % item['description']) - click.echo("") + single_key = data.keys()[0] if len(data.keys()) == 1 else None + + if json_output: + return click.echo(json.dumps(data[single_key] if single_key else data)) + + titles = { + "serial": "Serial Ports", + "logical": "Logical Devices", + "mdns": "Multicast DNS Services" + } + + for key, value in data.iteritems(): + if not single_key: + click.secho(titles[key], bold=True) + click.echo("=" * len(titles[key])) + + if key == "serial": + for item in value: + click.secho(item['port'], fg="cyan") + click.echo("-" * len(item['port'])) + click.echo("Hardware ID: %s" % item['hwid']) + click.echo("Description: %s" % item['description']) + click.echo("") + + if key == "logical": + for item in value: + click.secho(item['device'], fg="cyan") + click.echo("-" * len(item['device'])) + click.echo("Name: %s" % item['name']) + click.echo("") + + if key == "mdns": + for item in value: + click.secho(item['name'], fg="cyan") + click.echo("-" * len(item['name'])) + click.echo("Type: %s" % item['type']) + click.echo("IP: %s" % item['ip']) + click.echo("Port: %s" % item['port']) + if item['properties']: + click.echo("Properties: %s" % ("; ".join([ + "%s=%s" % (k, v) + for k, v in item['properties'].iteritems() + ]))) + click.echo("") + + if single_key: + click.echo("") return True @@ -123,7 +175,7 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches pass if not kwargs['port']: - ports = util.get_serialports(filter_hwid=True) + ports = util.get_serial_ports(filter_hwid=True) if len(ports) == 1: kwargs['port'] = ports[0]['port'] diff --git a/platformio/util.py b/platformio/util.py index ee295fb0..0669aaad 100644 --- a/platformio/util.py +++ b/platformio/util.py @@ -32,6 +32,7 @@ from time import sleep, time import click import requests +import zeroconf from platformio import __apiurl__, __version__, exception @@ -417,7 +418,7 @@ def copy_pythonpath_to_osenv(): os.environ['PYTHONPATH'] = os.pathsep.join(_PYTHONPATH) -def get_serialports(filter_hwid=False): +def get_serial_ports(filter_hwid=False): try: from serial.tools.list_ports import comports except ImportError: @@ -445,42 +446,106 @@ def get_serialports(filter_hwid=False): return result -def get_logicaldisks(): - disks = [] +def get_logical_devices(): + items = [] if platform.system() == "Windows": try: result = exec_command( ["wmic", "logicaldisk", "get", "name,VolumeName"]).get( "out", "") - disknamere = re.compile(r"^([A-Z]{1}\:)\s*(\S+)?") + devicenamere = re.compile(r"^([A-Z]{1}\:)\s*(\S+)?") for line in result.split("\n"): - match = disknamere.match(line.strip()) + match = devicenamere.match(line.strip()) if not match: continue - disks.append({ - "disk": match.group(1) + "\\", + items.append({ + "device": match.group(1) + "\\", "name": match.group(2) }) - return disks + return items except WindowsError: # pylint: disable=undefined-variable pass # try "fsutil" result = exec_command(["fsutil", "fsinfo", "drives"]).get("out", "") - for disk in re.findall(r"[A-Z]:\\", result): - disks.append({"disk": disk, "name": None}) - return disks + for device in re.findall(r"[A-Z]:\\", result): + items.append({"device": device, "name": None}) + return items else: result = exec_command(["df"]).get("out") - disknamere = re.compile(r"^/.+\d+\%\s+([a-z\d\-_/]+)$", flags=re.I) + devicenamere = re.compile(r"^/.+\d+\%\s+([a-z\d\-_/]+)$", flags=re.I) for line in result.split("\n"): - match = disknamere.match(line.strip()) + match = devicenamere.match(line.strip()) if not match: continue - disks.append({ - "disk": match.group(1), + items.append({ + "device": match.group(1), "name": basename(match.group(1)) }) - return disks + return items + + +### Backward compatibility for PIO Core <3.5 +get_serialports = get_serial_ports +get_logicaldisks = lambda: [{ + "disk": d['device'], + "name": d['name'] +} for d in get_logical_devices()] + + +def get_mdns_services(): + + class mDNSListener(object): + + def __init__(self): + self._zc = zeroconf.Zeroconf( + interfaces=zeroconf.InterfaceChoice.All) + self._found_types = [] + self._found_services = [] + + def __enter__(self): + zeroconf.ServiceBrowser(self._zc, "_services._dns-sd._udp.local.", + self) + return self + + def __exit__(self, etype, value, traceback): + self._zc.close() + + def remove_service(self, zc, type_, name): + pass + + def add_service(self, zc, type_, name): + try: + zeroconf.service_type_name(name) + except zeroconf.BadTypeInNameException: + return + if name not in self._found_types: + self._found_types.append(name) + zeroconf.ServiceBrowser(self._zc, name, self) + if type_ in self._found_types: + s = zc.get_service_info(type_, name) + if s: + self._found_services.append(s) + + def get_services(self): + return self._found_services + + items = [] + with mDNSListener() as mdns: + sleep(5) + for service in mdns.get_services(): + items.append({ + "type": + service.type, + "name": + service.name, + "ip": + ".".join([str(ord(c)) for c in service.address]), + "port": + service.port, + "properties": + service.properties + }) + return items def get_request_defheaders(): diff --git a/setup.py b/setup.py index dceee7b2..11476caa 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,8 @@ install_requires = [ "lockfile>=0.9.1,<0.13", "pyserial>=3,<4,!=3.3", "requests>=2.4.0,<3", - "semantic_version>=2.5.0,<3" + "semantic_version>=2.5.0,<3", + "zeroconf<=0.19.1" ] setup(