Extend "pio device list" command with new options to list logical devices and multicast DNS services // Resolve #463

This commit is contained in:
Ivan Kravets
2017-12-18 21:31:49 +02:00
parent 724135f40e
commit 31814b5122
6 changed files with 166 additions and 40 deletions

View File

@ -15,16 +15,24 @@ PlatformIO 3.0
* Allowed to depend on development platform using VSC URL (Git, Mercurial and Subversion) * Allowed to depend on development platform using VSC URL (Git, Mercurial and Subversion)
in `Project Configuration File "platformio.ini" <http://docs.platformio.org/en/latest/projectconf/section_env_general.html#platform>`__ in `Project Configuration File "platformio.ini" <http://docs.platformio.org/en/latest/projectconf/section_env_general.html#platform>`__
Dropped support for ``*_stage`` dev/platforms. Use VCS URL instead. Dropped support for ``*_stage`` dev/platforms. Use VCS URL instead.
* Improvements to `Library Dependency Finder (LDF) <http://docs.platformio.org/page/librarymanager/ldf.html>`__: * New options for `platformio device list <http://docs.platformio.org/en/latest/userguide/cmd_device.html#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 <https://github.com/platformio/platformio-core/issues/463>`_)
* `Library Dependency Finder (LDF) <http://docs.platformio.org/page/librarymanager/ldf.html>`__:
- Search for dependencies used in `PIO Unit Testing <http://docs.platformio.org/page/plus/unit-testing.html>`__
(`issue #953 <https://github.com/platformio/platformio-core/issues/953>`_) (`issue #953 <https://github.com/platformio/platformio-core/issues/953>`_)
- Parse library source file in pair with a header when they have the same name - Parse library source file in pair with a header when they have the same name
(`issue #1175 <https://github.com/platformio/platformio-core/issues/1175>`_) (`issue #1175 <https://github.com/platformio/platformio-core/issues/1175>`_)
- Handle library dependencies defined as VCS or SemVer in - Handle library dependencies defined as VCS or SemVer in
`Project Configuration File "platformio.ini" <http://docs.platformio.org/en/latest/projectconf/section_env_general.html#platform>`__ `Project Configuration File "platformio.ini" <http://docs.platformio.org/page/projectconf/section_env_general.html#platform>`__
(`issue #1155 <https://github.com/platformio/platformio-core/issues/1155>`_) (`issue #1155 <https://github.com/platformio/platformio-core/issues/1155>`_)
- Added option to configure library `Compatible Mode <http://docs.platformio.org/en/latest/librarymanager/ldf.html#compatibility-mode>`__ - Added option to configure library `Compatible Mode <http://docs.platformio.org/page/librarymanager/ldf.html#compatibility-mode>`__
using `library.json <http://docs.platformio.org/page/librarymanager/config.html>`__ using `library.json <http://docs.platformio.org/page/librarymanager/config.html>`__
* Fixed platforms, packages, and libraries updating behind proxy * Fixed platforms, packages, and libraries updating behind proxy

2
docs

Submodule docs updated: a9a8554ed0...0c984c49f2

View File

@ -58,7 +58,7 @@ def WaitForNewSerialPort(env, before):
elapsed = 0 elapsed = 0
before = [p['port'] for p in before] before = [p['port'] for p in before]
while elapsed < 5 and new_port is None: 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: for p in now:
if p not in before: if p not in before:
new_port = p new_port = p
@ -107,18 +107,18 @@ def AutodetectUploadPort(*args, **kwargs): # pylint: disable=unused-argument
def _look_for_mbed_disk(): def _look_for_mbed_disk():
msdlabels = ("mbed", "nucleo", "frdm", "microbit") msdlabels = ("mbed", "nucleo", "frdm", "microbit")
for item in util.get_logicaldisks(): for item in util.get_logical_devices():
if item['disk'].startswith("/net") or not _is_match_pattern( if item['device'].startswith("/net") or not _is_match_pattern(
item['disk']): item['device']):
continue continue
mbed_pages = [ 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]): if any([isfile(p) for p in mbed_pages]):
return item['disk'] return item['device']
if item['name'] \ if item['name'] \
and any([l in item['name'].lower() for l in msdlabels]): and any([l in item['name'].lower() for l in msdlabels]):
return item['disk'] return item['device']
return None return None
def _look_for_serial_port(): def _look_for_serial_port():
@ -127,7 +127,7 @@ def AutodetectUploadPort(*args, **kwargs): # pylint: disable=unused-argument
upload_protocol = env.subst("$UPLOAD_PROTOCOL") upload_protocol = env.subst("$UPLOAD_PROTOCOL")
if "BOARD" in env and "build.hwids" in env.BoardConfig(): if "BOARD" in env and "build.hwids" in env.BoardConfig():
board_hwids = env.BoardConfig().get("build.hwids") 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']): if not _is_match_pattern(item['port']):
continue continue
port = item['port'] port = item['port']

