aboutsummaryrefslogtreecommitdiff
path: root/pw_system/py/pw_system/console.py
diff options
context:
space:
mode:
Diffstat (limited to 'pw_system/py/pw_system/console.py')
-rw-r--r--pw_system/py/pw_system/console.py193
1 files changed, 125 insertions, 68 deletions
diff --git a/pw_system/py/pw_system/console.py b/pw_system/py/pw_system/console.py
index 49950884a..6f6405cac 100644
--- a/pw_system/py/pw_system/console.py
+++ b/pw_system/py/pw_system/console.py
@@ -38,6 +38,7 @@ from inspect import cleandoc
import logging
from pathlib import Path
import sys
+import time
from types import ModuleType
from typing import (
Any,
@@ -49,7 +50,6 @@ from typing import (
Optional,
Union,
)
-import socket
import serial
import IPython # type: ignore
@@ -60,8 +60,11 @@ from pw_console.log_store import LogStore
from pw_console.plugins.bandwidth_toolbar import BandwidthToolbar
from pw_console.pyserial_wrapper import SerialWithLogging
from pw_console.python_logging import create_temp_log_file, JsonLogFormatter
+from pw_console.socket_client import SocketClient, SocketClientWithLogging
+from pw_hdlc import rpc
from pw_rpc.console_tools.console import flattened_rpc_completions
from pw_system.device import Device
+from pw_system.device_tracing import DeviceWithTracing
from pw_tokenizer.detokenize import AutoUpdatingDetokenizer
# Default proto imports:
@@ -69,20 +72,21 @@ from pw_log.proto import log_pb2
from pw_metric_proto import metric_service_pb2
from pw_thread_protos import thread_snapshot_service_pb2
from pw_unit_test_proto import unit_test_pb2
+from pw_file import file_pb2
+from pw_trace_protos import trace_service_pb2
+from pw_transfer import transfer_pb2
_LOG = logging.getLogger('tools')
_DEVICE_LOG = logging.getLogger('rpc_device')
_SERIAL_DEBUG = logging.getLogger('pw_console.serial_debug_logger')
_ROOT_LOG = logging.getLogger()
-PW_RPC_MAX_PACKET_SIZE = 256
-SOCKET_SERVER = 'localhost'
-SOCKET_PORT = 33000
MKFIFO_MODE = 0o666
-def _parse_args():
- """Parses and returns the command line arguments."""
+def get_parser() -> argparse.ArgumentParser:
+ """Gets argument parser with console arguments."""
+
parser = argparse.ArgumentParser(
prog="python -m pw_system.console", description=__doc__
)
@@ -157,8 +161,13 @@ def _parse_args():
'-s',
'--socket-addr',
type=str,
- help='use socket to connect to server, type default for\
- localhost:33000, or manually input the server address:port',
+ help=(
+ 'Socket address used to connect to server. Type "default" to use '
+ 'localhost:33000, pass the server address and port as '
+ 'address:port, or prefix the path to a forwarded socket with '
+ f'"{SocketClient.FILE_SOCKET_SERVER}:" as '
+ f'{SocketClient.FILE_SOCKET_SERVER}:path_to_file.'
+ ),
)
parser.add_argument(
"--token-databases",
@@ -179,6 +188,13 @@ def _parse_args():
help='glob pattern for .proto files.',
)
parser.add_argument(
+ '-f',
+ '--ticks_per_second',
+ type=int,
+ dest='ticks_per_second',
+ help=('The clock rate of the trace events.'),
+ )
+ parser.add_argument(
'-v',
'--verbose',
action='store_true',
@@ -191,7 +207,7 @@ def _parse_args():
help='Use IPython instead of pw_console.',
)
- # TODO(b/248257406) Use argparse.BooleanOptionalAction when Python 3.8 is
+ # TODO: b/248257406 - Use argparse.BooleanOptionalAction when Python 3.8 is
# no longer supported.
parser.add_argument(
'--rpc-logging',
@@ -207,6 +223,38 @@ def _parse_args():
help="Don't use pw_rpc based logging.",
)
+ # TODO: b/248257406 - Use argparse.BooleanOptionalAction when Python 3.8 is
+ # no longer supported.
+ parser.add_argument(
+ '--hdlc-encoding',
+ action='store_true',
+ default=True,
+ help='Use HDLC encoding on transfer interfaces.',
+ )
+
+ parser.add_argument(
+ '--no-hdlc-encoding',
+ action='store_false',
+ dest='hdlc_encoding',
+ help="Don't use HDLC encoding on transfer interface.",
+ )
+
+ parser.add_argument(
+ '--channel-id',
+ type=int,
+ default=rpc.DEFAULT_CHANNEL_ID,
+ help="Channel ID used in RPC communications.",
+ )
+
+ return parser
+
+
+def _parse_args(args: Optional[argparse.Namespace] = None):
+ """Parses and returns the command line arguments."""
+ if args is not None:
+ return args
+
+ parser = get_parser()
return parser.parse_args()
@@ -269,8 +317,10 @@ def _start_python_terminal( # pylint: disable=too-many-arguments
if use_ipython:
print(welcome_message)
- IPython.terminal.embed.InteractiveShellEmbed().mainloop(
- local_ns=local_variables, module=argparse.Namespace()
+ IPython.start_ipython(
+ argv=[],
+ display_banner=False,
+ user_ns=local_variables,
)
return
@@ -307,32 +357,12 @@ def _start_python_terminal( # pylint: disable=too-many-arguments
interactive_console.embed()
-class SocketClientImpl:
- def __init__(self, config: str):
- self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- socket_server = ''
- socket_port = 0
-
- if config == 'default':
- socket_server = SOCKET_SERVER
- socket_port = SOCKET_PORT
- else:
- socket_server, socket_port_str = config.split(':')
- socket_port = int(socket_port_str)
- self.socket.connect((socket_server, socket_port))
-
- def write(self, data: bytes):
- self.socket.sendall(data)
-
- def read(self, num_bytes: int = PW_RPC_MAX_PACKET_SIZE):
- return self.socket.recv(num_bytes)
-
-
# pylint: disable=too-many-arguments,too-many-locals
def console(
device: str,
baudrate: int,
proto_globs: Collection[str],
+ ticks_per_second: Optional[int],
token_databases: Collection[Path],
socket_addr: str,
logfile: str,
@@ -347,6 +377,8 @@ def console(
merge_device_and_host_logs: bool = False,
rpc_logging: bool = True,
use_ipython: bool = False,
+ channel_id: int = rpc.DEFAULT_CHANNEL_ID,
+ hdlc_encoding: bool = True,
) -> int:
"""Starts an interactive RPC console for HDLC."""
# argparse.FileType doesn't correctly handle '-' for binary files.
@@ -418,7 +450,11 @@ def console(
detokenizer = None
if token_databases:
- detokenizer = AutoUpdatingDetokenizer(*token_databases)
+ token_databases_with_domains = [] * len(token_databases)
+ for token_database in token_databases:
+ token_databases_with_domains.append(str(token_database) + "#trace")
+
+ detokenizer = AutoUpdatingDetokenizer(*token_databases_with_domains)
detokenizer.show_errors = True
protos: List[Union[ModuleType, Path]] = list(_expand_globs(proto_globs))
@@ -435,6 +471,9 @@ def console(
protos.extend(compiled_protos)
protos.append(metric_service_pb2)
protos.append(thread_snapshot_service_pb2)
+ protos.append(file_pb2)
+ protos.append(trace_service_pb2)
+ protos.append(transfer_pb2)
if not protos:
_LOG.critical(
@@ -449,12 +488,9 @@ def console(
', '.join(proto_globs),
)
- serial_impl = serial.Serial
- if serial_debug:
- serial_impl = SerialWithLogging
-
timestamp_decoder = None
if socket_addr is None:
+ serial_impl = SerialWithLogging if serial_debug else serial.Serial
serial_device = serial_impl(
device,
baudrate,
@@ -464,7 +500,7 @@ def console(
# https://pythonhosted.org/pyserial/pyserial_api.html#serial.Serial
timeout=0.1,
)
- read = lambda: serial_device.read(8192)
+ reader = rpc.SerialReader(serial_device, 8192)
write = serial_device.write
# Overwrite decoder for serial device.
@@ -474,47 +510,68 @@ def console(
timestamp_decoder = milliseconds_to_string
else:
+ socket_impl = SocketClientWithLogging if serial_debug else SocketClient
+
+ def disconnect_handler(socket_device: SocketClient) -> None:
+ """Attempts to reconnect on disconnected socket."""
+ _LOG.error('Socket disconnected. Will retry to connect.')
+ while True:
+ try:
+ socket_device.connect()
+ break
+ except: # pylint: disable=bare-except
+ # Ignore errors and retry to reconnect.
+ time.sleep(1)
+ _LOG.info('Successfully reconnected')
+
try:
- socket_device = SocketClientImpl(socket_addr)
- read = socket_device.read
+ socket_device = socket_impl(
+ socket_addr, on_disconnect=disconnect_handler
+ )
+ reader = rpc.SelectableReader(socket_device)
write = socket_device.write
except ValueError:
_LOG.exception('Failed to initialize socket at %s', socket_addr)
return 1
- device_client = Device(
- 1,
- read,
- write,
- protos,
- detokenizer,
- timestamp_decoder=timestamp_decoder,
- rpc_timeout_s=5,
- use_rpc_logging=rpc_logging,
- )
-
- _start_python_terminal(
- device=device_client,
- device_log_store=device_log_store,
- root_log_store=root_log_store,
- serial_debug_log_store=serial_debug_log_store,
- log_file=logfile,
- host_logfile=host_logfile,
- device_logfile=device_logfile,
- json_logfile=json_logfile,
- serial_debug=serial_debug,
- config_file_path=config_file,
- use_ipython=use_ipython,
- )
+ with reader:
+ device_client = DeviceWithTracing(
+ ticks_per_second,
+ channel_id,
+ reader,
+ write,
+ protos,
+ detokenizer=detokenizer,
+ timestamp_decoder=timestamp_decoder,
+ rpc_timeout_s=5,
+ use_rpc_logging=rpc_logging,
+ use_hdlc_encoding=hdlc_encoding,
+ )
+ with device_client:
+ _start_python_terminal(
+ device=device_client,
+ device_log_store=device_log_store,
+ root_log_store=root_log_store,
+ serial_debug_log_store=serial_debug_log_store,
+ log_file=logfile,
+ host_logfile=host_logfile,
+ device_logfile=device_logfile,
+ json_logfile=json_logfile,
+ serial_debug=serial_debug,
+ config_file_path=config_file,
+ use_ipython=use_ipython,
+ )
return 0
-def main() -> int:
- return console(**vars(_parse_args()))
+def main(args: Optional[argparse.Namespace] = None) -> int:
+ return console(**vars(_parse_args(args)))
-def main_with_compiled_protos(compiled_protos):
- return console(**vars(_parse_args()), compiled_protos=compiled_protos)
+def main_with_compiled_protos(
+ compiled_protos, args: Optional[argparse.Namespace] = None
+):
+ return console(**vars(_parse_args(args)), compiled_protos=compiled_protos)
if __name__ == '__main__':