pyconstrobe 1.0.2__tar.gz → 1.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyconstrobe
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: This package is used to automate the DES application ConStrobe.
5
5
  Author: Joseph Louis
6
6
  Author-email: joseph.louis@oregonstate.edu
@@ -15,8 +15,7 @@ Dynamic: license-file
15
15
  Dynamic: requires-python
16
16
  Dynamic: summary
17
17
 
18
- This package works when jStrobe is installed at c:/Program Files/jStrobe/jStrobe/jStrobe.exe
18
+ This package works when ConStrobe is installed at C:/Program Files/ConStrobe/ConStrobe.exe
19
19
 
20
- Download example files from https://github.com/jlouis2k4/TestPythonJS
21
20
 
22
21
 
@@ -0,0 +1,4 @@
1
+ This package works when ConStrobe is installed at C:/Program Files/ConStrobe/ConStrobe.exe
2
+
3
+
4
+
@@ -0,0 +1,8 @@
1
+ # Importing the ProcessManager class for easy access
2
+ from .process_manager import ProcessManager
3
+ from .hlaconstrobe import run_federation, send_control, request_federation_shutdown
4
+
5
+
6
+ # You can add any other initializations or imports here
7
+ __version__ = "1.0.4"
8
+ __all__ = ['ProcessManager', 'run_federation','send_control', 'request_federation_shutdown']
@@ -0,0 +1,235 @@
1
+ import subprocess
2
+ import threading
3
+ import time
4
+ import os
5
+ import platform
6
+ from pyconstrobe import ProcessManager
7
+ from pathlib import Path
8
+ FEDERATION_NAME = "SimulationFederation"
9
+ rtig_proc = None
10
+ listener_proc = None
11
+ listener_thread = None
12
+ shutdown_now = threading.Event()
13
+ fedhandles = {}
14
+ ending_federates = set()
15
+ federation_ending = False
16
+ build_mode = os.getenv('BUILD_MODE', 'Release')
17
+ system = platform.system()
18
+ listener_write_lock = threading.Lock()
19
+ if system == "Windows":
20
+ RUNTIME_DIR = Path(r"C:/Program Files/constrobe")
21
+ if build_mode == "Debug":
22
+ LISTENER_PATH = Path(r"C:/dev/tools/certi/ListenerFederate/x64/Debug/ListenerFederate.exe")
23
+ else:
24
+ LISTENER_PATH = RUNTIME_DIR / "ListenerFederate.exe"
25
+ RTIG_PATH = RUNTIME_DIR / "rtig.exe"
26
+ FOM_DIR = RUNTIME_DIR
27
+ elif system == "Linux":
28
+ RUNTIME_DIR = Path.home() / "dev" / "constrobe-runtime"
29
+ if build_mode == "Debug":
30
+ LISTENER_PATH = (Path.home() / "dev/tools/certi/ListenerFederate/build-linux/ListenerFederate")
31
+ else:
32
+ LISTENER_PATH = RUNTIME_DIR / "bin" / "ListenerFederate"
33
+ RTIG_PATH = RUNTIME_DIR / "bin" / "rtig"
34
+ FOM_DIR = RUNTIME_DIR / "fom"
35
+ else:
36
+ raise RuntimeError(f"Unsupported operating system: {system}")
37
+
38
+ def start_rtig():
39
+ global rtig_proc
40
+ print("Starting rtig...")
41
+ if system == "Windows":
42
+ subprocess.run(
43
+ ["taskkill", "/F", "/IM", "rtig.exe"],
44
+ stdout=subprocess.DEVNULL,
45
+ stderr=subprocess.DEVNULL
46
+ )
47
+ appdata = Path(os.environ["LOCALAPPDATA"]) / "ConStrobe"
48
+ elif system == "Linux":
49
+ subprocess.run(
50
+ ["pkill", "-x", "rtig"],
51
+ stdout=subprocess.DEVNULL,
52
+ stderr=subprocess.DEVNULL
53
+ )
54
+ appdata = Path.home() / ".local" / "share" / "ConStrobe"
55
+ appdata.mkdir(parents=True, exist_ok=True)
56
+ rtig_proc = subprocess.Popen(
57
+ [str(RTIG_PATH), "-v", "2", "-l", "127.0.0.1"],
58
+ cwd=appdata,
59
+ stdout=subprocess.PIPE,
60
+ stderr=subprocess.STDOUT,
61
+ text=True,
62
+ encoding="utf-8",
63
+ bufsize=1
64
+ )
65
+ while True:
66
+ line = rtig_proc.stdout.readline()
67
+ if not line:
68
+ raise RuntimeError("RTIG exited before becoming ready.")
69
+ print("[RTIG]", line.rstrip())
70
+ if "RTIG up and running" in line:
71
+ break
72
+ if (
73
+ "aborted" in line
74
+ or "exiting" in line
75
+ or "Network Error" in line
76
+ ):
77
+ raise RuntimeError(
78
+ "RTIG failed to start (see output above)."
79
+ )
80
+
81
+ def launch_listener(num_feds,handle_new_line,logging = True):
82
+ global listener_proc, listener_thread
83
+ listener_proc = subprocess.Popen(
84
+ [
85
+ str(LISTENER_PATH),
86
+ str(num_feds),
87
+ ],
88
+ cwd=FOM_DIR,
89
+ stdin=subprocess.PIPE,
90
+ stdout=subprocess.PIPE,
91
+ stderr=subprocess.STDOUT,
92
+ text=True,
93
+ encoding="utf-8",
94
+ bufsize=1,
95
+ )
96
+ listener_thread = threading.Thread(target=read_stdout, args=(listener_proc, handle_new_line, logging), daemon=True)
97
+ listener_thread.start()
98
+
99
+ def read_stdout(proc, callback, logging):
100
+ for line in proc.stdout:
101
+ if not line:
102
+ continue
103
+ line = line.strip()
104
+ if line.startswith("[DEBUG]"):
105
+ print(line)
106
+ continue
107
+ is_hla_message = (line.startswith("[HLACOMMUNICATION]") or line.startswith("[HLACOORDINATION]"))
108
+ if not is_hla_message:
109
+ if logging:
110
+ print(line)
111
+ continue
112
+ line = line.split("]", 1)[1].strip()
113
+
114
+ if "|" not in line:
115
+ continue
116
+ fed, msg = line.split("|", 1)
117
+ fed = fed.strip()
118
+ msg = msg.strip()
119
+ if msg == "ENDFEDERATION":
120
+ callback(fed, "ENDFEDERATION Requested. Proceeding to do so...")
121
+ request_federation_shutdown()
122
+ continue
123
+ if msg == "ENDFEDERATE":
124
+ callback(fed, "ENDFEDERATE Requested. Proceeding to do so...")
125
+ request_federate_shutdown(fed)
126
+ continue
127
+ callback(fed, msg)
128
+
129
+
130
+ def send_control(line):
131
+ try:
132
+ with listener_write_lock:
133
+ if listener_proc and listener_proc.stdin:
134
+ listener_proc.stdin.write(line + "\n")
135
+ listener_proc.stdin.flush()
136
+ except Exception as e:
137
+ print(f"send_control failed: {e}")
138
+ def start_simulation(fedName, modelName,pythoncallback=None,startupCommands= None, logging=None):
139
+ manager = ProcessManager(pythoncallback,fedName,logging)
140
+
141
+ full_path=os.path.join(os.getcwd(),modelName)
142
+
143
+ message = f"LOAD {full_path};"
144
+ manager.write_message(message)
145
+ if startupCommands is not None:
146
+ for command in startupCommands:
147
+ manager.write_message(command)
148
+ message = f"STARTFEDERATE {fedName};"
149
+ manager.write_message(message)
150
+ return manager
151
+ def run_federation(simfederates,listenerCallback,logging=True):
152
+ global federation_ending
153
+ shutdown_now.clear()
154
+ fedhandles.clear()
155
+ ending_federates.clear()
156
+ federation_ending = False
157
+ start_rtig()
158
+ launch_listener(len(simfederates),listenerCallback, logging)
159
+ for fedName, modelName, callback, startupCommands in simfederates:
160
+ if fedName.upper() == "UNITY":
161
+ print("Unity federate expected externally. Start it manually from Unity.")
162
+ continue
163
+ h = start_simulation(fedName, modelName, callback, startupCommands, logging)
164
+ h.write_message(f"RUNMODEL;")
165
+ fedhandles[fedName] = h
166
+ try:
167
+ while not shutdown_now.is_set():
168
+ check_all_federates_closed()
169
+ time.sleep(0.1)
170
+ finally:
171
+ print("Stop federation: Initiated")
172
+ stop_federation()
173
+ print("Stop federation: Success")
174
+ def request_federate_shutdown(fedName):
175
+ print(f"Stop federate: Requested for {fedName}")
176
+ if fedName in ending_federates:
177
+ print(f"Stop federate: {fedName} already ending")
178
+ return
179
+ h = fedhandles.get(fedName)
180
+ if h is not None:
181
+ ending_federates.add(fedName)
182
+ send_control(f"CONTROLSIGNAL {fedName} ENDFEDERATE")
183
+ else:
184
+ print(f"Stop federate: No Python handle found for {fedName}")
185
+ def federate_closed(fedName):
186
+ print(f"Stop federate: Closed {fedName}")
187
+
188
+ fedhandles.pop(fedName, None)
189
+ ending_federates.discard(fedName)
190
+
191
+ if not fedhandles:
192
+ print("All federates closed.")
193
+ shutdown_now.set()
194
+ def check_all_federates_closed():
195
+ if fedhandles and all(h.closedFlag for h in fedhandles.values()):
196
+ print("All federates closed.")
197
+ fedhandles.clear()
198
+ ending_federates.clear()
199
+ shutdown_now.set()
200
+ def request_federation_shutdown():
201
+ global federation_ending
202
+ if federation_ending:
203
+ print("Stop federation: Already requested")
204
+ return
205
+ federation_ending = True
206
+ print("Stop federation: Requested")
207
+ for fedName in list(fedhandles.keys()):
208
+ request_federate_shutdown(fedName)
209
+ #shutdown_now.set()
210
+ def stop_federation():
211
+ global listener_proc, listener_thread, rtig_proc
212
+ if listener_proc is not None:
213
+ try:
214
+ listener_proc.terminate()
215
+ listener_proc.wait(timeout=2)
216
+ except subprocess.TimeoutExpired:
217
+ listener_proc.kill()
218
+ listener_proc.wait()
219
+ except Exception:
220
+ pass
221
+ listener_proc = None
222
+ if listener_thread is not None:
223
+ try:
224
+ listener_thread.join(timeout=2)
225
+ except Exception:
226
+ pass
227
+
228
+ if rtig_proc is not None:
229
+ rtig_proc.terminate()
230
+ try:
231
+ rtig_proc.wait(timeout=2)
232
+ except subprocess.TimeoutExpired:
233
+ rtig_proc.kill()
234
+ rtig_proc.wait()
235
+ rtig_proc = None
@@ -1,12 +1,11 @@
1
1
  import subprocess
