diff --git a/HISTORY.rst b/HISTORY.rst index 98551c98..fba1bf03 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -9,6 +9,7 @@ PlatformIO Core 4 4.2.2 (2020-??-??) ~~~~~~~~~~~~~~~~~~ +* Control device monitor output with `filters and text transformations `__ * Added support for Arm Mbed "module.json" ``dependencies`` field (`issue #3400 `_) * Improved support for Arduino "library.properties" ``depends`` field * Fixed an issue when quitting from PlatformIO IDE does not shutdown PIO Home server diff --git a/docs b/docs index 1506c1bc..d6a2968e 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 1506c1bcbfc8a7a9564a225e07cafd766fb6e235 +Subproject commit d6a2968edce17a75db7c8847c8e2fa925e192b1d diff --git a/platformio/commands/device/__init__.py b/platformio/commands/device/__init__.py new file mode 100644 index 00000000..bcee03cc --- /dev/null +++ b/platformio/commands/device/__init__.py @@ -0,0 +1,15 @@ +# Copyright (c) 2014-present PlatformIO +# +# 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.commands.device.filters.base import DeviceMonitorFilter diff --git a/platformio/commands/device.py b/platformio/commands/device/command.py similarity index 64% rename from platformio/commands/device.py rename to platformio/commands/device/command.py index 245ae743..5c90f205 100644 --- a/platformio/commands/device.py +++ b/platformio/commands/device/command.py @@ -20,9 +20,9 @@ import click from serial.tools import miniterm from platformio import exception, fs, util -from platformio.compat import dump_json_to_unicode, load_python_module +from platformio.commands.device import helpers as device_helpers +from platformio.compat import dump_json_to_unicode from platformio.managers.platform import PlatformFactory -from platformio.project.config import ProjectConfig from platformio.project.exception import NotPlatformIOProjectError @@ -135,7 +135,7 @@ def device_list( # pylint: disable=too-many-branches 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 text transformation") +@click.option("--filter", "-f", multiple=True, help="Add filters/text transformations") @click.option( "--eol", default="CRLF", @@ -174,15 +174,11 @@ def device_list( # pylint: disable=too-many-branches help="Load configuration from `platformio.ini` and specified environment", ) def device_monitor(**kwargs): # pylint: disable=too-many-branches - click.echo( - "Looking for advanced Serial Monitor with UI? " - "Check http://bit.ly/pio-advanced-monitor" - ) project_options = {} try: with fs.cd(kwargs["project_dir"]): - project_options = get_project_options(kwargs["environment"]) - kwargs = apply_project_monitor_options(kwargs, project_options) + project_options = device_helpers.get_project_options(kwargs["environment"]) + kwargs = device_helpers.apply_project_monitor_options(kwargs, project_options) except NotPlatformIOProjectError: pass @@ -190,17 +186,17 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches if "platform" in project_options: with fs.cd(kwargs["project_dir"]): platform = PlatformFactory.newPlatform(project_options["platform"]) - register_platform_filters(platform, kwargs["project_dir"], kwargs["environment"]) + device_helpers.register_platform_filters( + platform, kwargs["project_dir"], kwargs["environment"] + ) if not kwargs["port"]: ports = util.get_serial_ports(filter_hwid=True) if len(ports) == 1: kwargs["port"] = ports[0]["port"] elif "platform" in project_options and "board" in project_options: - board_hwids = get_board_hwids( - kwargs["project_dir"], - platform, - project_options["board"], + board_hwids = device_helpers.get_board_hwids( + kwargs["project_dir"], platform, project_options["board"], ) for item in ports: for hwid in board_hwids: @@ -217,12 +213,18 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches break # override system argv with patched options - sys.argv = ["monitor"] + options_to_argv( + sys.argv = ["monitor"] + device_helpers.options_to_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 http://bit.ly/pio-monitor-filters") try: miniterm.main( default_port=kwargs["port"], @@ -232,98 +234,3 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches ) except Exception as e: raise exception.MinitermException(e) - -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 options_to_argv(cli_options, project_options, ignore=None): - result = project_options.get("monitor_flags", []) - for k, v in cli_options.items(): - if v is None or (ignore and k in ignore): - continue - k = "--" + k.replace("_", "-") - if k in project_options.get("monitor_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 - - -def get_project_options(environment=None): - config = ProjectConfig.get_instance() - config.validate(envs=[environment] if environment else None) - if not environment: - default_envs = config.default_envs() - if default_envs: - environment = default_envs[0] - else: - environment = config.envs()[0] - return config.items(env=environment, as_dict=True) - - -def get_board_hwids(project_dir, platform, board): - with fs.cd(project_dir): - return ( - platform - .board_config(board) - .get("build.hwids", []) - ) - -class DeviceMonitorFilter(miniterm.Transform): - # NAME = "esp_exception_decoder" - all filters must have one - - # Called by PlatformIO to pass context - def __init__(self, project_dir, environment): - super(DeviceMonitorFilter, self).__init__() - - self.project_dir = project_dir - self.environment = environment - - self.config = ProjectConfig.get_instance() - if not self.environment: - default_envs = self.config.default_envs() - if default_envs: - self.environment = default_envs[0] - else: - self.environment = self.config.envs()[0] - - # Called by the miniterm library when the filter is acutally used - def __call__(self): - return self - -def register_platform_filters(platform, project_dir, environment): - monitor_dir = os.path.join(platform.get_dir(), "monitor") - if not os.path.isdir(monitor_dir): - return - - for fn in os.listdir(monitor_dir): - if not fn.startswith("filter_") or not fn.endswith(".py"): - continue - path = os.path.join(monitor_dir, fn) - if not os.path.isfile(path): - continue - - dot = fn.find(".") - module = load_python_module("platformio.commands.device.%s" % fn[:dot], path) - for key in dir(module): - member = getattr(module, key) - try: - if issubclass(member, DeviceMonitorFilter) and hasattr(member, "NAME"): - miniterm.TRANSFORMATIONS[member.NAME] = member(project_dir, environment) - except TypeError: - pass diff --git a/platformio/commands/device/filters/__init__.py b/platformio/commands/device/filters/__init__.py new file mode 100644 index 00000000..b0514903 --- /dev/null +++ b/platformio/commands/device/filters/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2014-present PlatformIO +# +# 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. diff --git a/platformio/commands/device/filters/base.py b/platformio/commands/device/filters/base.py new file mode 100644 index 00000000..c532eb18 --- /dev/null +++ b/platformio/commands/device/filters/base.py @@ -0,0 +1,42 @@ +# Copyright (c) 2014-present PlatformIO +# +# 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 serial.tools import miniterm + +from platformio.project.config import ProjectConfig + + +class DeviceMonitorFilter(miniterm.Transform): + def __init__(self, project_dir, environment): + """ Called by PlatformIO to pass context """ + super(DeviceMonitorFilter, self).__init__() + + self.project_dir = project_dir + self.environment = environment + + self.config = ProjectConfig.get_instance() + if not self.environment: + default_envs = self.config.default_envs() + if default_envs: + self.environment = default_envs[0] + else: + 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") diff --git a/platformio/commands/device/helpers.py b/platformio/commands/device/helpers.py new file mode 100644 index 00000000..17b72967 --- /dev/null +++ b/platformio/commands/device/helpers.py @@ -0,0 +1,102 @@ +# Copyright (c) 2014-present PlatformIO +# +# 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 import fs +from platformio.commands.device import DeviceMonitorFilter +from platformio.compat import get_object_members, load_python_module +from platformio.project.config import ProjectConfig + + +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 options_to_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 + + +def get_project_options(environment=None): + config = ProjectConfig.get_instance() + config.validate(envs=[environment] if environment else None) + if not environment: + default_envs = config.default_envs() + if default_envs: + environment = default_envs[0] + else: + environment = config.envs()[0] + return config.items(env=environment, as_dict=True) + + +def get_board_hwids(project_dir, platform, board): + with fs.cd(project_dir): + return platform.board_config(board).get("build.hwids", []) + + +def register_platform_filters(platform, project_dir, environment): + monitor_dir = os.path.join(platform.get_dir(), "monitor") + if not os.path.isdir(monitor_dir): + return + + for fn in os.listdir(monitor_dir): + if not fn.startswith("filter_") or not fn.endswith(".py"): + continue + path = os.path.join(monitor_dir, fn) + if not os.path.isfile(path): + continue + + module = load_python_module( + "platformio.commands.device.filters.%s" % fn[: fn.find(".")], path + ) + for cls in get_object_members(module).values(): + if ( + not inspect.isclass(cls) + or not issubclass(cls, DeviceMonitorFilter) + or cls == DeviceMonitorFilter + ): + continue + obj = cls(project_dir, environment) + miniterm.TRANSFORMATIONS[obj.NAME] = obj diff --git a/platformio/commands/remote.py b/platformio/commands/remote.py index ee950982..ca296b69 100644 --- a/platformio/commands/remote.py +++ b/platformio/commands/remote.py @@ -21,7 +21,8 @@ from time import sleep import click from platformio import exception, fs -from platformio.commands import device +from platformio.commands.device import helpers as device_helpers +from platformio.commands.device.command import device_monitor as cmd_device_monitor from platformio.managers.core import pioplus_call from platformio.project.exception import NotPlatformIOProjectError @@ -197,8 +198,8 @@ def device_monitor(ctx, **kwargs): project_options = {} try: with fs.cd(kwargs["project_dir"]): - project_options = device.get_project_options(kwargs["environment"]) - kwargs = device.apply_project_monitor_options(kwargs, project_options) + project_options = device_helpers.get_project_options(kwargs["environment"]) + kwargs = device_helpers.apply_project_monitor_options(kwargs, project_options) except NotPlatformIOProjectError: pass @@ -206,7 +207,7 @@ def device_monitor(ctx, **kwargs): def _tx_target(sock_dir): pioplus_argv = ["remote", "device", "monitor"] - pioplus_argv.extend(device.options_to_argv(kwargs, project_options)) + pioplus_argv.extend(device_helpers.options_to_argv(kwargs, project_options)) pioplus_argv.extend(["--sock", sock_dir]) try: pioplus_call(pioplus_argv) @@ -224,7 +225,7 @@ def device_monitor(ctx, **kwargs): return with open(sock_file) as fp: kwargs["port"] = fp.read() - ctx.invoke(device.device_monitor, **kwargs) + ctx.invoke(cmd_device_monitor, **kwargs) t.join(2) finally: fs.rmtree(sock_dir) diff --git a/platformio/commands/run/command.py b/platformio/commands/run/command.py index 5058c159..378eaf0d 100644 --- a/platformio/commands/run/command.py +++ b/platformio/commands/run/command.py @@ -21,7 +21,7 @@ import click from tabulate import tabulate from platformio import app, exception, fs, util -from platformio.commands.device import device_monitor as cmd_device_monitor +from platformio.commands.device.command import device_monitor as cmd_device_monitor from platformio.commands.run.helpers import clean_build_dir, handle_legacy_libdeps from platformio.commands.run.processor import EnvironmentProcessor from platformio.commands.test.processor import CTX_META_TEST_IS_RUNNING diff --git a/platformio/project/options.py b/platformio/project/options.py index d38ed566..ecf030b8 100644 --- a/platformio/project/options.py +++ b/platformio/project/options.py @@ -445,6 +445,14 @@ ProjectOptions = OrderedDict( oldnames=["monitor_baud"], default=9600, ), + ConfigEnvOption( + group="monitor", + name="monitor_filters", + description=( + "Apply the filters and text transformations to monitor output" + ), + multiple=True, + ), ConfigEnvOption( group="monitor", name="monitor_rts",