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.
- {pyconstrobe-1.0.2 → pyconstrobe-1.0.4}/PKG-INFO +2 -3
- pyconstrobe-1.0.4/README.md +4 -0
- pyconstrobe-1.0.4/pyconstrobe/__init__.py +8 -0
- pyconstrobe-1.0.4/pyconstrobe/hlaconstrobe.py +235 -0
- {pyconstrobe-1.0.2 → pyconstrobe-1.0.4}/pyconstrobe/process_manager.py +46 -54
- {pyconstrobe-1.0.2 → pyconstrobe-1.0.4}/pyconstrobe.egg-info/PKG-INFO +2 -3
- pyconstrobe-1.0.2/README.md +0 -5
- pyconstrobe-1.0.2/pyconstrobe/__init__.py +0 -8
- pyconstrobe-1.0.2/pyconstrobe/hlaconstrobe.py +0 -144
- {pyconstrobe-1.0.2 → pyconstrobe-1.0.4}/LICENSE +0 -0
- {pyconstrobe-1.0.2 → pyconstrobe-1.0.4}/pyconstrobe.egg-info/SOURCES.txt +0 -0
- {pyconstrobe-1.0.2 → pyconstrobe-1.0.4}/pyconstrobe.egg-info/dependency_links.txt +0 -0
- {pyconstrobe-1.0.2 → pyconstrobe-1.0.4}/pyconstrobe.egg-info/top_level.txt +0 -0
- {pyconstrobe-1.0.2 → pyconstrobe-1.0.4}/setup.cfg +0 -0
- {pyconstrobe-1.0.2 → pyconstrobe-1.0.4}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyconstrobe
|
|
3
|
-
Version: 1.0.
|
|
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
|
|
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,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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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=
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
|
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("
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
pyconstrobe-1.0.2/README.md
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|