2
2
  import threading
3
3
  import os
4
+ import platform
4
5
  import sys
5
6
  from pathlib import Path
6
7
  import time
7
8
 
8
- INSTALL_DIR = Path(r"C:\Program Files\ConStrobe")
9
-
10
9
  def debug(msg,senderName = "Sim"):
11
10
  build_mode = os.getenv('BUILD_MODE', 'Release')
12
11
  if build_mode=='Debug':
@@ -19,37 +18,53 @@ class ProcessManager:
19
18
  self.callback = callback
20
19
  self.logging = logging
21
20
  build_mode = os.getenv('BUILD_MODE', 'Release') # Default to 'release' if not set
22
- if build_mode=='Debug':
23
- path_to_exe = r"C:\\dev\\source\\constrobe\\csApp\\Debug\\constrobe.exe"
21
+ system = platform.system()
22
+ if system == "Windows":
23
+ if build_mode=='Debug':
24
+ path_to_exe = r"C:\\dev\\source\\constrobe\\csApp\\Debug\\constrobe.exe"
25
+ else:
26
+ path_to_exe = r"C:\\Program Files\\constrobe\\constrobe.exe"
27
+
28
+ #comment this
29
+ #path_to_exe = r"C:\\dev\\source\\constrobe\\csApp\\Release\\constrobe.exe"
30
+ creationflags = subprocess.CREATE_NEW_CONSOLE
31
+ elif system == "Linux":
32
+ if build_mode == "Debug":
33
+ path_to_exe = os.path.expanduser("~/dev/source/constrobe/build-linux/csApp")
34
+ else:
35
+ path_to_exe = os.path.expanduser("~/dev/constrobe-runtime/bin/ConStrobe")
36
+ creationflags = 0
24
37
  else:
