diff --git a/0mq/funbody_zmq.dir/concore2.py b/0mq/funbody_zmq.dir/concore2.py index e3ec817..7b0b3b4 100644 --- a/0mq/funbody_zmq.dir/concore2.py +++ b/0mq/funbody_zmq.dir/concore2.py @@ -3,6 +3,7 @@ from ast import literal_eval import sys import re +import zmq # Added for ZeroMQ #if windows, create script to kill this process # because batch files don't provide easy way to know pid of last command @@ -12,15 +13,60 @@ with open("concorekill.bat","w") as fpid: fpid.write("taskkill /F /PID "+str(os.getpid())+"\n") -try: - iport = literal_eval(open("concore.iport").read()) -except: - iport = dict() -try: - oport = literal_eval(open("concore.oport").read()) -except: - oport = dict() +# --- ZeroMQ Integration Start --- +class ZeroMQPort: + def __init__(self, port_type, address, zmq_socket_type): + self.context = zmq.Context() + self.socket = self.context.socket(zmq_socket_type) + self.port_type = port_type # "bind" or "connect" + self.address = address + if self.port_type == "bind": + self.socket.bind(address) + print(f"ZMQ Port bound to {address}") + else: + self.socket.connect(address) + print(f"ZMQ Port connected to {address}") + +# Global ZeroMQ ports registry +zmq_ports = {} + +def init_zmq_port(port_name, port_type, address, socket_type_str): + """ + Initializes and registers a ZeroMQ port. + port_name (str): A unique name for this ZMQ port. + port_type (str): "bind" or "connect". + address (str): The ZMQ address (e.g., "tcp://*:5555", "tcp://localhost:5555"). + socket_type_str (str): String representation of ZMQ socket type (e.g., "REQ", "REP", "PUB", "SUB"). + """ + if port_name in zmq_ports: + print(f"ZMQ Port {port_name} already initialized.") + return # Avoid reinitialization + + try: + # Map socket type string to actual ZMQ constant (e.g., zmq.REQ, zmq.REP) + zmq_socket_type = getattr(zmq, socket_type_str.upper()) + zmq_ports[port_name] = ZeroMQPort(port_type, address, zmq_socket_type) + print(f"Initialized ZMQ port: {port_name} ({socket_type_str}) on {address}") + except AttributeError: + print(f"Error: Invalid ZMQ socket type string '{socket_type_str}'.") + except zmq.error.ZMQError as e: + print(f"Error initializing ZMQ port {port_name} on {address}: {e}") + except Exception as e: + print(f"An unexpected error occurred during ZMQ port initialization for {port_name}: {e}") +# --- ZeroMQ Integration End --- + +def safe_literal_eval(filename, defaultValue): + try: + with open(filename, "r") as file: + return literal_eval(file.read()) + except (FileNotFoundError, SyntaxError, ValueError, Exception) as e: + # Keep print for debugging, but can be made quieter + # print(f"Info: Error reading {filename} or file not found, using default: {e}") + return defaultValue + +iport = safe_literal_eval("concore.iport", {}) +oport = safe_literal_eval("concore.oport", {}) s = '' olds = '' @@ -28,86 +74,182 @@ retrycount = 0 inpath = "./in" #must be rel path for local outpath = "./out" +simtime = 0 #9/21/22 try: - sparams = open(inpath+"1/concore.params").read() - if sparams[0] == '"': #windows keeps "" need to remove - sparams = sparams[1:] - sparams = sparams[0:sparams.find('"')] - if sparams != '{': - print("converting sparams: "+sparams) - sparams = "{'"+re.sub(';',",'",re.sub('=',"':",re.sub(' ','',sparams)))+"}" - print("converted sparams: " + sparams) - try: - params = literal_eval(sparams) - except: - print("bad params: "+sparams) -except: + sparams_path = os.path.join(inpath + "1", "concore.params") + if os.path.exists(sparams_path): + with open(sparams_path, "r") as f: + sparams = f.read() + if sparams: # Ensure sparams is not empty + if sparams[0] == '"' and sparams[-1] == '"': #windows keeps "" need to remove + sparams = sparams[1:-1] + if sparams != '{' and not (sparams.startswith('{') and sparams.endswith('}')): # Check if it needs conversion + print("converting sparams: "+sparams) + sparams = "{'"+re.sub(';',",'",re.sub('=',"':",re.sub(' ','',sparams)))+"}" + print("converted sparams: " + sparams) + try: + params = literal_eval(sparams) + except Exception as e: + print(f"bad params content: {sparams}, error: {e}") + params = dict() + else: + params = dict() + else: + params = dict() +except Exception as e: + # print(f"Info: concore.params not found or error reading, using empty dict: {e}") params = dict() + #9/30/22 -def tryparam(n,i): - try: - return params[n] - except: - return i +def tryparam(n, i): + return params.get(n, i) #9/12/21 def default_maxtime(default): global maxtime - try: - maxtime = literal_eval(open(inpath+"1/concore.maxtime").read()) - except: - maxtime = default + maxtime_path = os.path.join(inpath + "1", "concore.maxtime") + maxtime = safe_literal_eval(maxtime_path, default) + default_maxtime(100) def unchanged(): - global olds,s - if olds==s: + global olds, s + if olds == s: s = '' return True - else: - olds = s - return False + olds = s + return False + +def read(port_identifier, name, initstr_val): + global s, simtime, retrycount + + default_return_val = initstr_val + if isinstance(initstr_val, str): + try: + default_return_val = literal_eval(initstr_val) + except (SyntaxError, ValueError): + pass + + if isinstance(port_identifier, str) and port_identifier in zmq_ports: + zmq_p = zmq_ports[port_identifier] + try: + message = zmq_p.socket.recv_json() + return message + except zmq.error.ZMQError as e: + print(f"ZMQ read error on port {port_identifier} (name: {name}): {e}. Returning default.") + return default_return_val + except Exception as e: + print(f"Unexpected error during ZMQ read on port {port_identifier} (name: {name}): {e}. Returning default.") + return default_return_val + + try: + file_port_num = int(port_identifier) + except ValueError: + print(f"Error: Invalid port identifier '{port_identifier}' for file operation. Must be integer or ZMQ name.") + return default_return_val + + time.sleep(delay) + file_path = os.path.join(inpath+str(file_port_num), name) + ins = "" -def read(port, name, initstr): - global s,simtime,retrycount - time.sleep(delay) try: - infile = open(inpath+str(port)+"/"+name); - ins = infile.read() - except: - ins = initstr - while len(ins)==0: + with open(file_path, "r") as infile: + ins = infile.read() + except FileNotFoundError: + ins = str(initstr_val) + except Exception as e: + print(f"Error reading {file_path}: {e}. Using default value.") + return default_return_val + + attempts = 0 + max_retries = 5 + while len(ins) == 0 and attempts < max_retries: time.sleep(delay) - ins = infile.read() + try: + with open(file_path, "r") as infile: + ins = infile.read() + except Exception as e: + print(f"Retry {attempts + 1}: Error reading {file_path} - {e}") + attempts += 1 retrycount += 1 - s += ins - inval = literal_eval(ins) - simtime = max(simtime,inval[0]) - return inval[1:] - -def write(port, name, val, delta=0): - global outpath,simtime - if isinstance(val,str): - time.sleep(2*delay) - elif isinstance(val,list)==False: - print("mywrite must have list or str") - quit() + + if len(ins) == 0: + print(f"Max retries reached for {file_path}, using default value.") + return default_return_val + + s += ins try: - with open(outpath+str(port)+"/"+name,"w") as outfile: - if isinstance(val,list): - outfile.write(str([simtime+delta]+val)) - simtime += delta - else: + inval = literal_eval(ins) + if isinstance(inval, list) and len(inval) > 0: + current_simtime_from_file = inval[0] + if isinstance(current_simtime_from_file, (int, float)): + simtime = max(simtime, current_simtime_from_file) + return inval[1:] + else: + print(f"Warning: Unexpected data format in {file_path}: {ins}. Returning raw content or default.") + return inval + except Exception as e: + print(f"Error parsing content from {file_path} ('{ins}'): {e}. Returning default.") + return default_return_val + + +def write(port_identifier, name, val, delta=0): + global simtime + + if isinstance(port_identifier, str) and port_identifier in zmq_ports: + zmq_p = zmq_ports[port_identifier] + try: + zmq_p.socket.send_json(val) + except zmq.error.ZMQError as e: + print(f"ZMQ write error on port {port_identifier} (name: {name}): {e}") + except Exception as e: + print(f"Unexpected error during ZMQ write on port {port_identifier} (name: {name}): {e}") + return + + try: + file_port_num = int(port_identifier) + except ValueError: + print(f"Error: Invalid port identifier '{port_identifier}' for file operation. Must be integer or ZMQ name.") + return + + file_path = os.path.join(outpath+str(file_port_num), name) + + if isinstance(val, str): + time.sleep(2 * delay) + elif not isinstance(val, list): + print(f"File write to {file_path} must have list or str value, got {type(val)}") + return + + try: + with open(file_path, "w") as outfile: + if isinstance(val, list): + data_to_write = [simtime + delta] + val + outfile.write(str(data_to_write)) + simtime += delta + else: outfile.write(val) - except: - print("skipping"+outpath+str(port)+"/"+name); + except Exception as e: + print(f"Error writing to {file_path}: {e}") -def initval(simtime_val): +def initval(simtime_val_str): global simtime - val = literal_eval(simtime_val) - simtime = val[0] - return val[1:] + try: + val = literal_eval(simtime_val_str) + if isinstance(val, list) and len(val) > 0: + first_element = val[0] + if isinstance(first_element, (int, float)): + simtime = first_element + return val[1:] + else: + print(f"Error: First element in initval string '{simtime_val_str}' is not a number. Using data part as is or empty.") + return val[1:] if len(val) > 1 else [] + else: + print(f"Error: initval string '{simtime_val_str}' is not a list or is empty. Returning empty list.") + return [] + except Exception as e: + print(f"Error parsing simtime_val_str '{simtime_val_str}': {e}. Returning empty list.") + return [] \ No newline at end of file diff --git a/0mq/funbody_zmq.py b/0mq/funbody_zmq.py index b3ecd63..576f692 100644 --- a/0mq/funbody_zmq.py +++ b/0mq/funbody_zmq.py @@ -8,7 +8,7 @@ context = zmq.Context() socket = context.socket(zmq.REP) -socket.bind("tcp://*:2346") +socket.bind("tcp://*:2356") concore.delay = 0.07 concore2.delay = 0.07 diff --git a/0mq/funbody_zmq2.dir/concore2.py b/0mq/funbody_zmq2.dir/concore2.py new file mode 100644 index 0000000..65bd955 --- /dev/null +++ b/0mq/funbody_zmq2.dir/concore2.py @@ -0,0 +1,254 @@ +import time +import os +from ast import literal_eval +import sys +import re +import zmq # Added for ZeroMQ + +#if windows, create script to kill this process +# because batch files don't provide easy way to know pid of last command +# ignored for posix!=windows, because "concorepid" is handled by script +# ignored for docker (linux!=windows), because handled by docker stop +if hasattr(sys, 'getwindowsversion'): + with open("concorekill.bat","w") as fpid: + fpid.write("taskkill /F /PID "+str(os.getpid())+"\n") + +# --- ZeroMQ Integration Start --- +class ZeroMQPort: + def __init__(self, port_type, address, zmq_socket_type): + self.context = zmq.Context() + self.socket = self.context.socket(zmq_socket_type) + self.port_type = port_type # "bind" or "connect" + self.address = address + if self.port_type == "bind": + self.socket.bind(address) + print(f"ZMQ Port bound to {address}") + else: + self.socket.connect(address) + print(f"ZMQ Port connected to {address}") + +# Global ZeroMQ ports registry +zmq_ports = {} + +def init_zmq_port(port_name, port_type, address, socket_type_str): + """ + Initializes and registers a ZeroMQ port. + port_name (str): A unique name for this ZMQ port. + port_type (str): "bind" or "connect". + address (str): The ZMQ address (e.g., "tcp://*:5555", "tcp://localhost:5555"). + socket_type_str (str): String representation of ZMQ socket type (e.g., "REQ", "REP", "PUB", "SUB"). + """ + if port_name in zmq_ports: + print(f"ZMQ Port {port_name} already initialized.") + return # Avoid reinitialization + + try: + # Map socket type string to actual ZMQ constant (e.g., zmq.REQ, zmq.REP) + zmq_socket_type = getattr(zmq, socket_type_str.upper()) + zmq_ports[port_name] = ZeroMQPort(port_type, address, zmq_socket_type) + print(f"Initialized ZMQ port: {port_name} ({socket_type_str}) on {address}") + except AttributeError: + print(f"Error: Invalid ZMQ socket type string '{socket_type_str}'.") + except zmq.error.ZMQError as e: + print(f"Error initializing ZMQ port {port_name} on {address}: {e}") + except Exception as e: + print(f"An unexpected error occurred during ZMQ port initialization for {port_name}: {e}") + + +def safe_literal_eval(filename, defaultValue): + try: + with open(filename, "r") as file: + return literal_eval(file.read()) + except (FileNotFoundError, SyntaxError, ValueError, Exception) as e: + # Keep print for debugging, but can be made quieter + # print(f"Info: Error reading {filename} or file not found, using default: {e}") + return defaultValue + +iport = safe_literal_eval("concore.iport", {}) +oport = safe_literal_eval("concore.oport", {}) + +s = '' +olds = '' +delay = 1 +retrycount = 0 +inpath = "./in" #must be rel path for local +outpath = "./out" +simtime = 0 + +#9/21/22 +try: + sparams_path = os.path.join(inpath + "1", "concore.params") + if os.path.exists(sparams_path): + with open(sparams_path, "r") as f: + sparams = f.read() + if sparams: # Ensure sparams is not empty + if sparams[0] == '"' and sparams[-1] == '"': #windows keeps "" need to remove + sparams = sparams[1:-1] + if sparams != '{' and not (sparams.startswith('{') and sparams.endswith('}')): # Check if it needs conversion + print("converting sparams: "+sparams) + sparams = "{'"+re.sub(';',",'",re.sub('=',"':",re.sub(' ','',sparams)))+"}" + print("converted sparams: " + sparams) + try: + params = literal_eval(sparams) + except Exception as e: + print(f"bad params content: {sparams}, error: {e}") + params = dict() + else: + params = dict() + else: + params = dict() +except Exception as e: + # print(f"Info: concore.params not found or error reading, using empty dict: {e}") + params = dict() + +#9/30/22 +def tryparam(n, i): + return params.get(n, i) + + +#9/12/21 +def default_maxtime(default): + global maxtime + maxtime_path = os.path.join(inpath + "1", "concore.maxtime") + maxtime = safe_literal_eval(maxtime_path, default) + +default_maxtime(100) + +def unchanged(): + global olds, s + if olds == s: + s = '' + return True + olds = s + return False + +def read(port_identifier, name, initstr_val): + global s, simtime, retrycount + + default_return_val = initstr_val + if isinstance(initstr_val, str): + try: + default_return_val = literal_eval(initstr_val) + except (SyntaxError, ValueError): + pass + + if isinstance(port_identifier, str) and port_identifier in zmq_ports: + zmq_p = zmq_ports[port_identifier] + try: + message = zmq_p.socket.recv_json() + return message + except zmq.error.ZMQError as e: + print(f"ZMQ read error on port {port_identifier} (name: {name}): {e}. Returning default.") + return default_return_val + except Exception as e: + print(f"Unexpected error during ZMQ read on port {port_identifier} (name: {name}): {e}. Returning default.") + return default_return_val + + try: + file_port_num = int(port_identifier) + except ValueError: + print(f"Error: Invalid port identifier '{port_identifier}' for file operation. Must be integer or ZMQ name.") + return default_return_val + + time.sleep(delay) + file_path = os.path.join(inpath+str(file_port_num), name) + ins = "" + + try: + with open(file_path, "r") as infile: + ins = infile.read() + except FileNotFoundError: + ins = str(initstr_val) + except Exception as e: + print(f"Error reading {file_path}: {e}. Using default value.") + return default_return_val + + attempts = 0 + max_retries = 5 + while len(ins) == 0 and attempts < max_retries: + time.sleep(delay) + try: + with open(file_path, "r") as infile: + ins = infile.read() + except Exception as e: + print(f"Retry {attempts + 1}: Error reading {file_path} - {e}") + attempts += 1 + retrycount += 1 + + if len(ins) == 0: + print(f"Max retries reached for {file_path}, using default value.") + return default_return_val + + s += ins + try: + inval = literal_eval(ins) + if isinstance(inval, list) and len(inval) > 0: + current_simtime_from_file = inval[0] + if isinstance(current_simtime_from_file, (int, float)): + simtime = max(simtime, current_simtime_from_file) + return inval[1:] + else: + print(f"Warning: Unexpected data format in {file_path}: {ins}. Returning raw content or default.") + return inval + except Exception as e: + print(f"Error parsing content from {file_path} ('{ins}'): {e}. Returning default.") + return default_return_val + + +def write(port_identifier, name, val, delta=0): + global simtime + + if isinstance(port_identifier, str) and port_identifier in zmq_ports: + zmq_p = zmq_ports[port_identifier] + try: + zmq_p.socket.send_json(val) + except zmq.error.ZMQError as e: + print(f"ZMQ write error on port {port_identifier} (name: {name}): {e}") + except Exception as e: + print(f"Unexpected error during ZMQ write on port {port_identifier} (name: {name}): {e}") + return + + try: + file_port_num = int(port_identifier) + except ValueError: + print(f"Error: Invalid port identifier '{port_identifier}' for file operation. Must be integer or ZMQ name.") + return + + file_path = os.path.join(outpath+str(file_port_num), name) + + if isinstance(val, str): + time.sleep(2 * delay) + elif not isinstance(val, list): + print(f"File write to {file_path} must have list or str value, got {type(val)}") + return + + try: + with open(file_path, "w") as outfile: + if isinstance(val, list): + data_to_write = [simtime + delta] + val + outfile.write(str(data_to_write)) + simtime += delta + else: + outfile.write(val) + except Exception as e: + print(f"Error writing to {file_path}: {e}") + +def initval(simtime_val_str): + global simtime + try: + val = literal_eval(simtime_val_str) + if isinstance(val, list) and len(val) > 0: + first_element = val[0] + if isinstance(first_element, (int, float)): + simtime = first_element + return val[1:] + else: + print(f"Error: First element in initval string '{simtime_val_str}' is not a number. Using data part as is or empty.") + return val[1:] if len(val) > 1 else [] + else: + print(f"Error: initval string '{simtime_val_str}' is not a list or is empty. Returning empty list.") + return [] + + except Exception as e: + print(f"Error parsing simtime_val_str '{simtime_val_str}': {e}. Returning empty list.") + return [] \ No newline at end of file diff --git a/0mq/funbody_zmq2.py b/0mq/funbody_zmq2.py new file mode 100644 index 0000000..fce7b13 --- /dev/null +++ b/0mq/funbody_zmq2.py @@ -0,0 +1,68 @@ +# funbody2_zmq.py +import time +import concore +import concore2 + +print("funbody using ZMQ via concore") + +# ZMQ settings +ZMQ_PORT_NAME = "FUNBODY_REP_PORT" +ZMQ_BIND_ADDRESS = "tcp://*:2355" + +# Initialize ZMQ REP port using concore +concore.init_zmq_port( + port_name=ZMQ_PORT_NAME, + port_type="bind", + address=ZMQ_BIND_ADDRESS, + socket_type_str="REP" +) + +# Standard concore initializations +concore.delay = 0.07 +concore2.delay = 0.07 +concore2.inpath = concore.inpath +concore2.outpath = concore.outpath +concore2.simtime = 0 +concore.default_maxtime(100) +init_simtime_u_str = "[0.0, 0.0, 0.0]" +init_simtime_ym_str = "[0.0, 0.0, 0.0]" + +u_data_values = concore.initval(init_simtime_u_str) +ym_data_values = concore2.initval(init_simtime_ym_str) + +print(f"Initial u_data_values: {u_data_values}, ym_data_values: {ym_data_values}") +print(f"Max time: {concore.maxtime}") + +while concore2.simtime < concore.maxtime: + received_u_data = concore.read(ZMQ_PORT_NAME, "u_signal", init_simtime_u_str) + + if not (isinstance(received_u_data, list) and len(received_u_data) > 0): + print(f"Error or invalid data received via ZMQ: {received_u_data}. Skipping iteration.") + time.sleep(concore.delay) + continue + + received_time = received_u_data[0] + if isinstance(received_time, (int, float)): + concore.simtime = received_time + u_data_values = received_u_data[1:] + else: + print(f"Warning: Received ZMQ data's first element is not time: {received_u_data}. Using data part as is.") + u_data_values = received_u_data[1:] if len(received_u_data) > 1 else [] + + # Assuming concore.oport['U2'] is a file port (e.g., to pmpymax.py) + if 'U2' in concore.oport: + concore.write(concore.oport['U2'], "u", u_data_values) + + old_concore2_simtime = concore2.simtime + while concore2.unchanged() or concore2.simtime <= old_concore2_simtime: + # Assuming concore.iport['Y2'] is a file port (e.g., from pmpymax.py) + ym_data_values = concore2.read(concore.iport['Y2'], "ym", init_simtime_ym_str) + # time.sleep(concore2.delay) # Optional delay + + ym_full_to_send = [concore2.simtime] + ym_data_values + + concore.write(ZMQ_PORT_NAME, "ym_signal", ym_full_to_send) + + print(f"funbody u={u_data_values} ym={ym_data_values} time={concore2.simtime}") + +print("funbody retry=" + str(concore.retrycount)) \ No newline at end of file diff --git a/0mq/funcall_zmq.dir/concore2.py b/0mq/funcall_zmq.dir/concore2.py index e3ec817..7b0b3b4 100644 --- a/0mq/funcall_zmq.dir/concore2.py +++ b/0mq/funcall_zmq.dir/concore2.py @@ -3,6 +3,7 @@ from ast import literal_eval import sys import re +import zmq # Added for ZeroMQ #if windows, create script to kill this process # because batch files don't provide easy way to know pid of last command @@ -12,15 +13,60 @@ with open("concorekill.bat","w") as fpid: fpid.write("taskkill /F /PID "+str(os.getpid())+"\n") -try: - iport = literal_eval(open("concore.iport").read()) -except: - iport = dict() -try: - oport = literal_eval(open("concore.oport").read()) -except: - oport = dict() +# --- ZeroMQ Integration Start --- +class ZeroMQPort: + def __init__(self, port_type, address, zmq_socket_type): + self.context = zmq.Context() + self.socket = self.context.socket(zmq_socket_type) + self.port_type = port_type # "bind" or "connect" + self.address = address + if self.port_type == "bind": + self.socket.bind(address) + print(f"ZMQ Port bound to {address}") + else: + self.socket.connect(address) + print(f"ZMQ Port connected to {address}") + +# Global ZeroMQ ports registry +zmq_ports = {} + +def init_zmq_port(port_name, port_type, address, socket_type_str): + """ + Initializes and registers a ZeroMQ port. + port_name (str): A unique name for this ZMQ port. + port_type (str): "bind" or "connect". + address (str): The ZMQ address (e.g., "tcp://*:5555", "tcp://localhost:5555"). + socket_type_str (str): String representation of ZMQ socket type (e.g., "REQ", "REP", "PUB", "SUB"). + """ + if port_name in zmq_ports: + print(f"ZMQ Port {port_name} already initialized.") + return # Avoid reinitialization + + try: + # Map socket type string to actual ZMQ constant (e.g., zmq.REQ, zmq.REP) + zmq_socket_type = getattr(zmq, socket_type_str.upper()) + zmq_ports[port_name] = ZeroMQPort(port_type, address, zmq_socket_type) + print(f"Initialized ZMQ port: {port_name} ({socket_type_str}) on {address}") + except AttributeError: + print(f"Error: Invalid ZMQ socket type string '{socket_type_str}'.") + except zmq.error.ZMQError as e: + print(f"Error initializing ZMQ port {port_name} on {address}: {e}") + except Exception as e: + print(f"An unexpected error occurred during ZMQ port initialization for {port_name}: {e}") +# --- ZeroMQ Integration End --- + +def safe_literal_eval(filename, defaultValue): + try: + with open(filename, "r") as file: + return literal_eval(file.read()) + except (FileNotFoundError, SyntaxError, ValueError, Exception) as e: + # Keep print for debugging, but can be made quieter + # print(f"Info: Error reading {filename} or file not found, using default: {e}") + return defaultValue + +iport = safe_literal_eval("concore.iport", {}) +oport = safe_literal_eval("concore.oport", {}) s = '' olds = '' @@ -28,86 +74,182 @@ retrycount = 0 inpath = "./in" #must be rel path for local outpath = "./out" +simtime = 0 #9/21/22 try: - sparams = open(inpath+"1/concore.params").read() - if sparams[0] == '"': #windows keeps "" need to remove - sparams = sparams[1:] - sparams = sparams[0:sparams.find('"')] - if sparams != '{': - print("converting sparams: "+sparams) - sparams = "{'"+re.sub(';',",'",re.sub('=',"':",re.sub(' ','',sparams)))+"}" - print("converted sparams: " + sparams) - try: - params = literal_eval(sparams) - except: - print("bad params: "+sparams) -except: + sparams_path = os.path.join(inpath + "1", "concore.params") + if os.path.exists(sparams_path): + with open(sparams_path, "r") as f: + sparams = f.read() + if sparams: # Ensure sparams is not empty + if sparams[0] == '"' and sparams[-1] == '"': #windows keeps "" need to remove + sparams = sparams[1:-1] + if sparams != '{' and not (sparams.startswith('{') and sparams.endswith('}')): # Check if it needs conversion + print("converting sparams: "+sparams) + sparams = "{'"+re.sub(';',",'",re.sub('=',"':",re.sub(' ','',sparams)))+"}" + print("converted sparams: " + sparams) + try: + params = literal_eval(sparams) + except Exception as e: + print(f"bad params content: {sparams}, error: {e}") + params = dict() + else: + params = dict() + else: + params = dict() +except Exception as e: + # print(f"Info: concore.params not found or error reading, using empty dict: {e}") params = dict() + #9/30/22 -def tryparam(n,i): - try: - return params[n] - except: - return i +def tryparam(n, i): + return params.get(n, i) #9/12/21 def default_maxtime(default): global maxtime - try: - maxtime = literal_eval(open(inpath+"1/concore.maxtime").read()) - except: - maxtime = default + maxtime_path = os.path.join(inpath + "1", "concore.maxtime") + maxtime = safe_literal_eval(maxtime_path, default) + default_maxtime(100) def unchanged(): - global olds,s - if olds==s: + global olds, s + if olds == s: s = '' return True - else: - olds = s - return False + olds = s + return False + +def read(port_identifier, name, initstr_val): + global s, simtime, retrycount + + default_return_val = initstr_val + if isinstance(initstr_val, str): + try: + default_return_val = literal_eval(initstr_val) + except (SyntaxError, ValueError): + pass + + if isinstance(port_identifier, str) and port_identifier in zmq_ports: + zmq_p = zmq_ports[port_identifier] + try: + message = zmq_p.socket.recv_json() + return message + except zmq.error.ZMQError as e: + print(f"ZMQ read error on port {port_identifier} (name: {name}): {e}. Returning default.") + return default_return_val + except Exception as e: + print(f"Unexpected error during ZMQ read on port {port_identifier} (name: {name}): {e}. Returning default.") + return default_return_val + + try: + file_port_num = int(port_identifier) + except ValueError: + print(f"Error: Invalid port identifier '{port_identifier}' for file operation. Must be integer or ZMQ name.") + return default_return_val + + time.sleep(delay) + file_path = os.path.join(inpath+str(file_port_num), name) + ins = "" -def read(port, name, initstr): - global s,simtime,retrycount - time.sleep(delay) try: - infile = open(inpath+str(port)+"/"+name); - ins = infile.read() - except: - ins = initstr - while len(ins)==0: + with open(file_path, "r") as infile: + ins = infile.read() + except FileNotFoundError: + ins = str(initstr_val) + except Exception as e: + print(f"Error reading {file_path}: {e}. Using default value.") + return default_return_val + + attempts = 0 + max_retries = 5 + while len(ins) == 0 and attempts < max_retries: time.sleep(delay) - ins = infile.read() + try: + with open(file_path, "r") as infile: + ins = infile.read() + except Exception as e: + print(f"Retry {attempts + 1}: Error reading {file_path} - {e}") + attempts += 1 retrycount += 1 - s += ins - inval = literal_eval(ins) - simtime = max(simtime,inval[0]) - return inval[1:] - -def write(port, name, val, delta=0): - global outpath,simtime - if isinstance(val,str): - time.sleep(2*delay) - elif isinstance(val,list)==False: - print("mywrite must have list or str") - quit() + + if len(ins) == 0: + print(f"Max retries reached for {file_path}, using default value.") + return default_return_val + + s += ins try: - with open(outpath+str(port)+"/"+name,"w") as outfile: - if isinstance(val,list): - outfile.write(str([simtime+delta]+val)) - simtime += delta - else: + inval = literal_eval(ins) + if isinstance(inval, list) and len(inval) > 0: + current_simtime_from_file = inval[0] + if isinstance(current_simtime_from_file, (int, float)): + simtime = max(simtime, current_simtime_from_file) + return inval[1:] + else: + print(f"Warning: Unexpected data format in {file_path}: {ins}. Returning raw content or default.") + return inval + except Exception as e: + print(f"Error parsing content from {file_path} ('{ins}'): {e}. Returning default.") + return default_return_val + + +def write(port_identifier, name, val, delta=0): + global simtime + + if isinstance(port_identifier, str) and port_identifier in zmq_ports: + zmq_p = zmq_ports[port_identifier] + try: + zmq_p.socket.send_json(val) + except zmq.error.ZMQError as e: + print(f"ZMQ write error on port {port_identifier} (name: {name}): {e}") + except Exception as e: + print(f"Unexpected error during ZMQ write on port {port_identifier} (name: {name}): {e}") + return + + try: + file_port_num = int(port_identifier) + except ValueError: + print(f"Error: Invalid port identifier '{port_identifier}' for file operation. Must be integer or ZMQ name.") + return + + file_path = os.path.join(outpath+str(file_port_num), name) + + if isinstance(val, str): + time.sleep(2 * delay) + elif not isinstance(val, list): + print(f"File write to {file_path} must have list or str value, got {type(val)}") + return + + try: + with open(file_path, "w") as outfile: + if isinstance(val, list): + data_to_write = [simtime + delta] + val + outfile.write(str(data_to_write)) + simtime += delta + else: outfile.write(val) - except: - print("skipping"+outpath+str(port)+"/"+name); + except Exception as e: + print(f"Error writing to {file_path}: {e}") -def initval(simtime_val): +def initval(simtime_val_str): global simtime - val = literal_eval(simtime_val) - simtime = val[0] - return val[1:] + try: + val = literal_eval(simtime_val_str) + if isinstance(val, list) and len(val) > 0: + first_element = val[0] + if isinstance(first_element, (int, float)): + simtime = first_element + return val[1:] + else: + print(f"Error: First element in initval string '{simtime_val_str}' is not a number. Using data part as is or empty.") + return val[1:] if len(val) > 1 else [] + else: + print(f"Error: initval string '{simtime_val_str}' is not a list or is empty. Returning empty list.") + return [] + except Exception as e: + print(f"Error parsing simtime_val_str '{simtime_val_str}': {e}. Returning empty list.") + return [] \ No newline at end of file diff --git a/0mq/funcall_zmq.py b/0mq/funcall_zmq.py index dfbb0de..f0c2f33 100644 --- a/0mq/funcall_zmq.py +++ b/0mq/funcall_zmq.py @@ -8,7 +8,7 @@ context = zmq.Context() socket = context.socket(zmq.REQ) -socket.connect("tcp://localhost:2346") +socket.connect("tcp://localhost:2356") concore.delay = 0.07 concore2.delay = 0.07 diff --git a/0mq/funcall_zmq2.dir/concore2.py b/0mq/funcall_zmq2.dir/concore2.py new file mode 100644 index 0000000..7b0b3b4 --- /dev/null +++ b/0mq/funcall_zmq2.dir/concore2.py @@ -0,0 +1,255 @@ +import time +import os +from ast import literal_eval +import sys +import re +import zmq # Added for ZeroMQ + +#if windows, create script to kill this process +# because batch files don't provide easy way to know pid of last command +# ignored for posix!=windows, because "concorepid" is handled by script +# ignored for docker (linux!=windows), because handled by docker stop +if hasattr(sys, 'getwindowsversion'): + with open("concorekill.bat","w") as fpid: + fpid.write("taskkill /F /PID "+str(os.getpid())+"\n") + +# --- ZeroMQ Integration Start --- +class ZeroMQPort: + def __init__(self, port_type, address, zmq_socket_type): + self.context = zmq.Context() + self.socket = self.context.socket(zmq_socket_type) + self.port_type = port_type # "bind" or "connect" + self.address = address + if self.port_type == "bind": + self.socket.bind(address) + print(f"ZMQ Port bound to {address}") + else: + self.socket.connect(address) + print(f"ZMQ Port connected to {address}") + +# Global ZeroMQ ports registry +zmq_ports = {} + +def init_zmq_port(port_name, port_type, address, socket_type_str): + """ + Initializes and registers a ZeroMQ port. + port_name (str): A unique name for this ZMQ port. + port_type (str): "bind" or "connect". + address (str): The ZMQ address (e.g., "tcp://*:5555", "tcp://localhost:5555"). + socket_type_str (str): String representation of ZMQ socket type (e.g., "REQ", "REP", "PUB", "SUB"). + """ + if port_name in zmq_ports: + print(f"ZMQ Port {port_name} already initialized.") + return # Avoid reinitialization + + try: + # Map socket type string to actual ZMQ constant (e.g., zmq.REQ, zmq.REP) + zmq_socket_type = getattr(zmq, socket_type_str.upper()) + zmq_ports[port_name] = ZeroMQPort(port_type, address, zmq_socket_type) + print(f"Initialized ZMQ port: {port_name} ({socket_type_str}) on {address}") + except AttributeError: + print(f"Error: Invalid ZMQ socket type string '{socket_type_str}'.") + except zmq.error.ZMQError as e: + print(f"Error initializing ZMQ port {port_name} on {address}: {e}") + except Exception as e: + print(f"An unexpected error occurred during ZMQ port initialization for {port_name}: {e}") + +# --- ZeroMQ Integration End --- + +def safe_literal_eval(filename, defaultValue): + try: + with open(filename, "r") as file: + return literal_eval(file.read()) + except (FileNotFoundError, SyntaxError, ValueError, Exception) as e: + # Keep print for debugging, but can be made quieter + # print(f"Info: Error reading {filename} or file not found, using default: {e}") + return defaultValue + +iport = safe_literal_eval("concore.iport", {}) +oport = safe_literal_eval("concore.oport", {}) + +s = '' +olds = '' +delay = 1 +retrycount = 0 +inpath = "./in" #must be rel path for local +outpath = "./out" +simtime = 0 + +#9/21/22 +try: + sparams_path = os.path.join(inpath + "1", "concore.params") + if os.path.exists(sparams_path): + with open(sparams_path, "r") as f: + sparams = f.read() + if sparams: # Ensure sparams is not empty + if sparams[0] == '"' and sparams[-1] == '"': #windows keeps "" need to remove + sparams = sparams[1:-1] + if sparams != '{' and not (sparams.startswith('{') and sparams.endswith('}')): # Check if it needs conversion + print("converting sparams: "+sparams) + sparams = "{'"+re.sub(';',",'",re.sub('=',"':",re.sub(' ','',sparams)))+"}" + print("converted sparams: " + sparams) + try: + params = literal_eval(sparams) + except Exception as e: + print(f"bad params content: {sparams}, error: {e}") + params = dict() + else: + params = dict() + else: + params = dict() +except Exception as e: + # print(f"Info: concore.params not found or error reading, using empty dict: {e}") + params = dict() + +#9/30/22 +def tryparam(n, i): + return params.get(n, i) + + +#9/12/21 +def default_maxtime(default): + global maxtime + maxtime_path = os.path.join(inpath + "1", "concore.maxtime") + maxtime = safe_literal_eval(maxtime_path, default) + +default_maxtime(100) + +def unchanged(): + global olds, s + if olds == s: + s = '' + return True + olds = s + return False + +def read(port_identifier, name, initstr_val): + global s, simtime, retrycount + + default_return_val = initstr_val + if isinstance(initstr_val, str): + try: + default_return_val = literal_eval(initstr_val) + except (SyntaxError, ValueError): + pass + + if isinstance(port_identifier, str) and port_identifier in zmq_ports: + zmq_p = zmq_ports[port_identifier] + try: + message = zmq_p.socket.recv_json() + return message + except zmq.error.ZMQError as e: + print(f"ZMQ read error on port {port_identifier} (name: {name}): {e}. Returning default.") + return default_return_val + except Exception as e: + print(f"Unexpected error during ZMQ read on port {port_identifier} (name: {name}): {e}. Returning default.") + return default_return_val + + try: + file_port_num = int(port_identifier) + except ValueError: + print(f"Error: Invalid port identifier '{port_identifier}' for file operation. Must be integer or ZMQ name.") + return default_return_val + + time.sleep(delay) + file_path = os.path.join(inpath+str(file_port_num), name) + ins = "" + + try: + with open(file_path, "r") as infile: + ins = infile.read() + except FileNotFoundError: + ins = str(initstr_val) + except Exception as e: + print(f"Error reading {file_path}: {e}. Using default value.") + return default_return_val + + attempts = 0 + max_retries = 5 + while len(ins) == 0 and attempts < max_retries: + time.sleep(delay) + try: + with open(file_path, "r") as infile: + ins = infile.read() + except Exception as e: + print(f"Retry {attempts + 1}: Error reading {file_path} - {e}") + attempts += 1 + retrycount += 1 + + if len(ins) == 0: + print(f"Max retries reached for {file_path}, using default value.") + return default_return_val + + s += ins + try: + inval = literal_eval(ins) + if isinstance(inval, list) and len(inval) > 0: + current_simtime_from_file = inval[0] + if isinstance(current_simtime_from_file, (int, float)): + simtime = max(simtime, current_simtime_from_file) + return inval[1:] + else: + print(f"Warning: Unexpected data format in {file_path}: {ins}. Returning raw content or default.") + return inval + except Exception as e: + print(f"Error parsing content from {file_path} ('{ins}'): {e}. Returning default.") + return default_return_val + + +def write(port_identifier, name, val, delta=0): + global simtime + + if isinstance(port_identifier, str) and port_identifier in zmq_ports: + zmq_p = zmq_ports[port_identifier] + try: + zmq_p.socket.send_json(val) + except zmq.error.ZMQError as e: + print(f"ZMQ write error on port {port_identifier} (name: {name}): {e}") + except Exception as e: + print(f"Unexpected error during ZMQ write on port {port_identifier} (name: {name}): {e}") + return + + try: + file_port_num = int(port_identifier) + except ValueError: + print(f"Error: Invalid port identifier '{port_identifier}' for file operation. Must be integer or ZMQ name.") + return + + file_path = os.path.join(outpath+str(file_port_num), name) + + if isinstance(val, str): + time.sleep(2 * delay) + elif not isinstance(val, list): + print(f"File write to {file_path} must have list or str value, got {type(val)}") + return + + try: + with open(file_path, "w") as outfile: + if isinstance(val, list): + data_to_write = [simtime + delta] + val + outfile.write(str(data_to_write)) + simtime += delta + else: + outfile.write(val) + except Exception as e: + print(f"Error writing to {file_path}: {e}") + +def initval(simtime_val_str): + global simtime + try: + val = literal_eval(simtime_val_str) + if isinstance(val, list) and len(val) > 0: + first_element = val[0] + if isinstance(first_element, (int, float)): + simtime = first_element + return val[1:] + else: + print(f"Error: First element in initval string '{simtime_val_str}' is not a number. Using data part as is or empty.") + return val[1:] if len(val) > 1 else [] + else: + print(f"Error: initval string '{simtime_val_str}' is not a list or is empty. Returning empty list.") + return [] + + except Exception as e: + print(f"Error parsing simtime_val_str '{simtime_val_str}': {e}. Returning empty list.") + return [] \ No newline at end of file diff --git a/0mq/funcall_zmq2.py b/0mq/funcall_zmq2.py new file mode 100644 index 0000000..baff34b --- /dev/null +++ b/0mq/funcall_zmq2.py @@ -0,0 +1,65 @@ +# funcall2_zmq.py +import time +import concore +import concore2 + +print("funcall using ZMQ via concore") + +# ZMQ settings +ZMQ_PORT_NAME = "FUNCALL_REQ_PORT" +ZMQ_SERVER_ADDRESS = "tcp://localhost:2355" + +# Initialize ZMQ REQ port using concore +concore.init_zmq_port( + port_name=ZMQ_PORT_NAME, + port_type="connect", + address=ZMQ_SERVER_ADDRESS, + socket_type_str="REQ" +) + +# Standard concore initializations +concore.delay = 0.07 +concore2.delay = 0.07 +concore2.inpath = concore.inpath +concore2.outpath = concore.outpath +concore2.simtime = 0 +concore.default_maxtime(100) +init_simtime_u_str = "[0.0, 0.0, 0.0]" +init_simtime_ym_str = "[0.0, 0.0, 0.0]" + +u = concore.initval(init_simtime_u_str) +ym = concore2.initval(init_simtime_ym_str) + +print(f"Initial u: {u}, ym: {ym}, concore.simtime: {concore.simtime}, concore2.simtime: {concore2.simtime}") +print(f"Max time: {concore.maxtime}") + +while concore2.simtime < concore.maxtime: + while concore.unchanged(): + # Assuming concore.iport['U'] is a file port (e.g., from cpymax.py) + u = concore.read(concore.iport['U'], "u", init_simtime_u_str) + # time.sleep(concore.delay) # Optional: if file reads are too fast in a loop + + data_to_send_u = [concore.simtime] + u + + concore.write(ZMQ_PORT_NAME, "u_signal", data_to_send_u) + + received_ym_data = concore.read(ZMQ_PORT_NAME, "ym_signal", init_simtime_ym_str) + + if isinstance(received_ym_data, list) and len(received_ym_data) > 0: + response_time = received_ym_data[0] + if isinstance(response_time, (int, float)): + concore2.simtime = response_time + ym = received_ym_data[1:] + else: + print(f"Warning: Received ZMQ data's first element is not time: {received_ym_data}. Using as is.") + ym = received_ym_data + else: + print(f"Warning: Received unexpected ZMQ data format: {received_ym_data}. Using default ym.") + ym = concore2.initval(init_simtime_ym_str) + + # Assuming concore.oport['Y'] is a file port (e.g., to cpymax.py) + concore2.write(concore.oport['Y'], "ym", ym) + + print(f"funcall ZMQ u={u} ym={ym} time={concore2.simtime}") + +print("funcall retry=" + str(concore.retrycount)) \ No newline at end of file diff --git a/0mq/test0mq4.graphml b/0mq/test0mq4.graphml new file mode 100644 index 0000000..4206ece --- /dev/null +++ b/0mq/test0mq4.graphml @@ -0,0 +1,483 @@ + + + + + + + + + + + + PZ:pmpymax.py + + + + + + + + + + + CZ:cpymax.py + + + + + + + + + + + F1:funcall_zmq2.py + + + + + + + + + + + F2:funbody_zmq2.py + + + + + + + + + + U + + + + + + + + + + + + Y + + + + + + + + + + + + U2 + + + + + + + + + + + + Y2 + + + + + + + + 1664644923582 + + DEL_NODE + WyJiZDkyZjlkOC03ZDYzLTQyYjItOTY4Yi0zOTA5ZWY0YzcyMzciXQ== + + + ADD_NODE + WyJQWjpwbXB5bWF4LnB5Iix7IndpZHRoIjoxMzYsImhlaWdodCI6NTAsInNoYXBlIjoicmVjdGFuZ2xlIiwib3BhY2l0eSI6MSwiYmFja2dyb3VuZENvbG9yIjoiI2ZmY2MwMCIsImJvcmRlckNvbG9yIjoiIzAwMCIsImJvcmRlcldpZHRoIjoxfSwib3JkaW4iLHsieCI6NTg3LjE1NzgxMTg0NjY1NTYsInkiOjEwMC40NDIzMjkyNjUzNzgzNn0se30sImJkOTJmOWQ4LTdkNjMtNDJiMi05NjhiLTM5MDllZjRjNzIzNyJd + + a53a7f7273a40c7970938b6de1829249 + + + 1664644939781 + + SET_POS + WyJiZDkyZjlkOC03ZDYzLTQyYjItOTY4Yi0zOTA5ZWY0YzcyMzciLHsieCI6MTAwLCJ5IjoxMDB9LHsieCI6NDA0LjIzNDYxMDc2NDgxNDIsInkiOi04OS4wMTM4NDMyODM2NzE3OH1d + + + SET_POS + WyJiZDkyZjlkOC03ZDYzLTQyYjItOTY4Yi0zOTA5ZWY0YzcyMzciLHsieCI6NDA0LjIzNDYxMDc2NDgxNDIsInkiOi04OS4wMTM4NDMyODM2NzE3OH0seyJ4IjoxMDAsInkiOjEwMH1d + + 3d4a875a8a6ea281598aa70364b0ea82 + + + 1664644951652 + + DEL_NODE + WyJkOTVmNzg4Ny00MmJmLTRlMTItYTAyMi0wMjE4Njk3MDFjNmEiXQ== + + + ADD_NODE + WyJDWjpjcHltYXgucHkiLHsid2lkdGgiOjEyMiwiaGVpZ2h0Ijo1MCwic2hhcGUiOiJyZWN0YW5nbGUiLCJvcGFjaXR5IjoxLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJvcmRpbiIseyJ4IjotMTM3Ljc3NjIwMzU0OTQyMzgzLCJ5Ijo5MS4zMjAxMjcwNjgyNTY0MX0se30sImQ5NWY3ODg3LTQyYmYtNGUxMi1hMDIyLTAyMTg2OTcwMWM2YSJd + + 5ed7e3d12fd25656b2ad03e29c307d65 + + + 1664644958838 + + SET_POS + WyJkOTVmNzg4Ny00MmJmLTRlMTItYTAyMi0wMjE4Njk3MDFjNmEiLHsieCI6MTAwLCJ5IjoxMDB9LHsieCI6OTcuNDEwNzY5MjcwMDg2NjksInkiOi05MS42MDMwNzQwMTM1ODUxfV0= + + + SET_POS + WyJkOTVmNzg4Ny00MmJmLTRlMTItYTAyMi0wMjE4Njk3MDFjNmEiLHsieCI6OTcuNDEwNzY5MjcwMDg2NjksInkiOi05MS42MDMwNzQwMTM1ODUxfSx7IngiOjEwMCwieSI6MTAwfV0= + + 35c613c8203b65e1f44e066b3d783143 + + + 1664644988539 + + DEL_NODE + WyJmNTZmOGIyNC00MWE2LTQ1YjQtODgyYi02NTk2MDQwYjZhZjAiXQ== + + + ADD_NODE + WyJGMTpmdW5jYWxsLnB5Iix7IndpZHRoIjoxMTEsImhlaWdodCI6NTAsInNoYXBlIjoicmVjdGFuZ2xlIiwib3BhY2l0eSI6MSwiYmFja2dyb3VuZENvbG9yIjoiI2ZmY2MwMCIsImJvcmRlckNvbG9yIjoiIzAwMCIsImJvcmRlcldpZHRoIjoxfSwib3JkaW4iLHsieCI6MTAwLCJ5Ijo5NC43NzM2MjI4MjYyMzMxMX0se30sImY1NmY4YjI0LTQxYTYtNDViNC04ODJiLTY1OTYwNDBiNmFmMCJd + + 33d0b0cc4d3dbe3c42323e33f06993e9 + + + 1664645002278 + + DEL_NODE + WyJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiXQ== + + + ADD_NODE + WyJGMjpmdW5ib2R5LnB5Iix7IndpZHRoIjoxMjAsImhlaWdodCI6NTAsInNoYXBlIjoicmVjdGFuZ2xlIiwib3BhY2l0eSI6MSwiYmFja2dyb3VuZENvbG9yIjoiI2ZmY2MwMCIsImJvcmRlckNvbG9yIjoiIzAwMCIsImJvcmRlcldpZHRoIjoxfSwib3JkaW4iLHsieCI6MzM2LjAxODYzNzA4NTU4NjY1LCJ5IjoxMDAuOTM3NjkyNDQ1MzAzNDF9LHt9LCJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiXQ== + + 8d0d4a735631afe6241c66143fb29db8 + + + 1664645010353 + + SET_POS + WyJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLHsieCI6MTEwLCJ5IjoxMTB9LHsieCI6Mzk0LjgxNTM4MDI5MDQ2NDMsInkiOjEwMC45Mzc2OTI0NDUzMDM0MX1d + + + SET_POS + WyJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLHsieCI6Mzk0LjgxNTM4MDI5MDQ2NDMsInkiOjEwMC45Mzc2OTI0NDUzMDM0MX0seyJ4IjoxMTAsInkiOjExMH1d + + 8a01a9ca8b3706bc3b1ce4de33669b6d + + + 1664645015576 + + DEL_EDGE + WyIwZjk1MWZiYy0wZDNmLTQzYzAtYmJmMC04NjViYzQ3ZjEyMGUiXQ== + + + ADD_EDGE + W3sic291cmNlSUQiOiJkOTVmNzg4Ny00MmJmLTRlMTItYTAyMi0wMjE4Njk3MDFjNmEiLCJ0YXJnZXRJRCI6ImY1NmY4YjI0LTQxYTYtNDViNC04ODJiLTY1OTYwNDBiNmFmMCIsImxhYmVsIjoiVSIsInN0eWxlIjp7InRoaWNrbmVzcyI6MSwiYmFja2dyb3VuZENvbG9yIjoiIzgyNzcxNyIsInNoYXBlIjoic29saWQifSwiaWQiOiIwZjk1MWZiYy0wZDNmLTQzYzAtYmJmMC04NjViYzQ3ZjEyMGUifV0= + + 14416fc2e3c48db65e2b5e1012027ee0 + + + 1664645043815 + + DEL_EDGE + WyIzY2ZiNDBjZC01NTdhLTQ4NTAtOTNhNi1mZGMwOWNkMDA1ZjAiXQ== + + + ADD_EDGE + W3sic291cmNlSUQiOiJmNTZmOGIyNC00MWE2LTQ1YjQtODgyYi02NTk2MDQwYjZhZjAiLCJ0YXJnZXRJRCI6ImRiMzFiZTNmLTNlNTgtNGMzNi05NzlkLTQxZmIyOGFlZmU2OCIsImxhYmVsIjoiVTEiLCJzdHlsZSI6eyJ0aGlja25lc3MiOjEsImJhY2tncm91bmRDb2xvciI6IiM3YzRkZmYiLCJzaGFwZSI6InNvbGlkIn0sImlkIjoiM2NmYjQwY2QtNTU3YS00ODUwLTkzYTYtZmRjMDljZDAwNWYwIn1d + + 326a920ffd7e662bc64ca95c086fb9de + + + 1664645057658 + + DEL_EDGE + WyI3MTRkYjk4OS01NjcyLTQwM2ItYWU3Ni1mZDlhMjA4OTM0NzUiXQ== + + + ADD_EDGE + W3sic291cmNlSUQiOiJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLCJ0YXJnZXRJRCI6ImJkOTJmOWQ4LTdkNjMtNDJiMi05NjhiLTM5MDllZjRjNzIzNyIsImxhYmVsIjoiVTIiLCJzdHlsZSI6eyJ0aGlja25lc3MiOjEsImJhY2tncm91bmRDb2xvciI6IiNmZjZkMDAiLCJzaGFwZSI6InNvbGlkIn0sImlkIjoiNzE0ZGI5ODktNTY3Mi00MDNiLWFlNzYtZmQ5YTIwODkzNDc1In1d + + 4593337e9924ae4b23dc5c5576c31394 + + + 1664645068951 + + DEL_EDGE + WyJhOGFlNzg5MC1iMmJiLTQyNzMtODc1My0wMTgxY2ViNDg2YzEiXQ== + + + ADD_EDGE + W3sic291cmNlSUQiOiJiZDkyZjlkOC03ZDYzLTQyYjItOTY4Yi0zOTA5ZWY0YzcyMzciLCJ0YXJnZXRJRCI6ImRiMzFiZTNmLTNlNTgtNGMzNi05NzlkLTQxZmIyOGFlZmU2OCIsImxhYmVsIjoiWTIiLCJzdHlsZSI6eyJ0aGlja25lc3MiOjEsImJhY2tncm91bmRDb2xvciI6IiNmNDQzMzYiLCJzaGFwZSI6InNvbGlkIn0sImlkIjoiYThhZTc4OTAtYjJiYi00MjczLTg3NTMtMDE4MWNlYjQ4NmMxIn1d + + 568d8b7a109ffacc4b912095793cb2ca + + + 1664645081283 + + DEL_EDGE + WyI1NWI5OWFiNi1hN2Q2LTRjNjctYWI0ZS1hOGUyOTM5YzFiMGYiXQ== + + + ADD_EDGE + W3sic291cmNlSUQiOiJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLCJ0YXJnZXRJRCI6ImY1NmY4YjI0LTQxYTYtNDViNC04ODJiLTY1OTYwNDBiNmFmMCIsImxhYmVsIjoiWTEiLCJzdHlsZSI6eyJ0aGlja25lc3MiOjEsImJhY2tncm91bmRDb2xvciI6IiNmZjZkMDAiLCJzaGFwZSI6InNvbGlkIn0sImlkIjoiNTViOTlhYjYtYTdkNi00YzY3LWFiNGUtYThlMjkzOWMxYjBmIn1d + + 0d0aa0179f22f9d73a11f8ccfbcc145e + + + 1664645089735 + + SET_POS + WyJmNTZmOGIyNC00MWE2LTQ1YjQtODgyYi02NTk2MDQwYjZhZjAiLHsieCI6MTAwLCJ5IjoxMDB9LHsieCI6MTAwLCJ5Ijo5NC43NzM2MjI4MjYyMzMxMX1d + + + SET_POS + WyJmNTZmOGIyNC00MWE2LTQ1YjQtODgyYi02NTk2MDQwYjZhZjAiLHsieCI6MTAwLCJ5Ijo5NC43NzM2MjI4MjYyMzMxMX0seyJ4IjoxMDAsInkiOjEwMH1d + + 1c19591402c0f2daca7d2b4a8af5e956 + + + 1664645092868 + + DEL_EDGE + WyI3OWE1NDdmNS02NzBhLTQ1ZjYtYTc4My02ZGI4ZmYwZTY1NTkiXQ== + + + ADD_EDGE + W3sic291cmNlSUQiOiJmNTZmOGIyNC00MWE2LTQ1YjQtODgyYi02NTk2MDQwYjZhZjAiLCJ0YXJnZXRJRCI6ImQ5NWY3ODg3LTQyYmYtNGUxMi1hMDIyLTAyMTg2OTcwMWM2YSIsImxhYmVsIjoiWSIsInN0eWxlIjp7InRoaWNrbmVzcyI6MSwiYmFja2dyb3VuZENvbG9yIjoiIzgyNzcxNyIsInNoYXBlIjoic29saWQifSwiaWQiOiI3OWE1NDdmNS02NzBhLTQ1ZjYtYTc4My02ZGI4ZmYwZTY1NTkifV0= + + 3c913497d8aa8f1c79bbdc03c3feec16 + + + 1664645142026 + + SET_POS + WyJkOTVmNzg4Ny00MmJmLTRlMTItYTAyMi0wMjE4Njk3MDFjNmEiLHsieCI6OTcuNDEwNzY5MjcwMDg2NjksInkiOi05MS42MDMwNzQwMTM1ODUxfSx7IngiOi0xNzAuNDQxMDYwODg1NDY2OTUsInkiOjkwLjAxMzUzMjc3NDgxNDY5fV0= + + + SET_POS + WyJkOTVmNzg4Ny00MmJmLTRlMTItYTAyMi0wMjE4Njk3MDFjNmEiLHsieCI6LTE3MC40NDEwNjA4ODU0NjY5NSwieSI6OTAuMDEzNTMyNzc0ODE0Njl9LHsieCI6OTcuNDEwNzY5MjcwMDg2NjksInkiOi05MS42MDMwNzQwMTM1ODUxfV0= + + ecbd46b28ecaf800c8da0d2ada69b4de + + + 1664645149601 + + SET_POS + WyJiZDkyZjlkOC03ZDYzLTQyYjItOTY4Yi0zOTA5ZWY0YzcyMzciLHsieCI6NDA0LjIzNDYxMDc2NDgxNDIsInkiOi04OS4wMTM4NDMyODM2NzE3OH0seyJ4Ijo3NTQuNDAxODgxNDA3MTk2NSwieSI6OTkuMTM1NzM0OTcxOTM2NjR9XQ== + + + SET_POS + WyJiZDkyZjlkOC03ZDYzLTQyYjItOTY4Yi0zOTA5ZWY0YzcyMzciLHsieCI6NzU0LjQwMTg4MTQwNzE5NjUsInkiOjk5LjEzNTczNDk3MTkzNjY0fSx7IngiOjQwNC4yMzQ2MTA3NjQ4MTQyLCJ5IjotODkuMDEzODQzMjgzNjcxNzh9XQ== + + ea35c112764d7964f8b7e35e9a18efbd + + + 1664645223291 + + SET_POS + WyJiZDkyZjlkOC03ZDYzLTQyYjItOTY4Yi0zOTA5ZWY0YzcyMzciLHsieCI6NzU0LjQwMTg4MTQwNzE5NjUsInkiOjk5LjEzNTczNDk3MTkzNjY0fSx7IngiOjY3Ni4wMDYyMjM4MDA2OTMsInkiOjEwMC40NDIzMjkyNjUzNzgzNn1d + + + SET_POS + WyJiZDkyZjlkOC03ZDYzLTQyYjItOTY4Yi0zOTA5ZWY0YzcyMzciLHsieCI6Njc2LjAwNjIyMzgwMDY5MywieSI6MTAwLjQ0MjMyOTI2NTM3ODM2fSx7IngiOjc1NC40MDE4ODE0MDcxOTY1LCJ5Ijo5OS4xMzU3MzQ5NzE5MzY2NH1d + + d669d3d37a4693ad860a18c69999b31f + + + 1664645228453 + + SET_POS + WyJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLHsieCI6Mzk0LjgxNTM4MDI5MDQ2NDMsInkiOjEwMC45Mzc2OTI0NDUzMDM0MX0seyJ4IjozMzYuMDE4NjM3MDg1NTg2NjUsInkiOjEwMC45Mzc2OTI0NDUzMDM0MX1d + + + SET_POS + WyJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLHsieCI6MzM2LjAxODYzNzA4NTU4NjY1LCJ5IjoxMDAuOTM3NjkyNDQ1MzAzNDF9LHsieCI6Mzk0LjgxNTM4MDI5MDQ2NDMsInkiOjEwMC45Mzc2OTI0NDUzMDM0MX1d + + f7eb6af4003cb4eff19f39421fc00174 + + + 1664645231883 + + SET_POS + WyJiZDkyZjlkOC03ZDYzLTQyYjItOTY4Yi0zOTA5ZWY0YzcyMzciLHsieCI6Njc2LjAwNjIyMzgwMDY5MywieSI6MTAwLjQ0MjMyOTI2NTM3ODM2fSx7IngiOjU4Ny4xNTc4MTE4NDY2NTU2LCJ5IjoxMDAuNDQyMzI5MjY1Mzc4MzZ9XQ== + + + SET_POS + WyJiZDkyZjlkOC03ZDYzLTQyYjItOTY4Yi0zOTA5ZWY0YzcyMzciLHsieCI6NTg3LjE1NzgxMTg0NjY1NTYsInkiOjEwMC40NDIzMjkyNjUzNzgzNn0seyJ4Ijo2NzYuMDA2MjIzODAwNjkzLCJ5IjoxMDAuNDQyMzI5MjY1Mzc4MzZ9XQ== + + 49dd0c2013be6e39c2f11ea967dbcab4 + + + 1664645237206 + + SET_POS + WyJkOTVmNzg4Ny00MmJmLTRlMTItYTAyMi0wMjE4Njk3MDFjNmEiLHsieCI6LTE3MC40NDEwNjA4ODU0NjY5NSwieSI6OTAuMDEzNTMyNzc0ODE0Njl9LHsieCI6LTEzNy43NzYyMDM1NDk0MjM4MywieSI6OTEuMzIwMTI3MDY4MjU2NDF9XQ== + + + SET_POS + WyJkOTVmNzg4Ny00MmJmLTRlMTItYTAyMi0wMjE4Njk3MDFjNmEiLHsieCI6LTEzNy43NzYyMDM1NDk0MjM4MywieSI6OTEuMzIwMTI3MDY4MjU2NDF9LHsieCI6LTE3MC40NDEwNjA4ODU0NjY5NSwieSI6OTAuMDEzNTMyNzc0ODE0Njl9XQ== + + 009fc03903a9ae6003caea49a6575ddb + + + 1666487497309 + + ADD_EDGE + W3sibGFiZWwiOiJZMSIsInNvdXJjZSI6IjdiNjljNmU2LTY0ZDEtNGQxZC1hODVkLThmODIwZGVhZTkyZSIsInRhcmdldCI6ImY1NmY4YjI0LTQxYTYtNDViNC04ODJiLTY1OTYwNDBiNmFmMCIsInN0eWxlIjp7ImJhY2tncm91bmRDb2xvciI6IiNmZjZkMDAiLCJ0aGlja25lc3MiOjEsInNoYXBlIjoic29saWQifSwiYmVuZERhdGEiOnsiYmVuZERpc3RhbmNlIjoyNS4wMTU2MDQ0Mzg0NDI0NywiYmVuZFdlaWdodCI6MC41MDcyNjEyODIxMTY0NTQyLCJiZW5kUG9pbnQiOnsieCI6MTg1Ljk0OTI3NTk2OTkwNjY3LCJ5Ijo2Ny4wNTc5Mjg3NTI0OTY3Mn19LCJzb3VyY2VJRCI6ImRiMzFiZTNmLTNlNTgtNGMzNi05NzlkLTQxZmIyOGFlZmU2OCIsInRhcmdldElEIjoiZjU2ZjhiMjQtNDFhNi00NWI0LTg4MmItNjU5NjA0MGI2YWYwIiwidHlwZSI6Im9yZGluIiwiaWQiOiJkYWRmMjgyOS1lMDEwLTRlMGQtYTNhNy1iYTdkYjg3OWM2NDkifV0= + + + DEL_EDGE + WyJkYWRmMjgyOS1lMDEwLTRlMGQtYTNhNy1iYTdkYjg3OWM2NDkiXQ== + + 4957bb66f70bda073e7edafdad81f6b4 + + + 1666487500685 + + ADD_EDGE + W3sibGFiZWwiOiJVMSIsInNvdXJjZSI6IjZhYzczNmNiLWUyZmUtNGI4Mi04NzQ1LTUyZjkzNGYyZGYxMCIsInRhcmdldCI6ImRiMzFiZTNmLTNlNTgtNGMzNi05NzlkLTQxZmIyOGFlZmU2OCIsInN0eWxlIjp7ImJhY2tncm91bmRDb2xvciI6IiM3YzRkZmYiLCJ0aGlja25lc3MiOjEsInNoYXBlIjoic29saWQifSwiYmVuZERhdGEiOnsiYmVuZERpc3RhbmNlIjowLjAwMDAyNzg2ODE3Mzg2NTQ2NDAzLCJiZW5kV2VpZ2h0IjowLjUwMDAwMDAwNDAzMDUwNjYsImJlbmRQb2ludCI6eyJ4IjoyNDUuNzU5MzE4NTQyNzk0OTQsInkiOjk4LjU4MDQzNDE0NjM5NTE4fX0sInNvdXJjZUlEIjoiZjU2ZjhiMjQtNDFhNi00NWI0LTg4MmItNjU5NjA0MGI2YWYwIiwidGFyZ2V0SUQiOiJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLCJ0eXBlIjoib3JkaW4iLCJpZCI6ImRlYzYzYTI5LWY1NWEtNGNmNy04OWIwLWQ3NjY2YzcyMmFkOSJ9XQ== + + + DEL_EDGE + WyJkZWM2M2EyOS1mNTVhLTRjZjctODliMC1kNzY2NmM3MjJhZDkiXQ== + + c8d5e89e2630a4a0aa607594e14861d1 + + + 1666487596231 + + SET_POS + WyJmNTZmOGIyNC00MWE2LTQ1YjQtODgyYi02NTk2MDQwYjZhZjAiLHsieCI6MTAwLCJ5Ijo5NC43NzM2MjI4MjYyMzMxMX0seyJ4Ijo2MDEsInkiOjIwNC43NzM2MjI4MjYyMzMxMn1d + + + SET_POS + WyJmNTZmOGIyNC00MWE2LTQ1YjQtODgyYi02NTk2MDQwYjZhZjAiLHsieCI6NjAxLCJ5IjoyMDQuNzczNjIyODI2MjMzMTJ9LHsieCI6MTAwLCJ5Ijo5NC43NzM2MjI4MjYyMzMxMX1d + + c7aeac0c4601154d11c4c4068f631c62 + + + 1666487606788 + + SET_POS + WyJkOTVmNzg4Ny00MmJmLTRlMTItYTAyMi0wMjE4Njk3MDFjNmEiLHsieCI6LTEzNy43NzYyMDM1NDk0MjM4MywieSI6OTEuMzIwMTI3MDY4MjU2NDF9LHsieCI6MzQ2LjIyMzc5NjQ1MDU3NjIsInkiOjE5Mi4zMjAxMjcwNjgyNTY0fV0= + + + SET_POS + WyJkOTVmNzg4Ny00MmJmLTRlMTItYTAyMi0wMjE4Njk3MDFjNmEiLHsieCI6MzQ2LjIyMzc5NjQ1MDU3NjIsInkiOjE5Mi4zMjAxMjcwNjgyNTY0fSx7IngiOi0xMzcuNzc2MjAzNTQ5NDIzODMsInkiOjkxLjMyMDEyNzA2ODI1NjQxfV0= + + 600af97eb5c4cc3a573f65c724b85ae9 + + + 1666488874534 + + SET_POS + WyJkOTVmNzg4Ny00MmJmLTRlMTItYTAyMi0wMjE4Njk3MDFjNmEiLHsieCI6MzQ2LjIyMzc5NjQ1MDU3NjIsInkiOjI0Ni4zMjAxMjcwNjgyNTY0fSx7IngiOjM0Mi4yMjM3OTY0NTA1NzYyLCJ5IjoyMDkuMzIwMTI3MDY4MjU2NH1d + + + SET_POS + WyJkOTVmNzg4Ny00MmJmLTRlMTItYTAyMi0wMjE4Njk3MDFjNmEiLHsieCI6MzQyLjIyMzc5NjQ1MDU3NjIsInkiOjIwOS4zMjAxMjcwNjgyNTY0fSx7IngiOjM0Ni4yMjM3OTY0NTA1NzYyLCJ5IjoyNDYuMzIwMTI3MDY4MjU2NH1d + + ea3cf1656cbb3323791a747bcb8f624c + + + 1666810512066 + + UPDATE_NODE + WyJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLHsid2lkdGgiOjEyMCwiaGVpZ2h0Ijo1MCwib3BhY2l0eSI6MSwic2hhcGUiOiJyZWN0YW5nbGUiLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJGMjpmdW5ib2R5LnB5Iix0cnVlXQ== + + + UPDATE_NODE + WyJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLHsid2lkdGgiOjEzMCwiaGVpZ2h0Ijo1MCwib3BhY2l0eSI6MSwic2hhcGUiOiJyZWN0YW5nbGUiLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJGMjpmdW5ib2R5Mi5weSIsdHJ1ZV0= + + 33c0759e9ca1d4d92639a94477c9b052 + + + 1666810533063 + + UPDATE_NODE + WyJmNTZmOGIyNC00MWE2LTQ1YjQtODgyYi02NTk2MDQwYjZhZjAiLHsid2lkdGgiOjExMSwiaGVpZ2h0Ijo1MCwib3BhY2l0eSI6MSwic2hhcGUiOiJyZWN0YW5nbGUiLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJGMTpmdW5jYWxsLnB5Iix0cnVlXQ== + + + UPDATE_NODE + WyJmNTZmOGIyNC00MWE2LTQ1YjQtODgyYi02NTk2MDQwYjZhZjAiLHsid2lkdGgiOjEyMCwiaGVpZ2h0Ijo1MCwib3BhY2l0eSI6MSwic2hhcGUiOiJyZWN0YW5nbGUiLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJGMTpmdW5jYWxsMi5weSIsdHJ1ZV0= + + 06d61b24bc2485f9464f3e2751d1409a + + + 1748868954808 + + UPDATE_NODE + WyJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLHsid2lkdGgiOjEzMCwiaGVpZ2h0Ijo1MCwib3BhY2l0eSI6MSwic2hhcGUiOiJyZWN0YW5nbGUiLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJGMjpmdW5ib2R5Mi5weSIsdHJ1ZV0= + + + UPDATE_NODE + WyJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLHsid2lkdGgiOjEzMCwiaGVpZ2h0Ijo1MCwib3BhY2l0eSI6MSwic2hhcGUiOiJyZWN0YW5nbGUiLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJGMjpmdW5ib2R5My5weSIsdHJ1ZV0= + + 4afd9af5e90936e999770a941ab0ace6 + + + 1748868975001 + + UPDATE_NODE + WyJmNTZmOGIyNC00MWE2LTQ1YjQtODgyYi02NTk2MDQwYjZhZjAiLHsid2lkdGgiOjEyMCwiaGVpZ2h0Ijo1MCwib3BhY2l0eSI6MSwic2hhcGUiOiJyZWN0YW5nbGUiLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJGMTpmdW5jYWxsMi5weSIsdHJ1ZV0= + + + UPDATE_NODE + WyJmNTZmOGIyNC00MWE2LTQ1YjQtODgyYi02NTk2MDQwYjZhZjAiLHsid2lkdGgiOjE1MCwiaGVpZ2h0Ijo1MCwib3BhY2l0eSI6MSwic2hhcGUiOiJyZWN0YW5nbGUiLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJGMTpmdW5jYWxsX3ptcS5weSIsdHJ1ZV0= + + 38c7cf57be2a0a3b4b1bafb14e9d29fb + + + 1748868991559 + + UPDATE_NODE + WyJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLHsid2lkdGgiOjEzMCwiaGVpZ2h0Ijo1MCwib3BhY2l0eSI6MSwic2hhcGUiOiJyZWN0YW5nbGUiLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJGMjpmdW5ib2R5My5weSIsdHJ1ZV0= + + + UPDATE_NODE + WyJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLHsid2lkdGgiOjE2MSwiaGVpZ2h0Ijo1MCwib3BhY2l0eSI6MSwic2hhcGUiOiJyZWN0YW5nbGUiLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJGMjpmdW5ib2R5X3ptcS5weSIsdHJ1ZV0= + + 16982a4aa4703d98fc301e30aed0d25a + + + 1748971661849 + + UPDATE_NODE + WyJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLHsid2lkdGgiOjE2MSwiaGVpZ2h0Ijo1MCwib3BhY2l0eSI6MSwic2hhcGUiOiJyZWN0YW5nbGUiLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJGMjpmdW5ib2R5X3ptcS5weSIsdHJ1ZV0= + + + UPDATE_NODE + WyJkYjMxYmUzZi0zZTU4LTRjMzYtOTc5ZC00MWZiMjhhZWZlNjgiLHsid2lkdGgiOjE2OSwiaGVpZ2h0Ijo1MCwib3BhY2l0eSI6MSwic2hhcGUiOiJyZWN0YW5nbGUiLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJGMjpmdW5ib2R5X3ptcTIucHkiLHRydWVd + + bc5037b25958965e670ba8b2200dbcf3 + + + 1748971670391 + + UPDATE_NODE + WyJmNTZmOGIyNC00MWE2LTQ1YjQtODgyYi02NTk2MDQwYjZhZjAiLHsid2lkdGgiOjE1MCwiaGVpZ2h0Ijo1MCwib3BhY2l0eSI6MSwic2hhcGUiOiJyZWN0YW5nbGUiLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJGMTpmdW5jYWxsX3ptcS5weSIsdHJ1ZV0= + + + UPDATE_NODE + WyJmNTZmOGIyNC00MWE2LTQ1YjQtODgyYi02NTk2MDQwYjZhZjAiLHsid2lkdGgiOjE1OSwiaGVpZ2h0Ijo1MCwib3BhY2l0eSI6MSwic2hhcGUiOiJyZWN0YW5nbGUiLCJiYWNrZ3JvdW5kQ29sb3IiOiIjZmZjYzAwIiwiYm9yZGVyQ29sb3IiOiIjMDAwIiwiYm9yZGVyV2lkdGgiOjF9LCJGMTpmdW5jYWxsX3ptcTIucHkiLHRydWVd + + 7bcfd7cb9bf8eaae8a615d108c4264ac + + + \ No newline at end of file diff --git a/concore.py b/concore.py index 51eef7b..7b0b3b4 100644 --- a/concore.py +++ b/concore.py @@ -3,6 +3,7 @@ from ast import literal_eval import sys import re +import zmq # Added for ZeroMQ #if windows, create script to kill this process # because batch files don't provide easy way to know pid of last command @@ -12,12 +13,56 @@ with open("concorekill.bat","w") as fpid: fpid.write("taskkill /F /PID "+str(os.getpid())+"\n") +# --- ZeroMQ Integration Start --- +class ZeroMQPort: + def __init__(self, port_type, address, zmq_socket_type): + self.context = zmq.Context() + self.socket = self.context.socket(zmq_socket_type) + self.port_type = port_type # "bind" or "connect" + self.address = address + if self.port_type == "bind": + self.socket.bind(address) + print(f"ZMQ Port bound to {address}") + else: + self.socket.connect(address) + print(f"ZMQ Port connected to {address}") + +# Global ZeroMQ ports registry +zmq_ports = {} + +def init_zmq_port(port_name, port_type, address, socket_type_str): + """ + Initializes and registers a ZeroMQ port. + port_name (str): A unique name for this ZMQ port. + port_type (str): "bind" or "connect". + address (str): The ZMQ address (e.g., "tcp://*:5555", "tcp://localhost:5555"). + socket_type_str (str): String representation of ZMQ socket type (e.g., "REQ", "REP", "PUB", "SUB"). + """ + if port_name in zmq_ports: + print(f"ZMQ Port {port_name} already initialized.") + return # Avoid reinitialization + + try: + # Map socket type string to actual ZMQ constant (e.g., zmq.REQ, zmq.REP) + zmq_socket_type = getattr(zmq, socket_type_str.upper()) + zmq_ports[port_name] = ZeroMQPort(port_type, address, zmq_socket_type) + print(f"Initialized ZMQ port: {port_name} ({socket_type_str}) on {address}") + except AttributeError: + print(f"Error: Invalid ZMQ socket type string '{socket_type_str}'.") + except zmq.error.ZMQError as e: + print(f"Error initializing ZMQ port {port_name} on {address}: {e}") + except Exception as e: + print(f"An unexpected error occurred during ZMQ port initialization for {port_name}: {e}") + +# --- ZeroMQ Integration End --- + def safe_literal_eval(filename, defaultValue): try: with open(filename, "r") as file: return literal_eval(file.read()) except (FileNotFoundError, SyntaxError, ValueError, Exception) as e: - print(f"Error reading {filename}: {e}") + # Keep print for debugging, but can be made quieter + # print(f"Info: Error reading {filename} or file not found, using default: {e}") return defaultValue iport = safe_literal_eval("concore.iport", {}) @@ -33,20 +78,30 @@ def safe_literal_eval(filename, defaultValue): #9/21/22 try: - sparams = open(inpath+"1/concore.params").read() - if sparams[0] == '"': #windows keeps "" need to remove - sparams = sparams[1:] - sparams = sparams[0:sparams.find('"')] - if sparams != '{': - print("converting sparams: "+sparams) - sparams = "{'"+re.sub(';',",'",re.sub('=',"':",re.sub(' ','',sparams)))+"}" - print("converted sparams: " + sparams) - try: - params = literal_eval(sparams) - except: - print("bad params: "+sparams) -except: + sparams_path = os.path.join(inpath + "1", "concore.params") + if os.path.exists(sparams_path): + with open(sparams_path, "r") as f: + sparams = f.read() + if sparams: # Ensure sparams is not empty + if sparams[0] == '"' and sparams[-1] == '"': #windows keeps "" need to remove + sparams = sparams[1:-1] + if sparams != '{' and not (sparams.startswith('{') and sparams.endswith('}')): # Check if it needs conversion + print("converting sparams: "+sparams) + sparams = "{'"+re.sub(';',",'",re.sub('=',"':",re.sub(' ','',sparams)))+"}" + print("converted sparams: " + sparams) + try: + params = literal_eval(sparams) + except Exception as e: + print(f"bad params content: {sparams}, error: {e}") + params = dict() + else: + params = dict() + else: + params = dict() +except Exception as e: + # print(f"Info: concore.params not found or error reading, using empty dict: {e}") params = dict() + #9/30/22 def tryparam(n, i): return params.get(n, i) @@ -55,7 +110,8 @@ def tryparam(n, i): #9/12/21 def default_maxtime(default): global maxtime - maxtime = safe_literal_eval(os.path.join(inpath + "1", "concore.maxtime"), default) + maxtime_path = os.path.join(inpath + "1", "concore.maxtime") + maxtime = safe_literal_eval(maxtime_path, default) default_maxtime(100) @@ -67,23 +123,49 @@ def unchanged(): olds = s return False -def read(port, name, initstr): +def read(port_identifier, name, initstr_val): global s, simtime, retrycount - max_retries=5 - time.sleep(delay) - file_path = os.path.join(inpath+str(port), name) + + default_return_val = initstr_val + if isinstance(initstr_val, str): + try: + default_return_val = literal_eval(initstr_val) + except (SyntaxError, ValueError): + pass + + if isinstance(port_identifier, str) and port_identifier in zmq_ports: + zmq_p = zmq_ports[port_identifier] + try: + message = zmq_p.socket.recv_json() + return message + except zmq.error.ZMQError as e: + print(f"ZMQ read error on port {port_identifier} (name: {name}): {e}. Returning default.") + return default_return_val + except Exception as e: + print(f"Unexpected error during ZMQ read on port {port_identifier} (name: {name}): {e}. Returning default.") + return default_return_val + + try: + file_port_num = int(port_identifier) + except ValueError: + print(f"Error: Invalid port identifier '{port_identifier}' for file operation. Must be integer or ZMQ name.") + return default_return_val + + time.sleep(delay) + file_path = os.path.join(inpath+str(file_port_num), name) + ins = "" try: with open(file_path, "r") as infile: ins = infile.read() except FileNotFoundError: - print(f"File {file_path} not found, using default value.") - ins = initstr + ins = str(initstr_val) except Exception as e: - print(f"Error reading {file_path}: {e}") - return initstr + print(f"Error reading {file_path}: {e}. Using default value.") + return default_return_val attempts = 0 + max_retries = 5 while len(ins) == 0 and attempts < max_retries: time.sleep(delay) try: @@ -96,44 +178,78 @@ def read(port, name, initstr): if len(ins) == 0: print(f"Max retries reached for {file_path}, using default value.") - return initstr + return default_return_val - s += ins + s += ins try: inval = literal_eval(ins) - simtime = max(simtime, inval[0]) - return inval[1:] + if isinstance(inval, list) and len(inval) > 0: + current_simtime_from_file = inval[0] + if isinstance(current_simtime_from_file, (int, float)): + simtime = max(simtime, current_simtime_from_file) + return inval[1:] + else: + print(f"Warning: Unexpected data format in {file_path}: {ins}. Returning raw content or default.") + return inval except Exception as e: - print(f"Error parsing {ins}: {e}") - return initstr + print(f"Error parsing content from {file_path} ('{ins}'): {e}. Returning default.") + return default_return_val -def write(port, name, val, delta=0): +def write(port_identifier, name, val, delta=0): global simtime - file_path = os.path.join(outpath+str(port), name) + + if isinstance(port_identifier, str) and port_identifier in zmq_ports: + zmq_p = zmq_ports[port_identifier] + try: + zmq_p.socket.send_json(val) + except zmq.error.ZMQError as e: + print(f"ZMQ write error on port {port_identifier} (name: {name}): {e}") + except Exception as e: + print(f"Unexpected error during ZMQ write on port {port_identifier} (name: {name}): {e}") + return + + try: + file_port_num = int(port_identifier) + except ValueError: + print(f"Error: Invalid port identifier '{port_identifier}' for file operation. Must be integer or ZMQ name.") + return + + file_path = os.path.join(outpath+str(file_port_num), name) if isinstance(val, str): - time.sleep(2 * delay) + time.sleep(2 * delay) elif not isinstance(val, list): - print("write must have list or str") + print(f"File write to {file_path} must have list or str value, got {type(val)}") return try: with open(file_path, "w") as outfile: if isinstance(val, list): - outfile.write(str([simtime + delta] + val)) - simtime += delta - else: + data_to_write = [simtime + delta] + val + outfile.write(str(data_to_write)) + simtime += delta + else: outfile.write(val) except Exception as e: print(f"Error writing to {file_path}: {e}") -def initval(simtime_val): +def initval(simtime_val_str): global simtime try: - val = literal_eval(simtime_val) - simtime = val[0] - return val[1:] + val = literal_eval(simtime_val_str) + if isinstance(val, list) and len(val) > 0: + first_element = val[0] + if isinstance(first_element, (int, float)): + simtime = first_element + return val[1:] + else: + print(f"Error: First element in initval string '{simtime_val_str}' is not a number. Using data part as is or empty.") + return val[1:] if len(val) > 1 else [] + else: + print(f"Error: initval string '{simtime_val_str}' is not a list or is empty. Returning empty list.") + return [] + except Exception as e: - print(f"Error parsing simtime_val: {e}") - return [] + print(f"Error parsing simtime_val_str '{simtime_val_str}': {e}. Returning empty list.") + return [] \ No newline at end of file