View File

@ -28,17 +28,69 @@ def cli():
@cli.command("list", short_help="List devices") @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) @click.option("--json-output", is_flag=True)
def device_list(json_output): def device_list( # pylint: disable=too-many-branches
if json_output: serial, logical, mdns, json_output):
return click.echo(json.dumps(util.get_serialports())) 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(): single_key = data.keys()[0] if len(data.keys()) == 1 else None
click.secho(item['port'], fg="cyan")
click.echo("-" * len(item['port'])) if json_output:
click.echo("Hardware ID: %s" % item['hwid']) return click.echo(json.dumps(data[single_key] if single_key else data))
click.echo("Description: %s" % item['description'])
click.echo("") 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 return True
@ -123,7 +175,7 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches
pass pass
if not kwargs['port']: if not kwargs['port']:
ports = util.get_serialports(filter_hwid=True) ports = util.get_serial_ports(filter_hwid=True)
if len(ports) == 1: if len(ports) == 1:
kwargs['port'] = ports[0]['port'] kwargs['port'] = ports[0]['port']

View File

@ -32,6 +32,7 @@ from time import sleep, time
import click import click
import requests import requests
import zeroconf
from platformio import __apiurl__, __version__, exception from platformio import __apiurl__, __version__, exception
@ -417,7 +418,7 @@ def copy_pythonpath_to_osenv():
os.environ['PYTHONPATH'] = os.pathsep.join(_PYTHONPATH) os.environ['PYTHONPATH'] = os.pathsep.join(_PYTHONPATH)
def get_serialports(filter_hwid=False): def get_serial_ports(filter_hwid=False):
try: try:
from serial.tools.list_ports import comports from serial.tools.list_ports import comports
except ImportError: except ImportError:
@ -445,42 +446,106 @@ def get_serialports(filter_hwid=False):
return result return result
def get_logicaldisks(): def get_logical_devices():
disks = [] items = []
if platform.system() == "Windows": if platform.system() == "Windows":
try: try:
result = exec_command( result = exec_command(
["wmic", "logicaldisk", "get", "name,VolumeName"]).get( ["wmic", "logicaldisk", "get", "name,VolumeName"]).get(
"out", "") "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"): for line in result.split("\n"):
match = disknamere.match(line.strip()) match = devicenamere.match(line.strip())
if not match: if not match:
continue continue
disks.append({ items.append({
"disk": match.group(1) + "\\", "device": match.group(1) + "\\",
"name": match.group(2) "name": match.group(2)
}) })
return disks return items
except WindowsError: # pylint: disable=undefined-variable except WindowsError: # pylint: disable=undefined-variable
pass pass
# try "fsutil" # try "fsutil"
result = exec_command(["fsutil", "fsinfo", "drives"]).get("out", "") result = exec_command(["fsutil", "fsinfo", "drives"]).get("out", "")
for disk in re.findall(r"[A-Z]:\\", result): for device in re.findall(r"[A-Z]:\\", result):
disks.append({"disk": disk, "name": None}) items.append({"device": device, "name": None})
return disks return items
else: else:
result = exec_command(["df"]).get("out") 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"): for line in result.split("\n"):
match = disknamere.match(line.strip()) match = devicenamere.match(line.strip())
if not match: if not match:
continue continue
disks.append({ items.append({
"disk": match.group(1), "device": match.group(1),
"name": basename(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(): def get_request_defheaders():

View File

@ -25,7 +25,8 @@ install_requires = [
"lockfile>=0.9.1,<0.13", "lockfile>=0.9.1,<0.13",
"pyserial>=3,<4,!=3.3", "pyserial>=3,<4,!=3.3",
"requests>=2.4.0,<3", "requests>=2.4.0,<3",
"semantic_version>=2.5.0,<3" "semantic_version>=2.5.0,<3",
"zeroconf<=0.19.1"
] ]
setup( setup(