25
- path_to_exe = r"C:\\Program Files\\constrobe\\constrobe.exe"
26
- #path_to_exe = r"C:\\dev\\source\\constrobe\\deploy\\constrobe.exe"
38
+ raise RuntimeError(f"Unsupported operating system: {system}")
39
+
27
40
  self.process = subprocess.Popen(
28
41
  [path_to_exe, '--from-python'],
29
42
  stdin=subprocess.PIPE,
30
43
  stdout=subprocess.PIPE,
31
44
  stderr=subprocess.STDOUT,#PIPE,
32
45
  text=True,
33
- creationflags=subprocess.CREATE_NEW_CONSOLE
46
+ creationflags=creationflags
34
47
  )
35
48
  self.keep_reading = True # Flag to control the reading loop
36
49
  self.finishRunFlag = False
37
50
  self.gotTraceFlag = False
38
51
  self.gotResultsFlag = False
52
+ self.closedFlag = False
39
53
  self.reader_thread = threading.Thread(target=self.read_messages)
40
54
  self.reader_thread.daemon = True # Allow thread to exit when main program exits
41
55
  self.reader_thread.start()
42
56
  def write_message(self, message):
43
57
  #if self.logging:
44
- # debug(f"Sending message: {message}",self.name)
58
+ #print(f"Sending message: {message}",self.name)
45
59
  self.process.stdin.write(message + "\n")
