forked from platformio/platformio-core
Restructure "device" module
This commit is contained in:
@ -14,8 +14,8 @@
|
|||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from platformio.device.commands.list import device_list_cmd
|
from platformio.device.list.command import device_list_cmd
|
||||||
from platformio.device.commands.monitor import device_monitor_cmd
|
from platformio.device.monitor.command import device_monitor_cmd
|
||||||
|
|
||||||
|
|
||||||
@click.group(
|
@click.group(
|
||||||
|
@ -18,7 +18,7 @@ from fnmatch import fnmatch
|
|||||||
import serial
|
import serial
|
||||||
|
|
||||||
from platformio.compat import IS_WINDOWS
|
from platformio.compat import IS_WINDOWS
|
||||||
from platformio.device.list import list_logical_devices, list_serial_ports
|
from platformio.device.list.util import list_logical_devices, list_serial_ports
|
||||||
|
|
||||||
|
|
||||||
def is_pattern_port(port):
|
def is_pattern_port(port):
|
||||||
|
13
platformio/device/list/__init__.py
Normal file
13
platformio/device/list/__init__.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
99
platformio/device/list/command.py
Normal file
99
platformio/device/list/command.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
from platformio.device.list.util import (
|
||||||
|
list_logical_devices,
|
||||||
|
list_mdns_services,
|
||||||
|
list_serial_ports,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@click.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_cmd( # pylint: disable=too-many-branches
|
||||||
|
serial, logical, mdns, json_output
|
||||||
|
):
|
||||||
|
if not logical and not mdns:
|
||||||
|
serial = True
|
||||||
|
data = {}
|
||||||
|
if serial:
|
||||||
|
data["serial"] = list_serial_ports()
|
||||||
|
if logical:
|
||||||
|
data["logical"] = list_logical_devices()
|
||||||
|
if mdns:
|
||||||
|
data["mdns"] = list_mdns_services()
|
||||||
|
|
||||||
|
single_key = list(data)[0] if len(list(data)) == 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.items():
|
||||||
|
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["path"], fg="cyan")
|
||||||
|
click.echo("-" * len(item["path"]))
|
||||||
|
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"].items()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
click.echo("")
|
||||||
|
|
||||||
|
if single_key:
|
||||||
|
click.echo("")
|
||||||
|
|
||||||
|
return True
|
154
platformio/device/list/util.py
Normal file
154
platformio/device/list/util.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
import zeroconf
|
||||||
|
|
||||||
|
from platformio import __version__, exception, proc
|
||||||
|
from platformio.compat import IS_MACOS, IS_WINDOWS
|
||||||
|
|
||||||
|
|
||||||
|
def list_serial_ports(filter_hwid=False):
|
||||||
|
try:
|
||||||
|
# pylint: disable=import-outside-toplevel
|
||||||
|
from serial.tools.list_ports import comports
|
||||||
|
except ImportError:
|
||||||
|
raise exception.GetSerialPortsError(os.name)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for p, d, h in comports():
|
||||||
|
if not p:
|
||||||
|
continue
|
||||||
|
if not filter_hwid or "VID:PID" in h:
|
||||||
|
result.append({"port": p, "description": d, "hwid": h})
|
||||||
|
|
||||||
|
if filter_hwid:
|
||||||
|
return result
|
||||||
|
|
||||||
|
# fix for PySerial
|
||||||
|
if not result and IS_MACOS:
|
||||||
|
for p in glob("/dev/tty.*"):
|
||||||
|
result.append({"port": p, "description": "n/a", "hwid": "n/a"})
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def list_logical_devices():
|
||||||
|
items = []
|
||||||
|
if IS_WINDOWS:
|
||||||
|
try:
|
||||||
|
result = proc.exec_command(
|
||||||
|
["wmic", "logicaldisk", "get", "name,VolumeName"]
|
||||||
|
).get("out", "")
|
||||||
|
devicenamere = re.compile(r"^([A-Z]{1}\:)\s*(\S+)?")
|
||||||
|
for line in result.split("\n"):
|
||||||
|
match = devicenamere.match(line.strip())
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
items.append({"path": match.group(1) + "\\", "name": match.group(2)})
|
||||||
|
return items
|
||||||
|
except WindowsError: # pylint: disable=undefined-variable
|
||||||
|
pass
|
||||||
|
# try "fsutil"
|
||||||
|
result = proc.exec_command(["fsutil", "fsinfo", "drives"]).get("out", "")
|
||||||
|
for device in re.findall(r"[A-Z]:\\", result):
|
||||||
|
items.append({"path": device, "name": None})
|
||||||
|
return items
|
||||||
|
|
||||||
|
result = proc.exec_command(["df"]).get("out")
|
||||||
|
devicenamere = re.compile(r"^/.+\d+\%\s+([a-z\d\-_/]+)$", flags=re.I)
|
||||||
|
for line in result.split("\n"):
|
||||||
|
match = devicenamere.match(line.strip())
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
items.append({"path": match.group(1), "name": os.path.basename(match.group(1))})
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def list_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,
|
||||||
|
[
|
||||||
|
"_http._tcp.local.",
|
||||||
|
"_hap._tcp.local.",
|
||||||
|
"_services._dns-sd._udp.local.",
|
||||||
|
],
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, etype, value, traceback):
|
||||||
|
self._zc.close()
|
||||||
|
|
||||||
|
def add_service(self, zc, type_, name):
|
||||||
|
try:
|
||||||
|
assert zeroconf.service_type_name(name)
|
||||||
|
assert str(name)
|
||||||
|
except (AssertionError, UnicodeError, 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 remove_service(self, zc, type_, name):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def update_service(self, zc, type_, name):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_services(self):
|
||||||
|
return self._found_services
|
||||||
|
|
||||||
|
items = []
|
||||||
|
with mDNSListener() as mdns:
|
||||||
|
time.sleep(3)
|
||||||
|
for service in mdns.get_services():
|
||||||
|
properties = None
|
||||||
|
if service.properties:
|
||||||
|
try:
|
||||||
|
properties = {
|
||||||
|
k.decode("utf8"): v.decode("utf8")
|
||||||
|
if isinstance(v, bytes)
|
||||||
|
else v
|
||||||
|
for k, v in service.properties.items()
|
||||||
|
}
|
||||||
|
json.dumps(properties)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
properties = None
|
||||||
|
|
||||||
|
items.append(
|
||||||
|
{
|
||||||
|
"type": service.type,
|
||||||
|
"name": service.name,
|
||||||
|
"ip": ", ".join(service.parsed_addresses()),
|
||||||
|
"port": service.port,
|
||||||
|
"properties": properties,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return items
|
13
platformio/device/monitor/__init__.py
Normal file
13
platformio/device/monitor/__init__.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
184
platformio/device/monitor/command.py
Normal file
184
platformio/device/monitor/command.py
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import click
|
||||||
|
from serial.tools import miniterm
|
||||||
|
|
||||||
|
from platformio import exception, fs
|
||||||
|
from platformio.device.finder import find_serial_port
|
||||||
|
from platformio.device.monitor.filters.base import register_filters
|
||||||
|
from platformio.platform.factory import PlatformFactory
|
||||||
|
from platformio.project.config import ProjectConfig
|
||||||
|
from platformio.project.exception import NotPlatformIOProjectError
|
||||||
|
from platformio.project.options import ProjectOptions
|
||||||
|
|
||||||
|
|
||||||
|
@click.command("monitor", short_help="Monitor device (Serial/Socket)")
|
||||||
|
@click.option("--port", "-p", help="Port, a number or a device name")
|
||||||
|
@click.option(
|
||||||
|
"--baud",
|
||||||
|
"-b",
|
||||||
|
type=int,
|
||||||
|
help="Set baud rate, default=%d" % ProjectOptions["env.monitor_speed"].default,
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--parity",
|
||||||
|
default="N",
|
||||||
|
type=click.Choice(["N", "E", "O", "S", "M"]),
|
||||||
|
help="Set parity, default=N",
|
||||||
|
)
|
||||||
|
@click.option("--rtscts", is_flag=True, help="Enable RTS/CTS flow control, default=Off")
|
||||||
|
@click.option(
|
||||||
|
"--xonxoff", is_flag=True, help="Enable software flow control, default=Off"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--rts", default=None, type=click.IntRange(0, 1), help="Set initial RTS line state"
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--dtr", default=None, type=click.IntRange(0, 1), help="Set initial DTR line state"
|
||||||
|
)
|
||||||
|
@click.option("--echo", is_flag=True, help="Enable local echo, default=Off")
|
||||||
|
@click.option(
|
||||||
|
"--encoding",
|
||||||
|
default="UTF-8",
|
||||||
|
help="Set the encoding for the serial port (e.g. hexlify, "
|
||||||
|
"Latin1, UTF-8), default: UTF-8",
|
||||||
|
)
|
||||||
|
@click.option("--filter", "-f", multiple=True, help="Add filters/text transformations")
|
||||||
|
@click.option(
|
||||||
|
"--eol",
|
||||||
|
default="CRLF",
|
||||||
|
type=click.Choice(["CR", "LF", "CRLF"]),
|
||||||
|
help="End of line mode, default=CRLF",
|
||||||
|
)
|
||||||
|
@click.option("--raw", is_flag=True, help="Do not apply any encodings/transformations")
|
||||||
|
@click.option(
|
||||||
|
"--exit-char",
|
||||||
|
type=int,
|
||||||
|
default=3,
|
||||||
|
help="ASCII code of special character that is used to exit "
|
||||||
|
"the application, default=3 (Ctrl+C)",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--menu-char",
|
||||||
|
type=int,
|
||||||
|
default=20,
|
||||||
|
help="ASCII code of special character that is used to "
|
||||||
|
"control miniterm (menu), default=20 (DEC)",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--quiet",
|
||||||
|
is_flag=True,
|
||||||
|
help="Diagnostics: suppress non-error messages, default=Off",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-d",
|
||||||
|
"--project-dir",
|
||||||
|
default=os.getcwd,
|
||||||
|
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True),
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-e",
|
||||||
|
"--environment",
|
||||||
|
help="Load configuration from `platformio.ini` and specified environment",
|
||||||
|
)
|
||||||
|
def device_monitor_cmd(**kwargs): # pylint: disable=too-many-branches
|
||||||
|
project_options = {}
|
||||||
|
platform = None
|
||||||
|
with fs.cd(kwargs["project_dir"]):
|
||||||
|
try:
|
||||||
|
project_options = get_project_options(kwargs["environment"])
|
||||||
|
kwargs = apply_project_monitor_options(kwargs, project_options)
|
||||||
|
if "platform" in project_options:
|
||||||
|
platform = PlatformFactory.new(project_options["platform"])
|
||||||
|
except NotPlatformIOProjectError:
|
||||||
|
pass
|
||||||
|
register_filters(platform=platform, options=kwargs)
|
||||||
|
kwargs["port"] = find_serial_port(
|
||||||
|
initial_port=kwargs["port"],
|
||||||
|
board_config=platform.board_config(project_options.get("board"))
|
||||||
|
if platform and project_options.get("board")
|
||||||
|
else None,
|
||||||
|
upload_protocol=project_options.get("upload_port"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# override system argv with patched options
|
||||||
|
sys.argv = ["monitor"] + project_options_to_monitor_argv(
|
||||||
|
kwargs,
|
||||||
|
project_options,
|
||||||
|
ignore=("port", "baud", "rts", "dtr", "environment", "project_dir"),
|
||||||
|
)
|
||||||
|
|
||||||
|
if not kwargs["quiet"]:
|
||||||
|
click.echo(
|
||||||
|
"--- Available filters and text transformations: %s"
|
||||||
|
% ", ".join(sorted(miniterm.TRANSFORMATIONS.keys()))
|
||||||
|
)
|
||||||
|
click.echo("--- More details at https://bit.ly/pio-monitor-filters")
|
||||||
|
try:
|
||||||
|
miniterm.main(
|
||||||
|
default_port=kwargs["port"],
|
||||||
|
default_baudrate=kwargs["baud"]
|
||||||
|
or ProjectOptions["env.monitor_speed"].default,
|
||||||
|
default_rts=kwargs["rts"],
|
||||||
|
default_dtr=kwargs["dtr"],
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise exception.MinitermException(e)
|
||||||
|
|
||||||
|
|
||||||
|
def get_project_options(environment=None):
|
||||||
|
config = ProjectConfig.get_instance()
|
||||||
|
config.validate(envs=[environment] if environment else None)
|
||||||
|
environment = environment or config.get_default_env()
|
||||||
|
return config.items(env=environment, as_dict=True)
|
||||||
|
|
||||||
|
|
||||||
|
def apply_project_monitor_options(cli_options, project_options):
|
||||||
|
for k in ("port", "speed", "rts", "dtr"):
|
||||||
|
k2 = "monitor_%s" % k
|
||||||
|
if k == "speed":
|
||||||
|
k = "baud"
|
||||||
|
if cli_options[k] is None and k2 in project_options:
|
||||||
|
cli_options[k] = project_options[k2]
|
||||||
|
if k != "port":
|
||||||
|
cli_options[k] = int(cli_options[k])
|
||||||
|
return cli_options
|
||||||
|
|
||||||
|
|
||||||
|
def project_options_to_monitor_argv(cli_options, project_options, ignore=None):
|
||||||
|
confmon_flags = project_options.get("monitor_flags", [])
|
||||||
|
result = confmon_flags[::]
|
||||||
|
|
||||||
|
for f in project_options.get("monitor_filters", []):
|
||||||
|
result.extend(["--filter", f])
|
||||||
|
|
||||||
|
for k, v in cli_options.items():
|
||||||
|
if v is None or (ignore and k in ignore):
|
||||||
|
continue
|
||||||
|
k = "--" + k.replace("_", "-")
|
||||||
|
if k in confmon_flags:
|
||||||
|
continue
|
||||||
|
if isinstance(v, bool):
|
||||||
|
if v:
|
||||||
|
result.append(k)
|
||||||
|
elif isinstance(v, tuple):
|
||||||
|
for i in v:
|
||||||
|
result.extend([k, i])
|
||||||
|
else:
|
||||||
|
result.extend([k, str(v)])
|
||||||
|
return result
|
13
platformio/device/monitor/filters/__init__.py
Normal file
13
platformio/device/monitor/filters/__init__.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
100
platformio/device/monitor/filters/base.py
Normal file
100
platformio/device/monitor/filters/base.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
import os
|
||||||
|
|
||||||
|
from serial.tools import miniterm
|
||||||
|
|
||||||
|
from platformio.compat import get_object_members, load_python_module
|
||||||
|
from platformio.package.manager.tool import ToolPackageManager
|
||||||
|
from platformio.project.config import ProjectConfig
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceMonitorFilterBase(miniterm.Transform):
|
||||||
|
def __init__(self, options=None):
|
||||||
|
"""Called by PlatformIO to pass context"""
|
||||||
|
miniterm.Transform.__init__(self)
|
||||||
|
|
||||||
|
self.options = options or {}
|
||||||
|
self.project_dir = self.options.get("project_dir")
|
||||||
|
self.environment = self.options.get("environment")
|
||||||
|
|
||||||
|
self.config = ProjectConfig.get_instance()
|
||||||
|
if not self.environment:
|
||||||
|
default_envs = self.config.default_envs()
|
||||||
|
if default_envs:
|
||||||
|
self.environment = default_envs[0]
|
||||||
|
elif self.config.envs():
|
||||||
|
self.environment = self.config.envs()[0]
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
"""Called by the miniterm library when the filter is actually used"""
|
||||||
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def NAME(self):
|
||||||
|
raise NotImplementedError("Please declare NAME attribute for the filter class")
|
||||||
|
|
||||||
|
|
||||||
|
def register_filters(platform=None, options=None):
|
||||||
|
# project filters
|
||||||
|
load_monitor_filters(
|
||||||
|
ProjectConfig.get_instance().get("platformio", "monitor_dir"),
|
||||||
|
prefix="filter_",
|
||||||
|
options=options,
|
||||||
|
)
|
||||||
|
# platform filters
|
||||||
|
if platform:
|
||||||
|
load_monitor_filters(
|
||||||
|
os.path.join(platform.get_dir(), "monitor"),
|
||||||
|
prefix="filter_",
|
||||||
|
options=options,
|
||||||
|
)
|
||||||
|
# load package filters
|
||||||
|
pm = ToolPackageManager()
|
||||||
|
for pkg in pm.get_installed():
|
||||||
|
load_monitor_filters(
|
||||||
|
os.path.join(pkg.path, "monitor"), prefix="filter_", options=options
|
||||||
|
)
|
||||||
|
# default filters
|
||||||
|
load_monitor_filters(os.path.dirname(__file__), options=options)
|
||||||
|
|
||||||
|
|
||||||
|
def load_monitor_filters(monitor_dir, prefix=None, options=None):
|
||||||
|
if not os.path.isdir(monitor_dir):
|
||||||
|
return
|
||||||
|
for name in os.listdir(monitor_dir):
|
||||||
|
if (prefix and not name.startswith(prefix)) or not name.endswith(".py"):
|
||||||
|
continue
|
||||||
|
path = os.path.join(monitor_dir, name)
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
continue
|
||||||
|
load_monitor_filter(path, options)
|
||||||
|
|
||||||
|
|
||||||
|
def load_monitor_filter(path, options=None):
|
||||||
|
name = os.path.basename(path)
|
||||||
|
name = name[: name.find(".")]
|
||||||
|
module = load_python_module("platformio.device.monitor.filters.%s" % name, path)
|
||||||
|
for cls in get_object_members(module).values():
|
||||||
|
if (
|
||||||
|
not inspect.isclass(cls)
|
||||||
|
or not issubclass(cls, DeviceMonitorFilterBase)
|
||||||
|
or cls == DeviceMonitorFilterBase
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
obj = cls(options)
|
||||||
|
miniterm.TRANSFORMATIONS[obj.NAME] = obj
|
||||||
|
return True
|
38
platformio/device/monitor/filters/hexlify.py
Normal file
38
platformio/device/monitor/filters/hexlify.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import serial
|
||||||
|
|
||||||
|
from platformio.device.monitor.filters.base import DeviceMonitorFilterBase
|
||||||
|
|
||||||
|
|
||||||
|
class Hexlify(DeviceMonitorFilterBase):
|
||||||
|
NAME = "hexlify"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self._counter = 0
|
||||||
|
|
||||||
|
def rx(self, text):
|
||||||
|
result = ""
|
||||||
|
for b in serial.iterbytes(text):
|
||||||
|
if (self._counter % 16) == 0:
|
||||||
|
result += "\n{:04X} | ".format(self._counter)
|
||||||
|
asciicode = ord(b)
|
||||||
|
if asciicode <= 255:
|
||||||
|
result += "{:02X} ".format(asciicode)
|
||||||
|
else:
|
||||||
|
result += "?? "
|
||||||
|
self._counter += 1
|
||||||
|
return result
|
45
platformio/device/monitor/filters/log2file.py
Normal file
45
platformio/device/monitor/filters/log2file.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import io
|
||||||
|
import os.path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from platformio.device.monitor.filters.base import DeviceMonitorFilterBase
|
||||||
|
|
||||||
|
|
||||||
|
class LogToFile(DeviceMonitorFilterBase):
|
||||||
|
NAME = "log2file"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self._log_fp = None
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
log_file_name = "platformio-device-monitor-%s.log" % datetime.now().strftime(
|
||||||
|
"%y%m%d-%H%M%S"
|
||||||
|
)
|
||||||
|
print("--- Logging an output to %s" % os.path.abspath(log_file_name))
|
||||||
|
# pylint: disable=consider-using-with
|
||||||
|
self._log_fp = io.open(log_file_name, "w", encoding="utf-8")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if self._log_fp:
|
||||||
|
self._log_fp.close()
|
||||||
|
|
||||||
|
def rx(self, text):
|
||||||
|
self._log_fp.write(text)
|
||||||
|
self._log_fp.flush()
|
||||||
|
return text
|
38
platformio/device/monitor/filters/send_on_enter.py
Normal file
38
platformio/device/monitor/filters/send_on_enter.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from platformio.device.monitor.filters.base import DeviceMonitorFilterBase
|
||||||
|
|
||||||
|
|
||||||
|
class SendOnEnter(DeviceMonitorFilterBase):
|
||||||
|
NAME = "send_on_enter"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self._buffer = ""
|
||||||
|
|
||||||
|
if self.options.get("eol") == "CR":
|
||||||
|
self._eol = "\r"
|
||||||
|
elif self.options.get("eol") == "LF":
|
||||||
|
self._eol = "\n"
|
||||||
|
else:
|
||||||
|
self._eol = "\r\n"
|
||||||
|
|
||||||
|
def tx(self, text):
|
||||||
|
self._buffer += text
|
||||||
|
if self._buffer.endswith(self._eol):
|
||||||
|
text = self._buffer
|
||||||
|
self._buffer = ""
|
||||||
|
return text
|
||||||
|
return ""
|
37
platformio/device/monitor/filters/time.py
Normal file
37
platformio/device/monitor/filters/time.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from platformio.device.monitor.filters.base import DeviceMonitorFilterBase
|
||||||
|
|
||||||
|
|
||||||
|
class Timestamp(DeviceMonitorFilterBase):
|
||||||
|
NAME = "time"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self._line_started = False
|
||||||
|
|
||||||
|
def rx(self, text):
|
||||||
|
if self._line_started and "\n" not in text:
|
||||||
|
return text
|
||||||
|
timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
||||||
|
if not self._line_started:
|
||||||
|
self._line_started = True
|
||||||
|
text = "%s > %s" % (timestamp, text)
|
||||||
|
if text.endswith("\n"):
|
||||||
|
self._line_started = False
|
||||||
|
return text[:-1].replace("\n", "\n%s > " % timestamp) + "\n"
|
||||||
|
return text.replace("\n", "\n%s > " % timestamp)
|
Reference in New Issue
Block a user