55import re
66import zmq # Added for ZeroMQ
77
8- #if windows, create script to kill this process
8+ # if windows, create script to kill this process
99# because batch files don't provide easy way to know pid of last command
10- # ignored for posix!= windows, because "concorepid" is handled by script
11- # ignored for docker (linux!= windows), because handled by docker stop
10+ # ignored for posix != windows, because "concorepid" is handled by script
11+ # ignored for docker (linux != windows), because handled by docker stop
1212if hasattr (sys , 'getwindowsversion' ):
1313 with open ("concorekill.bat" ,"w" ) as fpid :
1414 fpid .write ("taskkill /F /PID " + str (os .getpid ())+ "\n " )
1515
16- # --- ZeroMQ Integration Start ---
16+ # ===================================================================
17+ # ZeroMQ Communication Wrapper
18+ # ===================================================================
1719class ZeroMQPort :
1820 def __init__ (self , port_type , address , zmq_socket_type ):
21+ """
22+ port_type: "bind" or "connect"
23+ address: ZeroMQ address (e.g., "tcp://*:5555")
24+ zmq_socket_type: zmq.REQ, zmq.REP, zmq.PUB, zmq.SUB etc.
25+ """
1926 self .context = zmq .Context ()
2027 self .socket = self .context .socket (zmq_socket_type )
2128 self .port_type = port_type # "bind" or "connect"
2229 self .address = address
2330
24- self .socket .setsockopt (zmq .RCVTIMEO , 2000 )
25- self .socket .setsockopt (zmq .SNDTIMEO , 2000 )
26- self .socket .setsockopt (zmq .LINGER , 0 )
31+ # Configure timeouts & immediate close on failure
32+ self .socket .setsockopt (zmq .RCVTIMEO , 2000 ) # 2 sec receive timeout
33+ self .socket .setsockopt (zmq .SNDTIMEO , 2000 ) # 2 sec send timeout
34+ self .socket .setsockopt (zmq .LINGER , 0 ) # Drop pending messages on close
2735
36+ # Bind or connect
2837 if self .port_type == "bind" :
2938 self .socket .bind (address )
3039 print (f"ZMQ Port bound to { address } " )
@@ -33,6 +42,7 @@ def __init__(self, port_type, address, zmq_socket_type):
3342 print (f"ZMQ Port connected to { address } " )
3443
3544 def send_json_with_retry (self , message ):
45+ """Send JSON message with retries if timeout occurs."""
3646 for attempt in range (5 ):
3747 try :
3848 self .socket .send_json (message )
@@ -44,6 +54,7 @@ def send_json_with_retry(self, message):
4454 return
4555
4656 def recv_json_with_retry (self ):
57+ """Receive JSON message with retries if timeout occurs."""
4758 for attempt in range (5 ):
4859 try :
4960 return self .socket .recv_json ()
@@ -89,6 +100,9 @@ def terminate_zmq():
89100 print (f"Error while terminating ZMQ port { port .address } : { e } " )
90101# --- ZeroMQ Integration End ---
91102
103+ # ===================================================================
104+ # File & Parameter Handling
105+ # ===================================================================
92106def safe_literal_eval (filename , defaultValue ):
93107 try :
94108 with open (filename , "r" ) as file :
@@ -97,10 +111,13 @@ def safe_literal_eval(filename, defaultValue):
97111 # Keep print for debugging, but can be made quieter
98112 # print(f"Info: Error reading {filename} or file not found, using default: {e}")
99113 return defaultValue
100-
114+
115+
116+ # Load input/output ports if present
101117iport = safe_literal_eval ("concore.iport" , {})
102118oport = safe_literal_eval ("concore.oport" , {})
103119
120+ # Global variables
104121s = ''
105122olds = ''
106123delay = 1
@@ -110,14 +127,20 @@ def safe_literal_eval(filename, defaultValue):
110127simtime = 0
111128
112129#9/21/22
130+ # ===================================================================
131+ # Parameter Parsing
132+ # ===================================================================
113133try :
114134 sparams_path = os .path .join (inpath + "1" , "concore.params" )
115135 if os .path .exists (sparams_path ):
116136 with open (sparams_path , "r" ) as f :
117137 sparams = f .read ()
118138 if sparams : # Ensure sparams is not empty
139+ # Windows sometimes keeps quotes
119140 if sparams [0 ] == '"' and sparams [- 1 ] == '"' : #windows keeps "" need to remove
120141 sparams = sparams [1 :- 1 ]
142+
143+ # Convert key=value;key2=value2 to Python dict format
121144 if sparams != '{' and not (sparams .startswith ('{' ) and sparams .endswith ('}' )): # Check if it needs conversion
122145 print ("converting sparams: " + sparams )
123146 sparams = "{'" + re .sub (';' ,",'" ,re .sub ('=' ,"':" ,re .sub (' ' ,'' ,sparams )))+ "}"
@@ -137,35 +160,46 @@ def safe_literal_eval(filename, defaultValue):
137160
138161#9/30/22
139162def tryparam (n , i ):
163+ """Return parameter `n` from params dict, else default `i`."""
140164 return params .get (n , i )
141165
142166
143167#9/12/21
168+ # ===================================================================
169+ # Simulation Time Handling
170+ # ===================================================================
144171def default_maxtime (default ):
172+ """Read maximum simulation time from file or use default."""
145173 global maxtime
146174 maxtime_path = os .path .join (inpath + "1" , "concore.maxtime" )
147175 maxtime = safe_literal_eval (maxtime_path , default )
148176
149177default_maxtime (100 )
150178
151179def unchanged ():
180+ """Check if global string `s` is unchanged since last call."""
152181 global olds , s
153182 if olds == s :
154183 s = ''
155184 return True
156185 olds = s
157186 return False
158187
188+ # ===================================================================
189+ # I/O Handling (File + ZMQ)
190+ # ===================================================================
159191def read (port_identifier , name , initstr_val ):
160192 global s , simtime , retrycount
161193
194+ # Default return
162195 default_return_val = initstr_val
163196 if isinstance (initstr_val , str ):
164197 try :
165198 default_return_val = literal_eval (initstr_val )
166199 except (SyntaxError , ValueError ):
167200 pass
168-
201+
202+ # Case 1: ZMQ port
169203 if isinstance (port_identifier , str ) and port_identifier in zmq_ports :
170204 zmq_p = zmq_ports [port_identifier ]
171205 try :
@@ -178,6 +212,7 @@ def read(port_identifier, name, initstr_val):
178212 print (f"Unexpected error during ZMQ read on port { port_identifier } (name: { name } ): { e } . Returning default." )
179213 return default_return_val
180214
215+ # Case 2: File-based port
181216 try :
182217 file_port_num = int (port_identifier )
183218 except ValueError :
@@ -197,6 +232,7 @@ def read(port_identifier, name, initstr_val):
197232 print (f"Error reading { file_path } : { e } . Using default value." )
198233 return default_return_val
199234
235+ # Retry logic if file is empty
200236 attempts = 0
201237 max_retries = 5
202238 while len (ins ) == 0 and attempts < max_retries :
@@ -214,6 +250,8 @@ def read(port_identifier, name, initstr_val):
214250 return default_return_val
215251
216252 s += ins
253+
254+ # Try parsing
217255 try :
218256 inval = literal_eval (ins )
219257 if isinstance (inval , list ) and len (inval ) > 0 :
@@ -230,8 +268,13 @@ def read(port_identifier, name, initstr_val):
230268
231269
232270def write (port_identifier , name , val , delta = 0 ):
271+ """
272+ Write data either to ZMQ port or file.
273+ `val` must be list (with simtime prefix) or string.
274+ """
233275 global simtime
234276
277+ # Case 1: ZMQ port
235278 if isinstance (port_identifier , str ) and port_identifier in zmq_ports :
236279 zmq_p = zmq_ports [port_identifier ]
237280 try :
@@ -240,7 +283,8 @@ def write(port_identifier, name, val, delta=0):
240283 print (f"ZMQ write error on port { port_identifier } (name: { name } ): { e } " )
241284 except Exception as e :
242285 print (f"Unexpected error during ZMQ write on port { port_identifier } (name: { name } ): { e } " )
243-
286+
287+ # Case 2: File-based port
244288 try :
245289 if isinstance (port_identifier , str ) and port_identifier in zmq_ports :
246290 file_path = os .path .join ("../" + port_identifier , name )
@@ -251,8 +295,9 @@ def write(port_identifier, name, val, delta=0):
251295 print (f"Error: Invalid port identifier '{ port_identifier } ' for file operation. Must be integer or ZMQ name." )
252296 return
253297
298+ # File writing rules
254299 if isinstance (val , str ):
255- time .sleep (2 * delay )
300+ time .sleep (2 * delay ) # string writes wait longer
256301 elif not isinstance (val , list ):
257302 print (f"File write to { file_path } must have list or str value, got { type (val )} " )
258303 return
@@ -269,6 +314,10 @@ def write(port_identifier, name, val, delta=0):
269314 print (f"Error writing to { file_path } : { e } " )
270315
271316def initval (simtime_val_str ):
317+ """
318+ Initialize simtime from string containing a list.
319+ Example: "[10, 'foo', 'bar']" → simtime=10, returns ['foo','bar']
320+ """
272321 global simtime
273322 try :
274323 val = literal_eval (simtime_val_str )
0 commit comments