46
60
  self.process.stdin.flush()
47
61
  def read_messages(self):
48
62
  while self.keep_reading:
49
63
  response = self.process.stdout.readline().strip()
50
64
  #if self.logging:
51
- # debug(f"Received message: {response}",self.name)
65
+ #print(f"Received message: {response}",self.name)
52
66
  if response:
67
+ #print(response)
53
68
  parts = response.split(" ",1)
54
69
  type = parts[0]
55
70
  message = parts[1] if len(parts)>1 else ""
@@ -57,6 +72,7 @@ class ProcessManager:
57
72
  self.finishRunFlag = True
58
73
  elif type == "CLOSED_CONSTROBE":
59
74
  self.keep_reading = False # Stop reading messages
75
+ self.closedFlag = True
60
76
  break
61
77
  elif self.callback:
62
78
  if type == "TRACE":
@@ -70,64 +86,40 @@ class ProcessManager:
70
86
  if type == "GET":
71
87
  response_message = f"RESPONSE_TO_GET {response_value}"
72
88
  self.write_message(response_message)
73
- def cleanup(self):
74
- print("Cleaning up resources...")
75
- self.keep_reading = False # Signal the thread to stop reading
76
- self.running = False
77
- self.reader_thread.join(timeout = 2) # Wait for the thread to finish
78
- self.process.stdin.close()
79
- self.process.stdout.close()
80
- self.process.stderr.close()
81
- if self.process:
82
- self.process.terminate() # Try to terminate the process gracefully
83
- try:
84
- return self.process.wait(timeout=5) # Wait up to 5 seconds for it to finish
85
- except subprocess.TimeoutExpired:
86
- debug("Process did not terminate, force killing it...",self.name)
87
- self.process.kill() # Force kill the process if it doesn't terminate in time
88
- return self.process.wait() # Ensure it finishes
89
- def stop(self, graceful=True, timeout=5):
90
- """Stop the process and cleanup."""
89
+ def stop(self, send_close=True, timeout=5):
91
90
  self.keep_reading = False
92
-
93
91
  if not getattr(self, "process", None):
94
92
  return
95
-
96
- # If already stopped, nothing to do
97
- if self.process.poll() is not None:
98
- print(f"[{self.name}] Process already stopped.")
99
- return
100
-
101
- if graceful:
93
+ if send_close and self.process.poll() is None:
102
94
  try:
103
95
  if self.process.stdin:
