pyconstrobe 0.1__tar.gz → 1.0.2__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
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: pyconstrobe
3
- Version: 0.1
3
+ Version: 1.0.2
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
@@ -11,6 +11,7 @@ Dynamic: author
11
11
  Dynamic: author-email
12
12
  Dynamic: description
13
13
  Dynamic: description-content-type
14
+ Dynamic: license-file
14
15
  Dynamic: requires-python
15
16
  Dynamic: summary
16
17
 
@@ -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, 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']
@@ -0,0 +1,144 @@
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
+
@@ -0,0 +1,133 @@
1
+ import subprocess
2
+ import threading
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+ import time
7
+
8
+ INSTALL_DIR = Path(r"C:\Program Files\ConStrobe")
9
+
10
+ def debug(msg,senderName = "Sim"):
11
+ build_mode = os.getenv('BUILD_MODE', 'Release')
12
+ if build_mode=='Debug':
13
+ sys.stderr.write(f"[PYCONSTROBE - {senderName}] {msg}\n")
14
+ sys.stderr.flush()
15
+
16
+ class ProcessManager:
17
+ def __init__(self, callback=None, name = "Sim", logging = True):
18
+ self.name = name
19
+ self.callback = callback
20
+ self.logging = logging
21
+ 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"
24
+ else:
25
+ path_to_exe = r"C:\\Program Files\\constrobe\\constrobe.exe"
26
+ #path_to_exe = r"C:\\dev\\source\\constrobe\\deploy\\constrobe.exe"
27
+ self.process = subprocess.Popen(
28
+ [path_to_exe, '--from-python'],
29
+ stdin=subprocess.PIPE,
30
+ stdout=subprocess.PIPE,
31
+ stderr=subprocess.STDOUT,#PIPE,
32
+ text=True,
33
+ creationflags=subprocess.CREATE_NEW_CONSOLE
34
+ )
35
+ self.keep_reading = True # Flag to control the reading loop
36
+ self.finishRunFlag = False
37
+ self.gotTraceFlag = False
38
+ self.gotResultsFlag = False
39
+ self.reader_thread = threading.Thread(target=self.read_messages)
40
+ self.reader_thread.daemon = True # Allow thread to exit when main program exits
41
+ self.reader_thread.start()
42
+ def write_message(self, message):
43
+ #if self.logging:
44
+ # debug(f"Sending message: {message}",self.name)
45
+ self.process.stdin.write(message + "\n")
46
+ self.process.stdin.flush()
47
+ def read_messages(self):
48
+ while self.keep_reading:
49
+ response = self.process.stdout.readline().strip()
50
+ #if self.logging:
51
+ # debug(f"Received message: {response}",self.name)
52
+ if response:
53
+ parts = response.split(" ",1)
54
+ type = parts[0]
55
+ message = parts[1] if len(parts)>1 else ""
56
+ if type == "FINISHED_RUN":
57
+ self.finishRunFlag = True
58
+ elif type == "CLOSED_CONSTROBE":
59
+ self.keep_reading = False # Stop reading messages
60
+ break
61
+ elif self.callback:
62
+ if type == "TRACE":
63
+ message=message.replace("|","\n")
64
+ self.gotTraceFlag = True
65
+ if type == "RESULTS":
66
+ self.gotResultsFlag = True
67
+
68
+ response_value = self.callback(type,message)
69
+
70
+ if type == "GET":
71
+ response_message = f"RESPONSE_TO_GET {response_value}"
72
+ 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."""
91
+ self.keep_reading = False
92
+
93
+ if not getattr(self, "process", None):
94
+ 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:
102
+ try:
103
+ if self.process.stdin:
104
+ self.process.stdin.write("EXIT\n")
105
+ 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
+ except Exception:
112
+ 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
+ try:
122
+ self.process.wait(timeout=timeout)
123
+ except subprocess.TimeoutExpired:
124
+ try:
125
+ self.process.kill()
126
+ except Exception:
127
+ pass
128
+ try:
129
+ self.process.wait()
130
+ except Exception:
131
+ pass
132
+
133
+ print(f"[{self.name}] Process stopped.")
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: pyconstrobe
3
- Version: 0.1
3
+ Version: 1.0.2
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
@@ -11,6 +11,7 @@ Dynamic: author
11
11
  Dynamic: author-email
12
12
  Dynamic: description
13
13
  Dynamic: description-content-type
14
+ Dynamic: license-file
14
15
  Dynamic: requires-python
15
16
  Dynamic: summary
16
17
 
@@ -1,9 +1,8 @@
1
1
  LICENSE
2
2
  README.md
3
3
  setup.py
4
- csPy/__init__.py
5
- csPy/process_manager.py
6
4
  pyconstrobe/__init__.py
5
+ pyconstrobe/hlaconstrobe.py
7
6
  pyconstrobe/process_manager.py
8
7
  pyconstrobe.egg-info/PKG-INFO
9
8
  pyconstrobe.egg-info/SOURCES.txt
@@ -1,8 +1,15 @@
1
1
  from setuptools import setup, find_packages
2
+ from pathlib import Path
3
+
4
+ def get_version():
5
+ version_file = Path(__file__).parent / "pyconstrobe" / "__init__.py"
6
+ for line in version_file.read_text().splitlines():
7
+ if line.startswith("__version__"):
8
+ return line.split("=")[-1].strip().strip('"').strip("'")
2
9
 
3
10
  setup(
4
11
  name="pyconstrobe", # Replace with your package name
5
- version="0.1", # Version number
12
+ version=get_version(), # Version number
6
13
  packages=find_packages(), # Automatically find and include all packages
7
14
  author="Joseph Louis",
8
15
  author_email="joseph.louis@oregonstate.edu",
@@ -1,6 +0,0 @@
1
- # Importing the ProcessManager class for easy access
2
- from .process_manager import ProcessManager
3
-
4
- # You can add any other initializations or imports here
5
- __version__ = "0.1.0"
6
- __all__ = ['ProcessManager']
@@ -1,67 +0,0 @@
1
- import subprocess
2
- import threading
3
- import os
4
-
5
-
6
-
7
- class ProcessManager:
8
- def __init__(self, callback=None):
9
- build_mode = os.getenv('BUILD_MODE', 'Release') # Default to 'release' if not set
10
- print(build_mode)
11
- if build_mode=='Debug':
12
- path_to_exe = r"C:\source\constrobe\csApp\Debug\constrobe.exe"
13
- else:
14
- path_to_exe = r"C:\Program Files\constrobe\constrobe\constrobe.exe"
15
-
16
-
17
-
18
- self.process = subprocess.Popen(
19
- [path_to_exe, '--from-python'],
20
- stdin=subprocess.PIPE,
21
- stdout=subprocess.PIPE,
22
- stderr=subprocess.PIPE,
23
- text=True,
24
- creationflags=subprocess.CREATE_NEW_CONSOLE
25
- )
26
- self.callback = callback # Custom callback for processing messages
27
- self.keep_reading = True # Flag to control the reading loop
28
- self.reader_thread = threading.Thread(target=self.read_messages)
29
- self.reader_thread.daemon = True # Allow thread to exit when main program exits
30
- self.reader_thread.start()
31
-
32
- def write_message(self, message):
33
- """Write a message to the process's stdin."""
34
- self.process.stdin.write(message + "\n")
35
- self.process.stdin.flush()
36
-
37
- def read_messages(self):
38
- """Read messages from the process's stdout."""
39
- while self.keep_reading:
40
- response = self.process.stdout.readline().strip()
41
- if response:
42
- if response == "COMPLETE":
43
- print("Process completed.")
44
- self.keep_reading = False # Stop reading messages
45
- break
46
- if self.callback:
47
- response_message = self.callback(response)
48
- if response_message:
49
- self.write_message(response_message)
50
-
51
- def cleanup(self):
52
- """Clean up the process and close streams."""
53
- print("Cleaning up resources...")
54
- self.keep_reading = False # Signal the thread to stop reading
55
- self.running = False
56
- self.reader_thread.join() # Wait for the thread to finish
57
- self.process.stdin.close()
58
- self.process.stdout.close()
59
- self.process.stderr.close()
60
- if self.process:
61
- self.process.terminate() # Try to terminate the process gracefully
62
- try:
63
- return self.process.wait(timeout=5) # Wait up to 5 seconds for it to finish
64
- except subprocess.TimeoutExpired:
65
- print("Process did not terminate, force killing it...")
66
- self.process.kill() # Force kill the process if it doesn't terminate in time
67
- return self.process.wait() # Ensure it finishes
@@ -1,6 +0,0 @@
1
- # Importing the ProcessManager class for easy access
2
- from .process_manager import ProcessManager
3
-
4
- # You can add any other initializations or imports here
5
- __version__ = "0.1.0"
6
- __all__ = ['ProcessManager']
@@ -1,67 +0,0 @@
1
- import subprocess
2
- import threading
3
- import os
4
-
5
-
6
-
7
- class ProcessManager:
8
- def __init__(self, callback=None):
9
- build_mode = os.getenv('BUILD_MODE', 'Release') # Default to 'release' if not set
10
- print(build_mode)
11
- if build_mode=='Debug':
12
- path_to_exe = r"C:\source\constrobe\csApp\Debug\constrobe.exe"
13
- else:
14
- path_to_exe = r"C:\Program Files\constrobe\constrobe\constrobe.exe"
15
-
16
-
17
-
18
- self.process = subprocess.Popen(
19
- [path_to_exe, '--from-python'],
20
- stdin=subprocess.PIPE,
21
- stdout=subprocess.PIPE,
22
- stderr=subprocess.PIPE,
23
- text=True,
24
- creationflags=subprocess.CREATE_NEW_CONSOLE
25
- )
26
- self.callback = callback # Custom callback for processing messages
27
- self.keep_reading = True # Flag to control the reading loop
28
- self.reader_thread = threading.Thread(target=self.read_messages)
29
- self.reader_thread.daemon = True # Allow thread to exit when main program exits
30
- self.reader_thread.start()
31
-
32
- def write_message(self, message):
33
- """Write a message to the process's stdin."""
34
- self.process.stdin.write(message + "\n")
35
- self.process.stdin.flush()
36
-
37
- def read_messages(self):
38
- """Read messages from the process's stdout."""
39
- while self.keep_reading:
40
- response = self.process.stdout.readline().strip()
41
- if response:
42
- if response == "COMPLETE":
43
- print("Process completed.")
44
- self.keep_reading = False # Stop reading messages
45
- break
46
- if self.callback:
47
- response_message = self.callback(response)
48
- if response_message:
49
- self.write_message(response_message)
50
-
51
- def cleanup(self):
52
- """Clean up the process and close streams."""
53
- print("Cleaning up resources...")
54
- self.keep_reading = False # Signal the thread to stop reading
55
- self.running = False
56
- self.reader_thread.join() # Wait for the thread to finish
57
- self.process.stdin.close()
58
- self.process.stdout.close()
59
- self.process.stderr.close()
60
- if self.process:
61
- self.process.terminate() # Try to terminate the process gracefully
62
- try:
63
- return self.process.wait(timeout=5) # Wait up to 5 seconds for it to finish
64
- except subprocess.TimeoutExpired:
65
- print("Process did not terminate, force killing it...")
66
- self.process.kill() # Force kill the process if it doesn't terminate in time
67
- return self.process.wait() # Ensure it finishes
File without changes
File without changes
File without changes