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)
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.
* 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>`_)
- 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>`_)
- 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>`_)
- 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>`__
* 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
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']

View File

@ -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']

View File

@ -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():

View File

@ -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(