104
- self.process.stdin.write("EXIT\n")
96
+ self.process.stdin.write("CLOSE;\n")
105
97
  self.process.stdin.flush()
106
- # closing stdin often helps the child exit cleanly
107
- try:
108
- self.process.stdin.close()
109
- except Exception:
110
- pass
111
98
  except Exception:
112
99
  pass
113
-
114
- # Ask OS to terminate
115
- try:
116
- self.process.terminate()
117
- except Exception:
118
- pass
119
-
120
- # Wait, then force kill if needed
121
100
  try:
122
- self.process.wait(timeout=timeout)
101
+ if self.process.poll() is None:
102
+ self.process.terminate()
103
+ self.process.wait(timeout=timeout)
123
104
  except subprocess.TimeoutExpired:
124
105
  try:
125
106
  self.process.kill()
107
+ self.process.wait()
126
108
  except Exception:
127
109
  pass
110
+ except Exception:
111
+ pass
112
+ try:
113
+ self.reader_thread.join(timeout=2)
114
+ except Exception:
115
+ pass
116
+ for pipe in (
117
+ self.process.stdin,
118
+ self.process.stdout,
119
+ self.process.stderr,
120
+ ):
128
121
  try:
129
- self.process.wait()
122
+ if pipe:
123
+ pipe.close()
130
124
  except Exception:
131
- pass
132
-
133
- print(f"[{self.name}] Process stopped.")
125
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyconstrobe
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: This package is used to automate the DES application ConStrobe.
5
5
  Author: Joseph Louis
6
6
  Author-email: joseph.louis@oregonstate.edu
@@ -15,8 +15,7 @@ Dynamic: license-file
15
15
  Dynamic: requires-python
16
16
  Dynamic: summary
17
17
 
18
- This package works when jStrobe is installed at c:/Program Files/jStrobe/jStrobe/jStrobe.exe
18
+ This package works when ConStrobe is installed at C:/Program Files/ConStrobe/ConStrobe.exe
19
19
 
20
- Download example files from https://github.com/jlouis2k4/TestPythonJS
21
20
 
22
21
 
