Embedding Guide#
pw_console: Multi-purpose pluggable interactive console for dev & manufacturing
Using embed()#
pw console
is invoked by calling PwConsoleEmbed().embed()
in your
own Python script. For a complete example of an embedded device console script see
pw_system/py/pw_system/console.py.
pw_console embed class.
- class pw_console.embed.PwConsoleEmbed(
- global_vars=None,
- local_vars=None,
- loggers: dict[str, Iterable[Logger]] | Iterable | None = None,
- test_mode=False,
- repl_startup_message: str | None = None,
- help_text: str | None = None,
- app_title: str | None = None,
- config_file_path: str | Path | None = None,
Bases:
object
Embed class for customizing the console before startup.
- __init__(
- global_vars=None,
- local_vars=None,
- loggers: dict[str, Iterable[Logger]] | Iterable | None = None,
- test_mode=False,
- repl_startup_message: str | None = None,
- help_text: str | None = None,
- app_title: str | None = None,
- config_file_path: str | Path | None = None,
Call this to embed pw console at the call point within your program.
Example usage:
import logging from pw_console import PwConsoleEmbed # Create the pw_console embed instance console = PwConsoleEmbed( global_vars=globals(), local_vars=locals(), loggers={ 'Host Logs': [ logging.getLogger(__package__), logging.getLogger(__name__), ], 'Device Logs': [ logging.getLogger('usb_gadget'), ], }, app_title='My Awesome Console', config_file_path='/home/user/project/.pw_console.yaml', ) # Optional: Add custom completions console.add_sentence_completer( { 'some_function', 'Function', 'some_variable', 'Variable', } ) # Setup Python loggers to output to a file instead of STDOUT. console.setup_python_logging() # Then run the console with: console.embed()
- Parameters:
global_vars – dictionary representing the desired global symbol table. Similar to what is returned by globals().
local_vars – dictionary representing the desired local symbol table. Similar to what is returned by locals().
loggers –
dict with keys of log window titles and values of either:
List of logging.getLogger() instances.
A single pw_console.log_store.LogStore instance.
app_title – Custom title text displayed in the user interface.
repl_startup_message – Custom text shown by default in the repl output pane.
help_text – Custom text shown at the top of the help window before keyboard shortcuts.
config_file_path – Path to a pw_console yaml config file.
- add_bottom_toolbar(toolbar: WindowPaneToolbar) None #
Include a toolbar plugin to display at the bottom of the screen.
Bottom toolbars appear below all window panes and span the full width of the screen.
- Parameters:
toolbar – Instance of the WindowPaneToolbar class.
- add_floating_window_plugin(window_pane: FloatingWindowPane, **float_args) None #
Include a custom floating window pane plugin.
This adds a FloatingWindowPane class to the pw_console UI. The first argument should be the window to add and the remaining keyword arguments are passed to the prompt_toolkit Float() class. This allows positioning of the floating window. By default the floating window will be centered. To anchor the window to a side or corner of the screen set the
left
,right
,top
, orbottom
keyword args.For example:
from pw_console import PwConsoleEmbed console = PwConsoleEmbed(...) my_plugin = MyPlugin() # Anchor this floating window 2 rows away from the top and 4 columns # away from the left edge of the screen. console.add_floating_window_plugin(my_plugin, top=2, left=4)
See all possible keyword args in the prompt_toolkit documentation: https://python-prompt-toolkit.readthedocs.io/en/stable/pages/reference.html#prompt_toolkit.layout.Float
- Parameters:
window_pane – Any instance of the FloatingWindowPane class.
left – Distance to the left edge of the screen
right – Distance to the right edge of the screen
top – Distance to the top edge of the screen
bottom – Distance to the bottom edge of the screen
- add_sentence_completer(word_meta_dict: dict[str, str], ignore_case=True) None #
Include a custom completer that matches on the entire repl input.
- Parameters:
word_meta_dict – dictionary representing the sentence completions and descriptions. Keys are completion text, values are descriptions.
- add_top_toolbar(toolbar: WindowPaneToolbar) None #
Include a toolbar plugin to display on the top of the screen.
Top toolbars appear above all window panes and just below the main menu bar. They span the full width of the screen.
- Parameters:
toolbar – Instance of the WindowPaneToolbar class.
- add_window_plugin(window_pane: WindowPane) None #
Include a custom window pane plugin.
- Parameters:
window_pane – Any instance of the WindowPane class.
- embed(override_window_config: dict | None = None) None #
Start the console.
- hide_windows(*window_titles) None #
Hide window panes specified by title on console startup.
- setup_python_logging(
- last_resort_filename: str | None = None,
- loggers_with_no_propagation: Iterable[Logger] | None = None,
Setup friendly logging for full-screen prompt_toolkit applications.
This function sets up Python log handlers to be friendly for full-screen prompt_toolkit applications. That is, logging to terminal STDOUT and STDERR is disabled so the terminal user interface can be drawn.
Specifically, all Python STDOUT and STDERR log handlers are disabled. It also sets log propagation to True. to ensure that all log messages are sent to the root logger.
- Parameters:
last_resort_filename – If specified use this file as a fallback for unhandled Python logging messages. Normally Python will output any log messages with no handlers to STDERR as a fallback. If None, a temp file will be created instead. See Python documentation on logging.lastResort for more info.
loggers_with_no_propagation – List of logger instances to skip setting
propagate = True
. This is useful if you would like log messages from a particular source to not appear in the root logger.
- class pw_console.log_store.LogStore(prefs: ConsolePrefs | None = None)#
Bases:
Handler
Pigweed Console logging handler.
This is a Python logging.Handler class used to store logs for display in the pw_console user interface.
You may optionally add this as a handler to an existing logger instances. This will be required if logs need to be captured for display in the pw_console UI before the user interface is running.
Example usage:
import logging from pw_console import PwConsoleEmbed, LogStore _DEVICE_LOG = logging.getLogger('usb_gadget') # Create a log store and add as a handler. device_log_store = LogStore() _DEVICE_LOG.addHander(device_log_store) # Start communication with your device here, before embedding # pw_console. # Create the pw_console embed instance console = PwConsoleEmbed( global_vars=globals(), local_vars=locals(), loggers={ 'Host Logs': [ logging.getLogger(__package__), logging.getLogger(__name__), ], # Set the LogStore as the value of this logger window. 'Device Logs': device_log_store, }, app_title='My Awesome Console', ) console.setup_python_logging() console.embed()
- __init__(prefs: ConsolePrefs | None = None)#
Initializes the LogStore instance.
Adding Plugins#
User plugin instances are created before starting-up and passed to the Pigweed
Console embed instance. Typically, a console is started by creating a
PwConsoleEmbed()
instance, calling customization functions, then calling
.embed()
as shown in Using embed(). Adding plugins functions similarly by
calling add_top_toolbar
, add_bottom_toolbar
,
add_floating_window_plugin
or add_window_plugin
. For example:
# Create plugin instances
user_toolbar1 = DeviceStatusToolbar(device=client.client.channel(1))
user_toolbar2 = BandwithToolbar()
user_device_window = CustomWindowPlugin()
console = PwConsoleEmbed(
global_vars=local_variables,
loggers={
'Device Logs': [logging.getLogger('rpc_device')],
'Host Logs': [logging.getLogger()],
},
...
)
# Add toolbar plugins
console.add_top_toolbar(user_toolbar1)
console.add_bottom_toolbar(user_toolbar2)
# Add Window plugins
console.add_window_plugin(user_device_window)
# Start the console
console.embed()
Adding Log Metadata#
pw_console
can display log messages in a table with justified columns for
metadata fields provided by pw_log_tokenized.
It is also possible to manually add values that should be displayed in columns
using the extra
keyword argument when logging from Python. See the Python’s
logging documentation for how extra
works. A dict of name, value pairs can
be passed in as the extra_metadata_fields
variable. For example, the
following code will create a log message with two custom columns titled
module
and timestamp
.
import logging
LOG = logging.getLogger('log_source_1')
LOG.info(
'Hello there!',
extra={
'extra_metadata_fields': {
'module': 'cool',
'timestamp': 1.2345,
}
}
)
Debugging Serial Data#
pw_console
is often used to communicate with devices using pySerial or
pw_console.socket_client.SocketClient
. To monitor the raw data flowing over
the wire, pw_console
provides simple wrappers for pySerial and socket client
instances that log data for each read and write call.
Logging data with PySerial#
# Instead of 'import serial' use this import:
from pw_console.pyserial_wrapper import SerialWithLogging
serial_device = SerialWithLogging('/dev/ttyUSB0', 115200, timeout=1)
Logging data with sockets#
from pw_console.socket_client import SocketClientWithLogging
# Name resolution with explicit port
serial_device = SocketClientWithLogging('localhost:1234')
# Name resolution with default port.
serial_device = SocketClientWithLogging('pigweed.dev')
# Link-local IPv6 address with explicit port.
serial_device = SocketClientWithLogging('[fe80::100%enp1s0]:1234')
# Link-local IPv6 address with default port.
serial_device = SocketClientWithLogging('[fe80::100%enp1s0]')
# IPv4 address with port.
serial_device = SocketClientWithLogging('1.2.3.4:5678')
Tip
The SocketClient
takes an optional callback called when a disconnect is
detected. The pw_system
console provides an example reconnect routine.
With the above examples each serial_device.read
and write
call will
create a log message to the pw_console.serial_debug_logger
Python
logger. This logger can then be included as a log window pane in the
PwConsoleEmbed()
call.
import logging
from pw_console import PwConsoleEmbed
console = PwConsoleEmbed(
global_vars=globals(),
local_vars=locals(),
loggers={
'Host Logs': [
# Root Python logger
logging.getLogger(''),
# Your current Python package logger.
logging.getLogger(__package__)
],
'Device Logs': [
logging.getLogger('usb_gadget')
],
'Serial Debug': [
# New log window to display serial read and writes
logging.getLogger('pw_console.serial_debug_logger')
],
},
app_title='CoolConsole',
)
# Then run the console with:
console.embed()
Embeddeding other interpreters#
The Pigweed console is optimized for use with Pigweed, but other embedded Python interpreters may be used to interact with Pigweed devices. Popular options include IPython and bpython.
Embedding IPython is similar to embedding pw_console
. After import
IPython
, call IPython.start_ipython()
with the set of variables to expose
in the console. See Embedding IPython
for details.
IPython.start_ipython(
argv=[],
display_banner=False,
user_ns=local_variables,
)
return