From 0df72411a09ef3421906c88c85e8d8b5e69d999f Mon Sep 17 00:00:00 2001 From: Ivan Kravets Date: Tue, 17 Mar 2020 23:08:57 +0200 Subject: [PATCH] Device Monitor Filter API, implement "time" and "log2file" filters // Resolve #981 Resolve #670 --- HISTORY.rst | 10 ++++- docs | 2 +- platformio/commands/device/command.py | 11 ++++- platformio/commands/device/filters/base.py | 4 +- .../commands/device/filters/log2file.py | 44 +++++++++++++++++++ platformio/commands/device/filters/time.py | 34 ++++++++++++++ platformio/commands/device/helpers.py | 36 ++++++++------- 7 files changed, 118 insertions(+), 23 deletions(-) create mode 100644 platformio/commands/device/filters/log2file.py create mode 100644 platformio/commands/device/filters/time.py diff --git a/HISTORY.rst b/HISTORY.rst index 622254cb..9ddfca12 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -9,7 +9,7 @@ PlatformIO Core 4 4.3.0 (2020-??-??) ~~~~~~~~~~~~~~~~~~ -* Added initial support for an official `PlatformIO for CLion IDE `__ plugin: +* Initial support for an official `PlatformIO for CLion IDE `__ plugin: - Smart C and C++ editor - Code refactoring @@ -18,7 +18,13 @@ PlatformIO Core 4 - Building, Uploading, Testing - Integrated debugger (inline variable view, conditional breakpoints, expressions, watchpoints, peripheral registers, multi-thread support, etc.) -* Control device monitor output with `filters and text transformations `__ (`pull #3383 `_) +* `Device Monitor 2.0 `__ + + - Added **PlatformIO Device Monitor Filter API** (dev-platforms can extend base device monitor with a custom functionality, such as exception decoding) (`pull #3383 `_) + - Configure project device monitor with `monitor_filters `__ option + - Show a timestamp for each new line with ``time`` filter (`issue #981 `_) + - `Capture device monitor output to a file `__ with ``log2file`` filter (`issue #670 `_) + * 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 ebf066f7..873114bd 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit ebf066f7911e9b39ead1d0278c2b530e27c3c8be +Subproject commit 873114bdbb091a26e25b61d9536b76439d5f9c3a diff --git a/platformio/commands/device/command.py b/platformio/commands/device/command.py index 5c90f205..e93b1214 100644 --- a/platformio/commands/device/command.py +++ b/platformio/commands/device/command.py @@ -174,6 +174,13 @@ 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 + # load default monitor filters + filters_dir = os.path.join(fs.get_source_dir(), "commands", "device", "filters") + for name in os.listdir(filters_dir): + if not name.endswith(".py"): + continue + device_helpers.load_monitor_filter(os.path.join(filters_dir, name)) + project_options = {} try: with fs.cd(kwargs["project_dir"]): @@ -221,10 +228,10 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches if not kwargs["quiet"]: click.echo( - "Available filters and text transformations: %s" + "--- Available filters and text transformations: %s" % ", ".join(sorted(miniterm.TRANSFORMATIONS.keys())) ) - click.echo("More details at http://bit.ly/pio-monitor-filters") + click.echo("--- More details at http://bit.ly/pio-monitor-filters") try: miniterm.main( default_port=kwargs["port"], diff --git a/platformio/commands/device/filters/base.py b/platformio/commands/device/filters/base.py index c532eb18..bc0880b3 100644 --- a/platformio/commands/device/filters/base.py +++ b/platformio/commands/device/filters/base.py @@ -18,7 +18,7 @@ from platformio.project.config import ProjectConfig class DeviceMonitorFilter(miniterm.Transform): - def __init__(self, project_dir, environment): + def __init__(self, project_dir=None, environment=None): """ Called by PlatformIO to pass context """ super(DeviceMonitorFilter, self).__init__() @@ -30,7 +30,7 @@ class DeviceMonitorFilter(miniterm.Transform): default_envs = self.config.default_envs() if default_envs: self.environment = default_envs[0] - else: + elif self.config.envs(): self.environment = self.config.envs()[0] def __call__(self): diff --git a/platformio/commands/device/filters/log2file.py b/platformio/commands/device/filters/log2file.py new file mode 100644 index 00000000..69118510 --- /dev/null +++ b/platformio/commands/device/filters/log2file.py @@ -0,0 +1,44 @@ +# 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 io +import os.path +from datetime import datetime + +from platformio.commands.device import DeviceMonitorFilter + + +class LogToFile(DeviceMonitorFilter): + NAME = "log2file" + + def __init__(self, *args, **kwargs): + super(LogToFile, self).__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)) + 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 diff --git a/platformio/commands/device/filters/time.py b/platformio/commands/device/filters/time.py new file mode 100644 index 00000000..203e6aa9 --- /dev/null +++ b/platformio/commands/device/filters/time.py @@ -0,0 +1,34 @@ +# 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 datetime import datetime + +from platformio.commands.device import DeviceMonitorFilter + + +class Timestamp(DeviceMonitorFilter): + NAME = "time" + + def __init__(self, *args, **kwargs): + super(Timestamp, self).__init__(*args, **kwargs) + self._first_text_received = False + + def rx(self, text): + if self._first_text_received and "\n" not in text: + return text + timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3] + if not self._first_text_received: + self._first_text_received = True + return "%s > %s" % (timestamp, text) + return text.replace("\n", "\n%s > " % timestamp) diff --git a/platformio/commands/device/helpers.py b/platformio/commands/device/helpers.py index 17b72967..3bfe8fc6 100644 --- a/platformio/commands/device/helpers.py +++ b/platformio/commands/device/helpers.py @@ -76,27 +76,31 @@ def get_board_hwids(project_dir, platform, board): return platform.board_config(board).get("build.hwids", []) +def load_monitor_filter(path, project_dir=None, environment=None): + name = os.path.basename(path) + name = name[: name.find(".")] + module = load_python_module("platformio.commands.device.filters.%s" % name, 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 + return True + + 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"): + for name in os.listdir(monitor_dir): + if not name.startswith("filter_") or not name.endswith(".py"): continue - path = os.path.join(monitor_dir, fn) + path = os.path.join(monitor_dir, name) 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 + load_monitor_filter(path, project_dir, environment)