From 3e51a849703a3be4f3b14ebb3cff574900b6e960 Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Thu, 22 May 2025 17:04:29 -0700 Subject: [PATCH 1/6] Feature: Allow for user defined protobufs --- synapse-api | 2 +- synapse/cli/rpc.py | 7 +--- synapse/utils/proto.py | 84 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 synapse/utils/proto.py diff --git a/synapse-api b/synapse-api index 344ac86..0af5d52 160000 --- a/synapse-api +++ b/synapse-api @@ -1 +1 @@ -Subproject commit 344ac867c60d1db95b9a6933f5c0819df5d26980 +Subproject commit 0af5d52bcb37f0ffd598cd4d8e17e4263e8d4389 diff --git a/synapse/cli/rpc.py b/synapse/cli/rpc.py index a5223d7..4362ac5 100644 --- a/synapse/cli/rpc.py +++ b/synapse/cli/rpc.py @@ -12,11 +12,11 @@ from google.protobuf.json_format import Parse from rich.console import Console -from rich.pretty import pprint from synapse.cli.query import StreamingQueryClient from synapse.utils.log import log_entry_to_str from synapse.cli.device_info_display import DeviceInfoDisplay +from synapse.utils.proto import load_config def add_commands(subparsers): @@ -196,10 +196,7 @@ def start(args): # Load the configuration proto and build Config object try: - with open(cfg_path, "r") as f: - json_text = f.read() - cfg_proto = Parse(json_text, DeviceConfiguration()) - config_obj = syn.Config.from_proto(cfg_proto) + config_obj = load_config(cfg_path, console) except Exception as e: console.print( f"[bold red]Failed to parse configuration file[/bold red]: {e}" diff --git a/synapse/utils/proto.py b/synapse/utils/proto.py new file mode 100644 index 0000000..017f616 --- /dev/null +++ b/synapse/utils/proto.py @@ -0,0 +1,84 @@ +import os +from pathlib import Path +from google.protobuf import descriptor_pb2, descriptor_pool +from google.protobuf.json_format import Parse +from synapse.api.synapse_pb2 import DeviceConfiguration +from synapse.api.app_pb2 import AppManifest +import synapse as syn + + +def load_client_protos(descriptor_file_paths, console): + if not descriptor_file_paths: + return + + pool = descriptor_pool.Default() + for desc_path in descriptor_file_paths: + if not os.path.exists(desc_path): + console.print(f"[red]Warning:[/red] Descriptor file not found: {desc_path}") + continue + try: + with open(desc_path, "rb") as f: + descriptor_data = f.read() + + file_descriptor_set = descriptor_pb2.FileDescriptorSet() + file_descriptor_set.ParseFromString(descriptor_data) + + for file_desc in file_descriptor_set.file: + try: + pool.Add(file_desc) + console.print( + f"[green]Successfully loaded descriptor:[/green] {file_desc.name}" + ) + except Exception as e: + console.print( + f"[red]Warning:[/red] Could not add {file_desc.name} to pool: {e}" + ) + + except Exception as e: + console.print(f"[red]Error:[/red] Loading descriptor {desc_path}: {e}") + + +def load_config(path_to_config, console): + # We support either a manifest or a device configuration. + # First, try to load a device configuration + try: + json_text = open(path_to_config, "r").read() + cfg_proto = Parse(json_text, DeviceConfiguration()) + return syn.Config.from_proto(cfg_proto) + except Exception: + pass + + # We couldn't load a device configuration, so try to load a manifest + try: + json_text = open(path_to_config, "r").read() + manifest_proto = Parse(json_text, AppManifest()) + + # First, load the descriptors from the proto_files (but look for .desc) + manifest_dir = Path(path_to_config).resolve().parent + + desc_files = [] + for proto_file in manifest_proto.proto_files: + # Convert .proto path to .desc path + desc_file = Path(proto_file).with_suffix(".desc") + desc_path = manifest_dir / desc_file + desc_files.append(str(desc_path)) + + if desc_files: + load_client_protos(desc_files, console) + + # Now load the device configuration from the specified path + device_config_path = manifest_dir / manifest_proto.device_config_path + + if device_config_path.exists(): + device_config_text = device_config_path.read_text() + cfg_proto = Parse(device_config_text, DeviceConfiguration()) + return syn.Config.from_proto(cfg_proto) + else: + raise FileNotFoundError(f"Device config not found: {device_config_path}") + + except Exception: + raise ValueError( + f"Could not parse {path_to_config} as either device configuration or manifest" + ) + + return None From 2dc3668c8a8df8be1c864f830974aebdbb02bc18 Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Thu, 22 May 2025 17:20:25 -0700 Subject: [PATCH 2/6] raw configure should still work --- synapse/cli/rpc.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/synapse/cli/rpc.py b/synapse/cli/rpc.py index 4362ac5..c36072f 100644 --- a/synapse/cli/rpc.py +++ b/synapse/cli/rpc.py @@ -4,7 +4,6 @@ from typing import Optional import synapse as syn -from synapse.api.synapse_pb2 import DeviceConfiguration from synapse.api.query_pb2 import QueryRequest, QueryResponse, StreamQueryRequest from synapse.api.status_pb2 import StatusCode @@ -264,17 +263,19 @@ def configure(args): print("Configuration file must be a JSON file") return False - with open(args.config_file) as config_json: - console = Console() - config_proto = Parse(config_json.read(), DeviceConfiguration()) - console.print("Configuring device with the following configuration:") - config = syn.Config.from_proto(config_proto) - console.print(config.to_proto()) - - config_ret = syn.Device(args.uri, args.verbose).configure_with_status(config) - if not config_ret: - console.print("[bold red]Internal error configuring device") - return + console = Console() + config_obj = load_config(args.config_file, console) + if not config_obj: + console.print("[bold red]Failed to parse configuration file") + return + + console.print("Configuring device with the following configuration:") + console.print(config_obj.to_proto()) + + config_ret = syn.Device(args.uri, args.verbose).configure_with_status(config_obj) + if not config_ret: + console.print("[bold red]Internal error configuring device") + return if config_ret.code != StatusCode.kOk: console.print(f"[bold red]Error configuring\n{config_ret.message}") return From 5e9d1282a68d037667d2fad3469a86525d310b17 Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Thu, 22 May 2025 18:07:22 -0700 Subject: [PATCH 3/6] Fix device info display --- synapse/cli/device_info_display.py | 27 ++++++++---------------- synapse/cli/rpc.py | 1 + synapse/client/nodes/application_node.py | 7 +++--- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/synapse/cli/device_info_display.py b/synapse/cli/device_info_display.py index 93e9cb3..3988293 100644 --- a/synapse/cli/device_info_display.py +++ b/synapse/cli/device_info_display.py @@ -1,37 +1,28 @@ -import time from rich.console import Console -from rich.panel import Panel -from rich.table import Table from rich.tree import Tree from google.protobuf.json_format import MessageToDict from synapse.client.device import Device -def visualize_configuration(info_dict): +def visualize_configuration(info_dict, status): + nodes_status = status.get("signal_chain", {}).get("nodes", {}) config = info_dict.get("configuration", {}) if config: tree = Tree("Configuration") - for node in config.get("nodes", []): + for index, node in enumerate(config.get("nodes", [])): node_type = node.get("type", "").replace("k", "") - node_name = node.get("name", "Unknown") - node_tree = tree.add(f"{node_name}") + node_tree = tree.add(f"{node_type}") node_tree.add(f"ID: {node.get('id', 'Unknown')}") - node_tree.add(f"Type: {node_type}") - if node_type == "Application": app = node.get("application", {}) name = app.get("name", "Unknown") - running = app.get("running", False) - status = "[green]Running[/green]" if running else "[red]Stopped[/red]" + status = nodes_status[index].get("application", None) + if status: + status = status.get("running", False) node_tree.add(f"Name: {name}") - node_tree.add(f"Status: {status}") + node_tree.add(f"Running: {status}") elif node_type == "BroadbandSource": source = node.get("broadband_source", {}) - name = source.get("name", "Unknown") - running = source.get("running", False) - status = "[green]Running[/green]" if running else "[red]Stopped[/red]" - node_tree.add(f"Name: {name}") - node_tree.add(f"Status: {status}") if "signal" in source and "electrode" in source["signal"]: channels = source["signal"]["electrode"].get("channels", []) electrode_ids = [ @@ -113,4 +104,4 @@ def summary(self, device: Device): ) self.console.print(visualize_peripherals(info_dict)) - self.console.print(visualize_configuration(info_dict)) + self.console.print(visualize_configuration(info_dict, status)) diff --git a/synapse/cli/rpc.py b/synapse/cli/rpc.py index c36072f..2d4ac51 100644 --- a/synapse/cli/rpc.py +++ b/synapse/cli/rpc.py @@ -196,6 +196,7 @@ def start(args): # Load the configuration proto and build Config object try: config_obj = load_config(cfg_path, console) + print(config_obj.nodes[0].name) except Exception as e: console.print( f"[bold red]Failed to parse configuration file[/bold red]: {e}" diff --git a/synapse/client/nodes/application_node.py b/synapse/client/nodes/application_node.py index 61c1dc0..aaa28e0 100644 --- a/synapse/client/nodes/application_node.py +++ b/synapse/client/nodes/application_node.py @@ -6,12 +6,13 @@ class ApplicationNode(Node): type = NodeType.kApplication - def __init__(self, name: str): + def __init__(self, name: str, parameters): self.name = name + self.parameters = parameters def _to_proto(self): n = NodeConfig() - p = ApplicationNodeConfig(name=self.name) + p = ApplicationNodeConfig(name=self.name, parameters=self.parameters) n.application.CopyFrom(p) return n @@ -22,4 +23,4 @@ def _from_proto(proto: ApplicationNodeConfig): if not isinstance(proto, ApplicationNodeConfig): raise ValueError("proto is not of type ApplicationNodeConfig") - return ApplicationNode(name=proto.name) + return ApplicationNode(name=proto.name, parameters=proto.parameters) From 2524222d1f2beddab91ed0106e951e99bc528f5b Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Thu, 22 May 2025 18:07:54 -0700 Subject: [PATCH 4/6] Fix device info display --- synapse/cli/rpc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/synapse/cli/rpc.py b/synapse/cli/rpc.py index 2d4ac51..c36072f 100644 --- a/synapse/cli/rpc.py +++ b/synapse/cli/rpc.py @@ -196,7 +196,6 @@ def start(args): # Load the configuration proto and build Config object try: config_obj = load_config(cfg_path, console) - print(config_obj.nodes[0].name) except Exception as e: console.print( f"[bold red]Failed to parse configuration file[/bold red]: {e}" From 7cbd375d521b69a7379e77e6cd10d8ea0e03aa60 Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Thu, 22 May 2025 18:53:11 -0700 Subject: [PATCH 5/6] Print error status too --- synapse-api | 2 +- synapse/cli/device_info_display.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/synapse-api b/synapse-api index 0af5d52..9cd751f 160000 --- a/synapse-api +++ b/synapse-api @@ -1 +1 @@ -Subproject commit 0af5d52bcb37f0ffd598cd4d8e17e4263e8d4389 +Subproject commit 9cd751ff52c9e8a7a1832c93689655cf1e0b0233 diff --git a/synapse/cli/device_info_display.py b/synapse/cli/device_info_display.py index 3988293..5f86a6b 100644 --- a/synapse/cli/device_info_display.py +++ b/synapse/cli/device_info_display.py @@ -16,11 +16,18 @@ def visualize_configuration(info_dict, status): if node_type == "Application": app = node.get("application", {}) name = app.get("name", "Unknown") - status = nodes_status[index].get("application", None) - if status: - status = status.get("running", False) + + application_status = nodes_status[index].get("application", None) + running = application_status.get("running", False) + if not running: + error_logs = application_status.get( + "error_logs", "Could not get error logs" + ) + node_tree.add(f"Name: {name}") - node_tree.add(f"Running: {status}") + node_tree.add(f"Running: {running}") + if not running: + node_tree.add(f"Error Logs:\n{error_logs}") elif node_type == "BroadbandSource": source = node.get("broadband_source", {}) if "signal" in source and "electrode" in source["signal"]: From f239e732a60c20193377f41371ac4cef97e2b474 Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Tue, 27 May 2025 17:14:13 -0700 Subject: [PATCH 6/6] Updated api --- synapse-api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse-api b/synapse-api index 9cd751f..58b73cd 160000 --- a/synapse-api +++ b/synapse-api @@ -1 +1 @@ -Subproject commit 9cd751ff52c9e8a7a1832c93689655cf1e0b0233 +Subproject commit 58b73cd76dcb1584066163d133e08f7a6084d328