From 68989ed8e4b4853a6562f6f29d04c373942ea786 Mon Sep 17 00:00:00 2001 From: "Leone, Mark A [LGS]" Date: Sat, 28 Jun 2025 18:06:09 -0400 Subject: [PATCH] Implement snapshot capability Rename plugin funcs to make implemenation more intuitive Add ID for snap. Remove redundant left/right id func. Show err if snap file not found Track per-file snapshot error --- .vscode/launch.json | 5 ++- drawing.py | 45 ++++++++++++++------ install_as_service.sh | 0 led_system_monitor.py | 61 +++++++++++++++++++++++----- patterns.py | 36 ++++++++++++++++ plugins/temp_fan_plugin.py | 9 ++-- snapshot_files/left/left-snap.json | 1 + snapshot_files/right/right-snap.json | 1 + 8 files changed, 128 insertions(+), 30 deletions(-) mode change 100644 => 100755 install_as_service.sh create mode 100644 snapshot_files/left/left-snap.json create mode 100644 snapshot_files/right/right-snap.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 948a236..8a82519 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,11 +6,12 @@ "configurations": [ { "name": "Python: Current File", - "type": "python", + "type": "debugpy", "request": "launch", "program": "${file}", "console": "integratedTerminal", - "justMyCode": true + "justMyCode": true, + "args": ["-ls", "left-snap.json", "-rs", "right-snap.json"] } ] } \ No newline at end of file diff --git a/drawing.py b/drawing.py index a3c221e..e2d6eec 100644 --- a/drawing.py +++ b/drawing.py @@ -6,6 +6,8 @@ import importlib.util import sys import os import re +import json +from pathlib import Path # Internal Dependencies from commands import Commands, send_command @@ -89,6 +91,21 @@ def draw_bar(grid, bar_ratio, bar_value, bar_x_offset = 1, y=0): grid[bar_x_offset+i,33-pixels_col:33] = bar_value else: grid[bar_x_offset+i,1:1+pixels_col] = bar_value + +warned = set() +def draw_snapshot(grid, fill_value, file, path, panel): + global warned + subdirs = [ f.name for f in os.scandir(path) if f.is_dir() and f.name in ['left', 'right']] + subdir = panel if panel in subdirs else '' + try: + with open(os.path.join(path, subdir, file)) as f: + snap = json.load(f) + grid[:,:] = np.array(snap).T * fill_value + except FileNotFoundError as e: + if not file in warned: + print(f"File {file} not found") + warned.add(file) + ## Border Draw Functions ## @@ -141,7 +158,7 @@ def draw_outline_border(grid, border_value): grid[8, :] = border_value # Right # Maps an app arg value to abstract app and border draw functions -metrics_funcs = { +direct_draw_funcs = { "cpu": { "fn": draw_spiral_vals, "border": draw_8_x_8_grid @@ -162,6 +179,10 @@ metrics_funcs = { "fn": draw_battery, "border": draw_1_x_2_vert_grid }, + "snap": { + "fn": draw_snapshot, + "border": lambda *x: None # No border + }, #noop "none": { "fn": lambda *x: x, @@ -171,25 +192,23 @@ metrics_funcs = { # Draws the app for the specified arg value def draw_app(app, *arguments, **kwargs): - metrics_funcs[app].get('fn')(*arguments, **kwargs) + direct_draw_funcs[app].get('fn')(*arguments, **kwargs) # Draws the border for the specified arg value def draw_app_border(app, *arguments): - metrics_funcs[app].get('border')(*arguments) + direct_draw_funcs[app].get('border')(*arguments) -# Draw the IDs of apps currently assigned to the top and bottom of the left panel -def draw_ids_left(grid, top_left, bot_left, fill_value): +# Draw the IDs of apps currently assigned to the top and bottom of a panel +def draw_ids(grid, top_left, bot_left, fill_value): fill_grid_top = id_patterns[top_left] fill_grid_bot = id_patterns[bot_left] grid[1:8, 1:16] = fill_grid_top * fill_value grid[1:8, 18:-1] = fill_grid_bot * fill_value -# Draw the IDs of apps currently assigned to the top and bottom of the right panel -def draw_ids_right(grid, top_right, bot_right, fill_value): - fill_grid_top = id_patterns[top_right] - fill_grid_bot = id_patterns[bot_right] - grid[1:8, 1:16] = fill_grid_top * fill_value - grid[1:8, 18:-1] = fill_grid_bot * fill_value +# Draw the ID of the app currently assigned to the full panel +def draw_id(grid, id, fill_value): + fill_grid = id_patterns[id] + grid[:,:] = fill_grid * fill_value def draw_to_LEDs(s, grid): for i in range(grid.shape[0]): @@ -245,8 +264,8 @@ if not re.search(r"--disable-plugins|-dp", str(sys.argv)): sys.modules[module_name] = module spec.loader.exec_module(module) - for k,v in module.metrics_funcs.items(): - metrics_funcs[k] = v + for k,v in module.direct_draw_funcs.items(): + direct_draw_funcs[k] = v from drawing import id_patterns for k,v in module.id_patterns.items(): diff --git a/install_as_service.sh b/install_as_service.sh old mode 100644 new mode 100755 diff --git a/led_system_monitor.py b/led_system_monitor.py index a064113..66192e8 100644 --- a/led_system_monitor.py +++ b/led_system_monitor.py @@ -10,7 +10,7 @@ import os # Internal Dependencies -from drawing import draw_outline_border, draw_ids_left, draw_ids_right, draw_app, draw_app_border, DrawingThread +from drawing import draw_outline_border, draw_ids, draw_id, draw_app, draw_app_border, DrawingThread from monitors import CPUMonitor, MemoryMonitor, BatteryMonitor, DiskMonitor, NetworkMonitor, get_monitor_brightness # External Dependencies @@ -101,11 +101,15 @@ def main(args): draw_app(arg, grid, last_network_upload, foreground_value, bar_x_offset=1, y=idx) draw_app(arg, grid, last_network_download, foreground_value, bar_x_offset=5, y=idx) + def draw_snap(grid, foreground_value, file, snap_path, panel): + draw_app("snap", grid, foreground_value, file, snap_path, panel) + app_functions = { "cpu": draw_cpu, "mem-bat": draw_mem_bat, "disk": draw_disk, "net": draw_net, + "snap": draw_snap, "none": lambda *x: x # noop } @@ -126,7 +130,13 @@ def main(args): app_functions[obj["name"]] = obj["fn"] ################################################# + if args.snapshot_duration > args.snapshot_interval: + print("Snapshot duration must be less than snapshot interval. Exiting...") + sys.exit(0) + start_time = time.time() while True: + elapsed_time = time.time() + show_snapshot = True if args.snapshot_interval == 0 or elapsed_time % args.snapshot_interval <= args.snapshot_duration else False try: screen_brightness = get_monitor_brightness() background_value = int(screen_brightness * (max_background_brightness - min_background_brightness) + min_background_brightness) @@ -134,26 +144,41 @@ def main(args): grid = np.zeros((9,34), dtype = int) active_keys = device.active_keys(verbose=True) if (MODIFIER_KEYS[0] in active_keys or MODIFIER_KEYS[1] in active_keys) and KEY_I in active_keys and not args.no_key_listener: - draw_outline_border(grid, background_value) - draw_ids_left(grid, args.top_left, args.bottom_left, foreground_value) + if args.left_snap and show_snapshot: + draw_id(grid, "snap", foreground_value) + else: + draw_outline_border(grid, background_value) + draw_ids(grid, args.top_left, args.bottom_left, foreground_value) left_drawing_queue.put(grid) grid = np.zeros((9,34), dtype = int) - draw_outline_border(grid, background_value) - draw_ids_right(grid, args.top_right, args.bottom_right, foreground_value) + if args.right_snap and show_snapshot: + draw_id(grid, "snap", foreground_value) + else: + draw_outline_border(grid, background_value) + draw_ids(grid, args.top_right, args.bottom_right, foreground_value) right_drawing_queue.put(grid) grid = np.zeros((9,34), dtype = int) time.sleep(0.1) continue - # Draw by quadrants (i.e. to top and bottom of left and right panels) + # Draw by half or whole panel, depending on program args for i, draw_queue in enumerate(drawing_queues): + grid = np.zeros((9,34), dtype = int) if i == 0: panel = 'left' - _args = [args.top_left, args.bottom_left] + if args.left_snap is not None and show_snapshot: + app_functions["snap"](grid, foreground_value, args.left_snap, args.snapshot_path, 'left') + draw_queue.put(grid) + continue + else: + _args = [args.top_left, args.bottom_left] else: panel = 'right' + if args.right_snap is not None and show_snapshot: + app_functions["snap"](grid, foreground_value, args.right_snap, args.snapshot_path, 'right') + draw_queue.put(grid) + continue _args = [args.top_right, args.bottom_right] - grid = np.zeros((9,34), dtype = int) for j, arg in enumerate(_args): if j == 0: idx = 0 @@ -165,7 +190,6 @@ def main(args): func = app_functions[arg] func(arg, grid, foreground_value, idx) except KeyError: - print(app_functions.keys()) print(f"Unrecognized display option {arg} for {loc} {panel}") if arg == 'mem-bat': arg = 'mem' # Single border draw for mem and bat together draw_app_border(arg, grid, background_value, idx) @@ -197,12 +221,12 @@ if __name__ == "__main__": sys.modules[module_name] = module spec.loader.exec_module(module) - app_names += module.metrics_funcs.keys() + app_names += map(lambda o: o["name"], module.app_funcs) ################################################################# parser = ArgumentParser(prog="FW LED System Monitor", add_help=False, description="Displays system performance metrics in the Framework 16 LED Matrix input module", formatter_class=ArgumentDefaultsHelpFormatter) - mode_group = parser.add_mutually_exclusive_group() + mode_group = parser.add_argument_group() mode_group.add_argument("--help", "-h", action="help", help="Show this help message and exit") @@ -215,6 +239,19 @@ if __name__ == "__main__": help="Metrics to display in the top section of the right matrix panel") addGroup.add_argument("--bottom-right", "-br", type=str, default="disk", choices=app_names, help="Metrics to display in the top section of the right matrix panel") + addGroup.add_argument("--left-snap", "-ls", type=str, default=None, + help="Snapshot file to display on the left panel. Specify * to cycle through all files in the snapshot dir") + addGroup.add_argument("--right-snap", "-rs", type=str, default=None, + help="Snapshot file to display on the right panel. Specify * to cycle through all files in the snapshot dir") + addGroup.add_argument("--snapshot-path", "-sp", type=str, default="snapshot_files", + help="The file path that contains either the snapshot files that may be displayed on either panel or " + + "'left' and 'right' directories that contain files that may be displayed on the respective panel") + addGroup.add_argument("--snapshot-interval", "-si", type=int, default=0, + help="The interval (in seconds) at which the selected snapshot files will be rendered. A value " + + "of zero means the snapshots should be rendered continuously") + addGroup.add_argument("--snapshot-duration", "-sd", type=int, default=0, + help="The number of seconds that the snapshot file will be rendered at the specified interval. Must be " + + "less than the value of --snapshot-interval") addGroup.add_argument("--no-key-listener", "-nkl", action="store_true", help="Do not listen for key presses") addGroup.add_argument("--disable-plugins", "-dp", action="store_true", help="Do not load any plugin code") @@ -224,6 +261,8 @@ if __name__ == "__main__": print(f"bottom left {args.bottom_left}") print(f"top right {args.top_right}") print(f"bottom right {args.bottom_right}") + print(f"left snap {args.left_snap}") + print(f"right snap {args.right_snap}") if args.no_key_listener: print("Key listener disabled") main(args) diff --git a/patterns.py b/patterns.py index 40a3106..fea7284 100644 --- a/patterns.py +++ b/patterns.py @@ -159,5 +159,41 @@ id_patterns = { [0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0], + ]).T, + "snap": np.array([ + [0,0,0,0,0,0,0,0,0], + [0,0,0,1,1,1,1,0,0], + [0,0,1,0,0,0,0,0,0], + [0,0,1,0,0,0,0,0,0], + [0,0,0,1,1,1,0,0,0], + [0,0,0,0,0,0,1,0,0], + [0,0,0,0,0,0,1,0,0], + [0,0,1,1,1,1,0,0,0], + [0,0,0,0,0,0,0,0,0], + [0,0,1,1,0,0,1,0,0], + [0,0,1,1,0,0,1,0,0], + [0,0,1,0,1,0,1,0,0], + [0,0,1,0,1,0,1,0,0], + [0,0,1,0,0,1,1,0,0], + [0,0,1,0,0,1,1,0,0], + [0,0,1,0,0,0,1,0,0], + [0,0,0,0,0,0,0,0,0], + [0,0,0,1,1,1,0,0,0], + [0,0,1,0,0,0,1,0,0], + [0,0,1,0,0,0,1,0,0], + [0,0,1,1,1,1,1,0,0], + [0,0,1,0,0,0,1,0,0], + [0,0,1,0,0,0,1,0,0], + [0,0,1,0,0,0,1,0,0], + [0,0,0,0,0,0,0,0,0], + [0,0,1,1,1,1,0,0,0], + [0,0,1,0,0,0,1,0,0], + [0,0,1,0,0,0,1,0,0], + [0,0,1,1,1,1,0,0,0], + [0,0,1,0,0,0,0,0,0], + [0,0,1,0,0,0,0,0,0], + [0,0,1,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0] ]).T } \ No newline at end of file diff --git a/plugins/temp_fan_plugin.py b/plugins/temp_fan_plugin.py index d985cf7..e31f5c2 100644 --- a/plugins/temp_fan_plugin.py +++ b/plugins/temp_fan_plugin.py @@ -56,9 +56,9 @@ draw_bar = getattr(drawing, 'draw_bar') draw_2_x_1_horiz_grid = getattr(drawing, 'draw_2_x_1_horiz_grid') #### Implement low-level drawing functions #### -# These functions will be dynamically imported by drawing.py and led_system_monitor.py +# These functions will be dynamically imported by drawing.py and called by their correcsponding app function -metrics_funcs = { +direct_draw_funcs = { "temp": { "fn": draw_spiral_vals, "border": draw_8_x_8_grid @@ -69,8 +69,9 @@ metrics_funcs = { } } -# Implement app functions that call your high-level draw functions -# These functions will be dynamically imported by led_system_monitor.py +# Implement app functions that call your direct_draw functions +# These functions will be dynamically imported by led_system_monitor.py. They call the direct_draw_funcs +# defined above, providing additional capabilities that can be targeted to panel quadrants app_funcs = [ { diff --git a/snapshot_files/left/left-snap.json b/snapshot_files/left/left-snap.json new file mode 100644 index 0000000..94617eb --- /dev/null +++ b/snapshot_files/left/left-snap.json @@ -0,0 +1 @@ +[[0,0,0,1,0,0,0,1,0],[0,0,0,1,0,0,1,0,1],[0,0,0,1,0,0,1,1,1],[0,0,0,1,0,0,1,0,1],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,1,1,1],[0,0,0,0,0,0,0,1,0],[0,0,0,0,0,0,0,1,0],[0,0,0,0,0,0,0,1,0],[0,0,0,0,0,0,0,1,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,1,0,0,0,1],[0,0,0,0,1,0,0,0,1],[0,0,0,0,1,0,1,0,1],[0,0,0,0,1,1,1,1,1],[0,0,0,0,1,1,0,1,1],[0,0,0,0,0,0,0,0,0],[1,1,1,1,0,0,1,0,1],[0,1,0,0,0,0,1,0,1],[0,1,0,1,1,0,1,0,1],[0,1,0,1,0,0,1,0,1],[0,1,0,1,0,0,0,1,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,1,1,1],[0,0,0,1,0,0,0,1,0],[0,0,1,1,1,0,0,1,0],[0,0,0,1,0,0,0,1,0],[0,0,0,0,0,0,0,1,0],[0,0,0,0,0,0,0,0,0],[1,0,0,0,1,0,1,1,1],[1,0,0,0,1,0,1,0,0],[1,0,0,0,1,0,1,1,0],[1,0,0,0,1,0,1,0,0],[1,1,1,0,1,0,1,0,0]] \ No newline at end of file diff --git a/snapshot_files/right/right-snap.json b/snapshot_files/right/right-snap.json new file mode 100644 index 0000000..f7a3ad7 --- /dev/null +++ b/snapshot_files/right/right-snap.json @@ -0,0 +1 @@ +[[1,1,0,1,1,0,0,0,0],[1,1,1,1,1,0,0,0,0],[1,0,1,0,1,0,0,0,0],[1,0,0,0,1,0,0,0,0],[0,0,0,0,0,0,0,0,0],[1,0,1,0,1,1,1,0,0],[1,0,1,0,1,0,0,0,0],[1,1,1,0,1,1,0,0,0],[1,0,1,0,1,0,0,0,0],[1,0,1,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0],[0,1,0,0,1,0,1,0,0],[1,0,1,0,1,0,1,0,0],[1,1,1,0,0,1,0,0,0],[1,0,1,0,0,1,0,0,0],[1,0,1,0,0,1,0,0,0],[0,0,0,0,0,0,0,0,0],[1,1,1,0,1,0,1,0,0],[0,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,1,0,0],[0,1,0,0,1,0,1,0,0],[0,1,0,0,1,0,1,0,0],[0,0,0,0,0,0,0,0,0],[1,0,1,0,1,1,1,0,0],[1,0,1,0,1,0,0,0,0],[1,1,1,0,1,1,0,0,0],[1,0,1,0,1,0,0,0,0],[1,0,1,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0],[1,1,1,0,0,0,0,0,0],[1,0,0,0,0,0,0,0,0],[1,1,0,0,0,0,0,0,0],[1,0,0,0,0,0,0,0,0],[1,1,1,0,0,0,0,0,0]] \ No newline at end of file