@@ -1,5 +0,0 @@
1
- This package works when jStrobe is installed at c:/Program Files/jStrobe/jStrobe/jStrobe.exe
2
-
3
- Download example files from https://github.com/jlouis2k4/TestPythonJS
4
-
5
-
@@ -1,8 +0,0 @@
1
- # Importing the ProcessManager class for easy access
2
- from .process_manager import ProcessManager
3
- from .hlaconstrobe import run_federation, send_control, graceful_shutdown, get_results
4
-
5
-
6
- # You can add any other initializations or imports here
7
- __version__ = "1.0.2"
8
- __all__ = ['ProcessManager', 'run_federation','send_control', 'graceful_shutdown', 'get_results']
@@ -1,144 +0,0 @@
1
- import subprocess
2
- import threading
3
- import time
4
- import os
5
- import signal
6
- import queue
7
- import json
8
- import sys
9
- from pyconstrobe import ProcessManager
10
-
11
- FEDERATION_NAME = "SimulationFederation"
12
- rtig_proc = None
13
- listener_proc = None
14
- listener_thread = None
15
- shutdown_now = threading.Event()
16
- fedhandles = {}
17
- build_mode = os.getenv('BUILD_MODE', 'Release')
18
- if build_mode=='Debug':
19
- LISTENER_PATH = "C:\\dev\\tools\\certi\\ListenerFederate\\x64\\Debug\\ListenerFederate.exe"
20
- else:
21
- LISTENER_PATH = "C:\\Program Files\\constrobe\\ListenerFederate.exe";
22
- #LISTENER_PATH = "C:\\dev\\source\\constrobe\\deploy\\ListenerFederate.exe"
23
- def start_rtig():
24
- global rtig_proc
25
- print("Starting rtig...")
26
- rtig_path = "C:\\Program Files\\constrobe\\rtig.exe"
27
- subprocess.run(
28
- ["taskkill", "/F", "/IM", "rtig.exe"],
29
- stdout=subprocess.DEVNULL,
30
- stderr=subprocess.DEVNULL
31
- )
32
- time.sleep(0.5)
33
- rtig_proc = subprocess.Popen(
34
- [rtig_path, "-v", "2", "-l", "127.0.0.1"],
35
- stdout=subprocess.PIPE,
36
- stderr=subprocess.STDOUT,
37
- text=True,
38
- encoding="utf-8",
39
- bufsize=1
40
- )
41
- while True:
42
- line = rtig_proc.stdout.readline()
43
- if not line:
44
- raise RuntimeError("RTIG exited before becoming ready.")
45
- print("[RTIG]", line.rstrip())
46
- if "RTIG up and running" in line:
47
- break
48
- if ("aborted" in line or
49
- "exiting" in line or
50
- "Network Error" in line):
51
- raise RuntimeError("RTIG failed to start (see output above).")
52
- def launch_listener(num_feds,handle_new_line,logging = True):
53
- global listener_proc, listener_thread
54
- listener_proc = subprocess.Popen(
55
- [LISTENER_PATH, str(num_feds),
56
- "true" if logging else "false"],
57
- stdin=subprocess.PIPE,
58
- stdout=subprocess.PIPE,
59
- stderr=subprocess.STDOUT,
60
- text=True,
61
- encoding="utf-8",
62
- bufsize=1,
63
- )
64
- listener_thread = threading.Thread(target=read_stdout, args=(listener_proc, handle_new_line, logging), daemon=True)
65
- listener_thread.start()
66
- def read_stdout(proc, callback, logging):
67
- for line in proc.stdout:
68
- if line:
69
- if "|" in line:
70
- print("[HLA]",line.strip())
71
- fed, msg = line.split("|", 1)
72
- callback(fed,msg.strip())
73
- else:
74
- if logging:
75
- print("[LOG]",line.strip())
76
- def send_control(line):
77
- try:
78
- if listener_proc.stdin:
79
- listener_proc.stdin.write(line + "\n")
80
- listener_proc.stdin.flush()
81
- except Exception:
82
- pass
83
-
84
- def startSimulation(fedName, modelName,pythoncallback=None,logging=None):
85
- manager = ProcessManager(pythoncallback,fedName,logging)
86
- full_path=os.path.join(os.getcwd(),modelName)
87
- message = f"LOAD {full_path};"
88
- manager.write_message(message)
89
- message = f"STARTFEDERATE {fedName};"
90
- manager.write_message(message)
91
- return manager
92
- def run_federation(simfederates,listenerCallback,logging=True):
93
- start_rtig()
94
- launch_listener(len(simfederates),listenerCallback, logging)
95
- for fedName, modelName, callback in simfederates:
96
- h = startSimulation(fedName, modelName, callback, logging)
97
- message = f"RUNMODEL;"
98
- h.write_message(message)
99
- fedhandles[fedName] = h
100
- try:
101
- while not shutdown_now.is_set():
102
- time.sleep(0.1)
103
- finally:
104
- print("All processes stopped. Exiting...")
105
- def get_results():
106
- for fedName, h in fedhandles.items():
107
- h.write_message("GETRESULTS;")
108
- def graceful_shutdown():
109
- print("Graceful shutdown…")
110
- send_control("CONTROLSIGNAL ALL ENDFEDERATE")
111
- for h in fedhandles.values():
112
- try:
113
- h.write_message("CLOSE;")
114
- except Exception:
115
- pass
116
- time.sleep(2)
117
- for h in fedhandles.values():
118
- try:
119
- h.stop()
120
- except Exception:
121
- pass
122
- global listener_proc
123
- if listener_proc is not None:
124
- try:
125
- listener_proc.terminate()
126
- listener_proc.wait(timeout=2)
127
- except subprocess.TimeoutExpired:
128
- listener_proc.kill()
129
- listener_proc.wait()
130
- except Exception:
131
- pass
132
- listener_proc = None
133
- global rtig_proc
134
- if rtig_proc is not None:
135
- rtig_proc.terminate()
136
- try:
137
- rtig_proc.wait(timeout=2)
138
- except subprocess.TimeoutExpired:
139
- rtig_proc.kill()
140
- rtig_proc.wait()
141
- rtig_proc = None
142
- shutdown_now.set()
143
-
144
-
File without changes
File without changes
File without changes