quarchpy 2.2.17.dev1__py3-none-any.whl → 2.2.17.dev3__py3-none-any.whl
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.
- quarchpy/__init__.py +57 -49
- quarchpy/_version.py +1 -1
- quarchpy/connection_specific/connection_QIS.py +3 -8
- quarchpy/connection_specific/connection_QPS.py +116 -8
- quarchpy/connection_specific/connection_Telnet.py +19 -5
- quarchpy/connection_specific/jdk_jres/fix_permissions.py +0 -3
- quarchpy/connection_specific/usb_libs/libusb1.py +21 -0
- quarchpy/debug/SystemTest.py +65 -7
- quarchpy/debug/module_debug.py +11 -3
- quarchpy/device/device.py +93 -45
- quarchpy/device/scanDevices.py +55 -24
- quarchpy/qis/qisFuncs.py +236 -127
- quarchpy/qps/qpsFuncs.py +267 -171
- quarchpy/run.py +1 -1
- {quarchpy-2.2.17.dev1.dist-info → quarchpy-2.2.17.dev3.dist-info}/METADATA +5 -1
- {quarchpy-2.2.17.dev1.dist-info → quarchpy-2.2.17.dev3.dist-info}/RECORD +19 -18
- {quarchpy-2.2.17.dev1.dist-info → quarchpy-2.2.17.dev3.dist-info}/WHEEL +1 -1
- quarchpy-2.2.17.dev3.dist-info/licenses/LICENSE.txt +21 -0
- {quarchpy-2.2.17.dev1.dist-info → quarchpy-2.2.17.dev3.dist-info}/top_level.txt +0 -0
quarchpy/qps/qpsFuncs.py
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
from subprocess import CompletedProcess, Popen
|
|
1
3
|
from threading import Thread, Lock, Event
|
|
2
4
|
from queue import Queue, Empty
|
|
3
5
|
import platform
|
|
6
|
+
from typing import Optional, List, Any, Tuple, Union
|
|
7
|
+
|
|
8
|
+
import quarchpy_binaries
|
|
4
9
|
|
|
5
10
|
from quarchpy.install_qps import find_qps
|
|
6
11
|
from quarchpy.qis import isQisRunning, startLocalQis
|
|
7
12
|
from quarchpy.connection_specific.connection_QPS import QpsInterface
|
|
8
13
|
from quarchpy.connection_specific.jdk_jres.fix_permissions import main as fix_permissions, find_java_permissions
|
|
14
|
+
from quarchpy.qis.qisFuncs import isQisRunningAndResponding
|
|
9
15
|
from quarchpy.user_interface import *
|
|
10
16
|
import subprocess
|
|
11
17
|
import logging
|
|
18
|
+
|
|
12
19
|
logger = logging.getLogger(__name__)
|
|
13
20
|
|
|
14
21
|
|
|
@@ -16,7 +23,7 @@ def isQpsRunning(host='127.0.0.1', port=9822, timeout=0):
|
|
|
16
23
|
'''
|
|
17
24
|
This func will return true if QPS is running with a working QIS connection.
|
|
18
25
|
'''
|
|
19
|
-
myQps=None
|
|
26
|
+
myQps = None
|
|
20
27
|
logger.debug("Checking if QPS is running")
|
|
21
28
|
start = time.time()
|
|
22
29
|
while True:
|
|
@@ -32,12 +39,13 @@ def isQpsRunning(host='127.0.0.1', port=9822, timeout=0):
|
|
|
32
39
|
logger.debug("QPS is not running")
|
|
33
40
|
return False
|
|
34
41
|
|
|
35
|
-
logger.debug(
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
logger.debug(
|
|
43
|
+
"Checking if QPS reports a QIS connection") # "$qis status" returns connected if it has ever had a QIS connection.
|
|
44
|
+
answer = 0
|
|
45
|
+
counter = 0
|
|
38
46
|
while True:
|
|
39
47
|
answer = myQps.sendCmdVerbose(cmd="$qis status")
|
|
40
|
-
if answer.lower()=="connected":
|
|
48
|
+
if answer.lower() == "connected":
|
|
41
49
|
logger.debug("QPS Running With QIS Connected")
|
|
42
50
|
break
|
|
43
51
|
else:
|
|
@@ -45,7 +53,7 @@ def isQpsRunning(host='127.0.0.1', port=9822, timeout=0):
|
|
|
45
53
|
time.sleep(0.5)
|
|
46
54
|
counter += 1
|
|
47
55
|
if counter > 5:
|
|
48
|
-
logger.debug("QPS Running QIS NOT found after "+str(counter)+" attempts.")
|
|
56
|
+
logger.debug("QPS Running QIS NOT found after " + str(counter) + " attempts.")
|
|
49
57
|
return False
|
|
50
58
|
|
|
51
59
|
logger.debug("Checking if QPS/QIS comms are running")
|
|
@@ -69,124 +77,88 @@ def isQpsRunning(host='127.0.0.1', port=9822, timeout=0):
|
|
|
69
77
|
return False
|
|
70
78
|
|
|
71
79
|
|
|
72
|
-
def startLocalQps(
|
|
73
|
-
|
|
80
|
+
def startLocalQps(
|
|
81
|
+
keepQisRunning: bool = False,
|
|
82
|
+
args: Optional[List[str]] = [],
|
|
83
|
+
timeout: int = 30,
|
|
84
|
+
startQPSMinimised: bool = True,
|
|
85
|
+
host: str = '127.0.0.1',
|
|
86
|
+
port: int = 9822,
|
|
87
|
+
qis_port: int = 9722,
|
|
88
|
+
qis_rest_port: int = 9780
|
|
89
|
+
) -> Optional['QpsInterface']:
|
|
90
|
+
"""
|
|
91
|
+
Main entry point to start a local QPS instance.
|
|
92
|
+
"""
|
|
93
|
+
# 1. Prepare Arguments
|
|
94
|
+
# We copy the list to avoid modifying the input in place
|
|
95
|
+
launch_args = args.copy()
|
|
96
|
+
if not launch_args:
|
|
97
|
+
launch_args = ""
|
|
98
|
+
|
|
99
|
+
# Sync 'port' arg to CLI flags if non-default
|
|
100
|
+
if port != 9822:
|
|
101
|
+
# Only add if not already manually present in args
|
|
102
|
+
if not any("-port=" in arg for arg in launch_args):
|
|
103
|
+
launch_args.append(f"-port={port}")
|
|
104
|
+
|
|
105
|
+
# Sync 'qisport' arg to CLI flags if non-default
|
|
106
|
+
if qis_port != 9722:
|
|
107
|
+
if not any("-qisport=" in arg for arg in launch_args):
|
|
108
|
+
launch_args.append(f"-qisport={qis_port}")
|
|
109
|
+
|
|
110
|
+
# Sync 'qisrestport' arg to CLI flags if non-default
|
|
111
|
+
if qis_rest_port != 9780:
|
|
112
|
+
if not any("-qisrestport=" in arg for arg in launch_args):
|
|
113
|
+
launch_args.append(f"-qisrestport={qis_port}")
|
|
114
|
+
|
|
115
|
+
# 2. Check if already running on the specific target port
|
|
116
|
+
if _check_port_open(host, port):
|
|
117
|
+
logger.debug(f"QPS instance on port {port} is already running. Connecting...")
|
|
118
|
+
return QpsInterface(host=host, port=port)
|
|
119
|
+
|
|
120
|
+
# 3. Check for QPS installation
|
|
74
121
|
if not find_qps():
|
|
75
122
|
logger.error("Unable to find or install QPS... Aborting...")
|
|
76
|
-
return
|
|
123
|
+
return None
|
|
77
124
|
|
|
125
|
+
# 4. Handle QIS Backend (if required)
|
|
78
126
|
if keepQisRunning:
|
|
79
|
-
if not
|
|
80
|
-
|
|
127
|
+
if not _ensure_qis_running(host, qis_port, qis_rest_port, timeout):
|
|
128
|
+
return None
|
|
81
129
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if startQPSMinimised == True:
|
|
87
|
-
if "-ccs" not in args.lower():
|
|
88
|
-
args +=" -ccs=MIN"
|
|
130
|
+
# 5. Prepare Command and Environment
|
|
131
|
+
command, qps_dir = _prepare_qps_launch_env(launch_args, startQPSMinimised)
|
|
132
|
+
if not command:
|
|
133
|
+
return None
|
|
89
134
|
|
|
90
|
-
#
|
|
135
|
+
# 6. Launch QPS Process
|
|
91
136
|
current_dir = os.getcwd()
|
|
92
137
|
|
|
93
|
-
# JRE path
|
|
94
|
-
java_path = os.path.dirname(os.path.abspath(__file__))
|
|
95
|
-
java_path, junk = os.path.split(java_path)
|
|
96
|
-
java_path = os.path.join(java_path, "connection_specific", "jdk_jres")
|
|
97
|
-
java_path = "\"" + java_path
|
|
98
|
-
# Start to build the path towards qps.jar
|
|
99
|
-
qps_path = os.path.dirname(os.path.abspath(__file__))
|
|
100
|
-
qps_path, junk = os.path.split(qps_path)
|
|
101
|
-
|
|
102
|
-
# Check the current OS
|
|
103
|
-
current_os = platform.system()
|
|
104
|
-
current_arch = platform.machine()
|
|
105
|
-
current_arch = current_arch.lower() # ensure comparing same case
|
|
106
|
-
|
|
107
|
-
# Currently officially unsupported
|
|
108
|
-
if (current_os in "Linux" and current_arch == "aarch64") or (current_os in "Darwin" and current_arch == "arm64"):
|
|
109
|
-
logger.warning("The system [" + current_os + ", " + current_arch + "] is not officially supported.")
|
|
110
|
-
logger.warning("Please contact Quarch support for running QuarchPy on this system.")
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
# ensure the jres folder has the required permissions
|
|
114
|
-
permissions, message = find_java_permissions()
|
|
115
|
-
if permissions is False:
|
|
116
|
-
logger.warning(message)
|
|
117
|
-
logger.warning("Not having correct permissions will prevent Quarch Java Programs from launching.")
|
|
118
|
-
logger.warning("Run \"python -m quarchpy.run permission_fix\" to fix this.")
|
|
119
|
-
user_input = input("Would you like to use auto run this now? (Y/N)")
|
|
120
|
-
if user_input.lower() == "y":
|
|
121
|
-
fix_permissions()
|
|
122
|
-
permissions, message = find_java_permissions()
|
|
123
|
-
time.sleep(0.5)
|
|
124
|
-
if permissions is False:
|
|
125
|
-
logger.warning("Attempt to fix permissions was unsuccessful. Please fix manually.")
|
|
126
|
-
else:
|
|
127
|
-
logger.warning("Attempt to fix permissions was successful. Now continuing.")
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
qps_path = os.path.join(qps_path, "connection_specific", "QPS", "qps.jar")
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
# Change the working directory to the directory containing qps.jar
|
|
134
|
-
os.chdir(os.path.dirname(qps_path))
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
# OS dependency
|
|
138
|
-
if current_os in "Windows":
|
|
139
|
-
command = java_path + "\\win_amd64_jdk_jre\\bin\\java\" -jar qps.jar " + str(args)
|
|
140
|
-
elif current_os in "Linux" and current_arch == "x86_64":
|
|
141
|
-
command = java_path + "/lin_amd64_jdk_jre/bin/java\" -jar qps.jar " + str(args)
|
|
142
|
-
elif current_os in "Linux" and current_arch == "aarch64":
|
|
143
|
-
command = java_path + "/lin_arm64_jdk_jre/bin/java\" -jar qps.jar " + str(args)
|
|
144
|
-
elif current_os in "Darwin" and current_arch == "x86_64":
|
|
145
|
-
command = java_path + "/mac_amd64_jdk_jre/bin/java\" -jar qps.jar " + str(args)
|
|
146
|
-
elif current_os in "Darwin" and current_arch == "arm64":
|
|
147
|
-
command = java_path + "/mac_arm64_jdk_jre/bin/java\" -jar qps.jar " + str(args)
|
|
148
|
-
else: # default to windows
|
|
149
|
-
command = java_path + "\\win_amd64_jdk_jre\\bin\\java\" -jar qps.jar " + str(args)
|
|
150
|
-
|
|
151
138
|
if isQpsRunning():
|
|
152
139
|
logger.debug("QPS is already running. Not starting another instance.")
|
|
153
140
|
os.chdir(current_dir)
|
|
154
|
-
return
|
|
155
|
-
if "-logging=ON" in str(args): #If logging to a terminal window is on then os.system should be used to keep a window open to view logging.
|
|
156
|
-
if current_os in "Windows":
|
|
157
|
-
process = subprocess.Popen(command,shell=True)
|
|
158
|
-
else:
|
|
159
|
-
# Add a hold command to keep the terminal open (useful for bash)
|
|
160
|
-
command_with_pause = command + "; exec bash"
|
|
161
|
-
process = subprocess.run(command_with_pause, shell=True)
|
|
162
|
-
else:
|
|
163
|
-
if sys.version_info[0] < 3:
|
|
164
|
-
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
|
165
|
-
else:
|
|
166
|
-
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True)
|
|
141
|
+
return None
|
|
167
142
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
os.chdir(current_dir)
|
|
174
|
-
raise TimeoutError("QPS failed to launch within timelimit of " + str(timeout) + " sec.")
|
|
175
|
-
logger.debug("QPS detected after " + str(time.time() - startTime) + "s")
|
|
143
|
+
try:
|
|
144
|
+
os.chdir(qps_dir) # Switch to QPS dir for launch dependencies
|
|
145
|
+
process = _launch_process(command, args)
|
|
146
|
+
finally:
|
|
147
|
+
os.chdir(current_dir) # Always return to original dir
|
|
176
148
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
"QPS did launch but QIS did not respond during the timeout time of " + str(timeout) + " sec.")
|
|
181
|
-
time.sleep(0.2)
|
|
182
|
-
logger.debug("QIS detected after " + str(time.time() - startTime) + "s")
|
|
149
|
+
# 7. Wait for QPS to be ready
|
|
150
|
+
if not _wait_for_service(host, port, timeout, process, args):
|
|
151
|
+
return None
|
|
183
152
|
|
|
184
|
-
#
|
|
185
|
-
|
|
186
|
-
|
|
153
|
+
# 8. Return Connected Interface
|
|
154
|
+
try:
|
|
155
|
+
return QpsInterface(host=host, port=port)
|
|
156
|
+
except Exception as e:
|
|
157
|
+
logger.error(f"QPS started, but failed to create interface object: {e}")
|
|
158
|
+
return None
|
|
187
159
|
|
|
188
160
|
|
|
189
|
-
def reader(stream, q, source, lock,stop_flag):
|
|
161
|
+
def reader(stream, q, source, lock, stop_flag):
|
|
190
162
|
'''
|
|
191
163
|
Used to read output and place it in a queue for multithreaded reading
|
|
192
164
|
:param stream:
|
|
@@ -222,7 +194,7 @@ def _get_std_msg_and_err_from_QPS_process(process):
|
|
|
222
194
|
t2.start()
|
|
223
195
|
counter = 0
|
|
224
196
|
# check for stderr or stdmsg from the queue
|
|
225
|
-
while counter <= 3:
|
|
197
|
+
while counter <= 3: # If 3 empty reads from the queue then move on to see if QPS is running.
|
|
226
198
|
try:
|
|
227
199
|
source, line = q.get(timeout=1) # Wait for 1 second for new lines
|
|
228
200
|
counter = 0
|
|
@@ -232,73 +204,197 @@ def _get_std_msg_and_err_from_QPS_process(process):
|
|
|
232
204
|
printText(f"{source}: {line}")
|
|
233
205
|
except Empty:
|
|
234
206
|
counter += 1
|
|
235
|
-
stop_flag.set()
|
|
207
|
+
stop_flag.set() #Close the threads and return to the main loop where QPS is check to see if its started yet
|
|
236
208
|
|
|
237
209
|
|
|
238
210
|
def closeQps(host='127.0.0.1', port=9822):
|
|
239
211
|
myQps = QpsInterface(host, port)
|
|
240
212
|
myQps.sendCmdVerbose("$shutdown")
|
|
241
213
|
del myQps
|
|
242
|
-
time.sleep(
|
|
214
|
+
time.sleep(
|
|
215
|
+
1) #needed as calling "isQpsRunning()" will throw an error if it ties to connect while shutdown is in progress.
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def GetQpsModuleSelection(QpsConnection: 'QpsInterface', favouriteOnly=True,
|
|
219
|
+
additionalOptions=['rescan', 'all con types', 'ip scan'], scan=True):
|
|
220
|
+
"""
|
|
221
|
+
Deprecated: use QpsInterface.get_module_selection instead.
|
|
222
|
+
This function will return a module selection list from QPS.
|
|
223
|
+
|
|
224
|
+
"""
|
|
225
|
+
return (
|
|
226
|
+
QpsConnection.get_qps_module_selection(preferred_connection_only=favouriteOnly,
|
|
227
|
+
additional_options=additionalOptions, scan=scan)
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# ==========================================
|
|
232
|
+
# HELPER FUNCTIONS
|
|
233
|
+
# ==========================================
|
|
234
|
+
|
|
235
|
+
def _parse_ports(args: List[str]) -> Tuple[int, int, int]:
|
|
236
|
+
"""Extracts QPS and QIS ports from the argument list."""
|
|
237
|
+
qps_port = 9822
|
|
238
|
+
qis_port = 9722
|
|
239
|
+
qis_rest_port = 9780
|
|
240
|
+
|
|
241
|
+
for arg in args:
|
|
242
|
+
arg_lower = arg.lower()
|
|
243
|
+
if "-port=" in arg_lower:
|
|
244
|
+
try:
|
|
245
|
+
qps_port = int(arg.split('=')[1])
|
|
246
|
+
except (IndexError, ValueError):
|
|
247
|
+
pass
|
|
248
|
+
elif "-qisport=" in arg_lower:
|
|
249
|
+
try:
|
|
250
|
+
qis_port = int(arg.split('=')[1])
|
|
251
|
+
except (IndexError, ValueError):
|
|
252
|
+
pass
|
|
253
|
+
elif "-qisrestport=" in arg_lower:
|
|
254
|
+
try:
|
|
255
|
+
qis_rest_port = int(arg.split('=')[1])
|
|
256
|
+
except (IndexError, ValueError):
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
return qps_port, qis_port, qis_rest_port
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _ensure_qis_running(host: str, qis_port: int, qis_rest_port: int, timeout: int) -> bool:
|
|
263
|
+
"""Checks if QIS is running on the target port, starts it if not."""
|
|
264
|
+
if _check_port_open(host, qis_port):
|
|
265
|
+
return True
|
|
243
266
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
267
|
+
logger.debug(f"Starting QIS on ports {qis_port}/{qis_rest_port}...")
|
|
268
|
+
qis_args = [f'-port={qis_port}', f'-restport={qis_rest_port}']
|
|
269
|
+
startLocalQis(args=qis_args)
|
|
270
|
+
|
|
271
|
+
# Wait for QIS
|
|
272
|
+
start_time = time.time()
|
|
273
|
+
while not _check_port_open(host, qis_port):
|
|
274
|
+
if time.time() - start_time > timeout:
|
|
275
|
+
logger.error(f"QIS failed to start on port {qis_port} within timeout.")
|
|
276
|
+
return False
|
|
277
|
+
time.sleep(0.5)
|
|
278
|
+
|
|
279
|
+
while isQisRunningAndResponding():
|
|
280
|
+
time.sleep(0.5)
|
|
281
|
+
|
|
282
|
+
return True
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _prepare_qps_launch_env(args: List[str], startQPSMinimised: bool) -> Tuple[Optional[str], Optional[str]]:
|
|
286
|
+
"""Resolves paths using quarchpy_binaries, checks permissions, and builds command."""
|
|
287
|
+
|
|
288
|
+
# 1. Check OS Support
|
|
289
|
+
current_os = platform.system()
|
|
290
|
+
current_arch = platform.machine().lower()
|
|
291
|
+
|
|
292
|
+
# 2. Handle Permissions
|
|
293
|
+
_handle_java_permissions()
|
|
294
|
+
|
|
295
|
+
# 3. Resolve Paths using quarchpy_binaries
|
|
296
|
+
if 'quarchpy_binaries' not in globals() and 'quarchpy_binaries' not in locals():
|
|
297
|
+
# Fallback if module isn't imported or available
|
|
298
|
+
logger.error("quarchpy_binaries module not found. Cannot locate JRE.")
|
|
299
|
+
return None, None
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
java_home = quarchpy_binaries.get_jre_home()
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.error(f"Failed to get JRE home: {e}")
|
|
305
|
+
return None, None
|
|
306
|
+
|
|
307
|
+
# Resolve QPS Jar Path
|
|
308
|
+
qps_root = os.path.dirname(os.path.abspath(__file__))
|
|
309
|
+
qps_root, _ = os.path.split(qps_root) # Up one level
|
|
310
|
+
qps_jar_path = os.path.join(qps_root, "connection_specific", "QPS", "qps.jar")
|
|
311
|
+
qps_dir = os.path.dirname(qps_jar_path)
|
|
312
|
+
|
|
313
|
+
# 4. Construct Command
|
|
314
|
+
# Determine separator based on OS (Windows uses \, Linux/Mac use /)
|
|
315
|
+
|
|
316
|
+
java_bin = "bin/java"
|
|
317
|
+
if current_os == "Windows":
|
|
318
|
+
java_bin = r"bin\java"
|
|
319
|
+
|
|
320
|
+
# Full path to java executable
|
|
321
|
+
java_exe = os.path.join(java_home, java_bin)
|
|
322
|
+
|
|
323
|
+
# Wrap java path in quotes for safety
|
|
324
|
+
java_exe_quoted = f'"{java_exe}"'
|
|
325
|
+
|
|
326
|
+
# Prepare Args String
|
|
327
|
+
args_str = " ".join(args) if args else " "
|
|
328
|
+
if startQPSMinimised and "-ccs" not in args_str.lower():
|
|
329
|
+
args_str += " -ccs=MIN"
|
|
330
|
+
|
|
331
|
+
# Build Final Command
|
|
332
|
+
command = f'{java_exe_quoted} -jar qps.jar {args_str}'
|
|
333
|
+
|
|
334
|
+
return command, qps_dir
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _handle_java_permissions() -> None:
|
|
338
|
+
"""Checks and attempts to fix Java execution permissions."""
|
|
339
|
+
permissions, message = find_java_permissions()
|
|
340
|
+
if not permissions:
|
|
341
|
+
logger.warning(message)
|
|
342
|
+
printText("Not having correct permissions will prevent Quarch Java Programs from launching.")
|
|
343
|
+
printText('Run "python -m quarchpy.run permission_fix" to fix this.')
|
|
344
|
+
printText("Would you like quarchpy to attempt to fix the permissions now? (Y/N)")
|
|
345
|
+
try:
|
|
346
|
+
user_input = requestDialog(">>> ")
|
|
347
|
+
except EOFError:
|
|
348
|
+
user_input = "N"
|
|
349
|
+
if user_input.strip().lower() in ['y', 'yes']:
|
|
350
|
+
fix_permissions()
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _launch_process(command: str, args: List[str]) -> Union[Popen, CompletedProcess]:
|
|
354
|
+
"""Launches the subprocess, handling logging flags."""
|
|
355
|
+
args_str = " ".join(args) if args else ""
|
|
356
|
+
|
|
357
|
+
if "-logconsole=ON" in args_str:
|
|
358
|
+
if platform.system() == "Windows":
|
|
359
|
+
return subprocess.Popen(command, shell=True)
|
|
303
360
|
else:
|
|
304
|
-
return
|
|
361
|
+
return subprocess.run(command + "; exec bash", shell=True)
|
|
362
|
+
else:
|
|
363
|
+
# Use text=True for Python 3.7+
|
|
364
|
+
text_mode = True if sys.version_info >= (3, 7) else False
|
|
365
|
+
# Fallback for 3.6 if needed (universal_newlines=True)
|
|
366
|
+
return subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=text_mode, shell=True)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _wait_for_service(host: str, port: int, timeout: int, process: Optional[subprocess.Popen], args: List[str]) -> bool:
|
|
370
|
+
"""Polls the port until open, checking process output for errors."""
|
|
371
|
+
start_time = time.time()
|
|
372
|
+
args_str = " ".join(args) if args else ""
|
|
373
|
+
logging_on = "-logconsole=ON" in args_str
|
|
374
|
+
|
|
375
|
+
while True:
|
|
376
|
+
if _check_port_open(host, port):
|
|
377
|
+
logger.debug(f"QPS detected on port {port} after {time.time() - start_time:.2f}s")
|
|
378
|
+
return True
|
|
379
|
+
|
|
380
|
+
# If hidden, drain pipes to prevent deadlock and check for crashes
|
|
381
|
+
if not logging_on and process:
|
|
382
|
+
_get_std_msg_and_err_from_QPS_process(process)
|
|
383
|
+
|
|
384
|
+
if time.time() - start_time > timeout:
|
|
385
|
+
logger.error(f"QPS failed to launch on port {port} within timelimit of {timeout} sec.")
|
|
386
|
+
return False
|
|
387
|
+
|
|
388
|
+
time.sleep(0.2)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _check_port_open(host: str, port: int) -> bool:
|
|
392
|
+
"""Simple TCP connect check."""
|
|
393
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
394
|
+
s.settimeout(1)
|
|
395
|
+
try:
|
|
396
|
+
s.connect((host, int(port)))
|
|
397
|
+
s.close()
|
|
398
|
+
return True
|
|
399
|
+
except (socket.timeout, ConnectionRefusedError, OSError):
|
|
400
|
+
return False
|
quarchpy/run.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quarchpy
|
|
3
|
-
Version: 2.2.17.
|
|
3
|
+
Version: 2.2.17.dev3
|
|
4
4
|
Summary: This packpage offers Python support for Quarch Technology modules.
|
|
5
5
|
Author: Quarch Technology ltd
|
|
6
6
|
Author-email: support@quarch.com
|
|
@@ -18,6 +18,7 @@ Classifier: Topic :: System
|
|
|
18
18
|
Classifier: Topic :: System :: Power (UPS)
|
|
19
19
|
Requires-Python: >=3.7
|
|
20
20
|
Description-Content-Type: text/x-rst
|
|
21
|
+
License-File: LICENSE.txt
|
|
21
22
|
Requires-Dist: zeroconf>=0.23.0
|
|
22
23
|
Requires-Dist: numpy
|
|
23
24
|
Requires-Dist: pandas
|
|
@@ -25,6 +26,8 @@ Requires-Dist: requests
|
|
|
25
26
|
Requires-Dist: packaging
|
|
26
27
|
Requires-Dist: quarchpy-binaries
|
|
27
28
|
Requires-Dist: typing-extensions
|
|
29
|
+
Requires-Dist: libusb-package
|
|
30
|
+
Requires-Dist: telnetlib-313-and-up; python_version >= "3.13"
|
|
28
31
|
Dynamic: author
|
|
29
32
|
Dynamic: author-email
|
|
30
33
|
Dynamic: classifier
|
|
@@ -32,6 +35,7 @@ Dynamic: description
|
|
|
32
35
|
Dynamic: description-content-type
|
|
33
36
|
Dynamic: keywords
|
|
34
37
|
Dynamic: license
|
|
38
|
+
Dynamic: license-file
|
|
35
39
|
Dynamic: requires-dist
|
|
36
40
|
Dynamic: requires-python
|
|
37
41
|
Dynamic: summary
|