quarchpy 2.1.12.dev6__py2.py3-none-any.whl → 2.1.13.dev1__py2.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.
Files changed (42) hide show
  1. quarchpy/.idea/.name +1 -1
  2. quarchpy/__pycache__/_version.cpython-311.pyc +0 -0
  3. quarchpy/__pycache__/run.cpython-311.pyc +0 -0
  4. quarchpy/_version.py +1 -1
  5. quarchpy/connection_specific/QPS/qis/help.txt +2 -2
  6. quarchpy/connection_specific/QPS/qis/qis.jar +0 -0
  7. quarchpy/connection_specific/QPS/qis/qis_lib/{CInterface-1.7.jar → CInterface-1.7.03.jar} +0 -0
  8. quarchpy/connection_specific/QPS/qis/qis_lib/{QuarchCommon-0.2.4.jar → QuarchCommon-0.2.5.jar} +0 -0
  9. quarchpy/connection_specific/QPS/qis/qis_lib/commons-lang3-3.12.0.jar +0 -0
  10. quarchpy/connection_specific/QPS/qis/qis_lib/istack-commons-runtime-3.0.11.jar +0 -0
  11. quarchpy/connection_specific/QPS/qis/qis_lib/jakarta.activation-api-2.1.0.jar +0 -0
  12. quarchpy/connection_specific/QPS/qis/qis_lib/jakarta.xml.bind-api-3.0.0.jar +0 -0
  13. quarchpy/connection_specific/QPS/qis/qis_lib/jaxb-core-3.0.0.jar +0 -0
  14. quarchpy/connection_specific/QPS/qis/qis_lib/jaxb-impl-3.0.0.jar +0 -0
  15. quarchpy/connection_specific/QPS/qis/qis_lib/jaxb-runtime-3.0.0.jar +0 -0
  16. quarchpy/connection_specific/QPS/qis/qis_lib/txw2-3.0.0.jar +0 -0
  17. quarchpy/connection_specific/QPS/qps.jar +0 -0
  18. quarchpy/connection_specific/QPS/scriptCommands.txt +27 -4
  19. quarchpy/connection_specific/connection_QIS.py +33 -33
  20. quarchpy/connection_specific/connection_QIS.py.bak +1821 -0
  21. quarchpy/debug/SystemTest.py +4 -6
  22. quarchpy/debug/__pycache__/module_debug.cpython-311.pyc +0 -0
  23. quarchpy/debug/__pycache__/simple_terminal.cpython-311.pyc +0 -0
  24. quarchpy/debug/__pycache__/upgrade_quarchpy.cpython-311.pyc +0 -0
  25. quarchpy/debug/simple_terminal.py +1 -1
  26. quarchpy/debug/upgrade_quarchpy.py +7 -10
  27. quarchpy/debug/versionCompare.py +0 -2
  28. quarchpy/device/__pycache__/scanDevices.cpython-311.pyc +0 -0
  29. quarchpy/device/device.py +15 -2
  30. quarchpy/device/device.py.bak +504 -0
  31. quarchpy/device/scanDevices.py +3 -3
  32. quarchpy/device/scanDevices.py.bak +661 -0
  33. quarchpy/docs/CHANGES.rst +3 -1
  34. quarchpy/qps/__pycache__/qpsFuncs.cpython-311.pyc +0 -0
  35. quarchpy/qps/qpsFuncs.py +2 -1
  36. quarchpy/user_interface/__pycache__/user_interface.cpython-311.pyc +0 -0
  37. quarchpy/user_interface/user_interface.py +10 -5
  38. {quarchpy-2.1.12.dev6.dist-info → quarchpy-2.1.13.dev1.dist-info}/METADATA +4 -2
  39. {quarchpy-2.1.12.dev6.dist-info → quarchpy-2.1.13.dev1.dist-info}/RECORD +41 -27
  40. quarchpy/connection_specific/QPS/qis/qis_lib/commons-lang3-3.2.1.jar +0 -0
  41. {quarchpy-2.1.12.dev6.dist-info → quarchpy-2.1.13.dev1.dist-info}/WHEEL +0 -0
  42. {quarchpy-2.1.12.dev6.dist-info → quarchpy-2.1.13.dev1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1821 @@
1
+ import socket
2
+ import re
3
+ import time
4
+ import sys
5
+ import os
6
+ import datetime
7
+ import select
8
+ import threading
9
+ import math
10
+ import logging
11
+ import struct
12
+ from pathlib import Path
13
+ from quarchpy.user_interface import *
14
+ import xml.etree.ElementTree as ET
15
+ from connection_specific.StreamChannels import StreamGroups
16
+
17
+
18
+ # QisInterface provides a way of connecting to a Quarch backend running at the specified ip address and port, defaults to localhost and 9722
19
+ class QisInterface:
20
+ def __init__(self, host='127.0.0.1', port=9722, connectionMessage=True):
21
+ self.host = host
22
+ self.port = port
23
+ self.maxRxBytes = 4096
24
+ self.sock = None
25
+ self.StreamRunSentSemaphore = threading.Semaphore()
26
+ self.sockSemaphore = threading.Semaphore()
27
+ self.stopFlagList = []
28
+ self.listSemaphore = threading.Semaphore()
29
+ self.deviceList = []
30
+ self.deviceDict = {}
31
+ self.dictSemaphore = threading.Semaphore()
32
+ self.connect(connectionMessage = connectionMessage)
33
+ self.stripesEvent = threading.Event()
34
+
35
+ self.qps_stream_header = None
36
+ self.qps_record_dir_path = None
37
+ self.qps_record_start_time = None
38
+ self.qps_stream_folder_name = None
39
+
40
+ self.module_xml_header = None
41
+ self.streamGroups = None
42
+ self.has_digitals = False
43
+ self.is_multirate = False
44
+
45
+ self.streamSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
46
+ self.streamSock.settimeout(5)
47
+ self.streamSock.connect((self.host, self.port))
48
+ self.pythonVersion = sys.version[0]
49
+ #self.sendText(self.streamSock, '$scan')
50
+ #time.sleep(3)
51
+ if self.pythonVersion == '3':
52
+ temp = '>'
53
+ self.cursor = temp.encode()
54
+ else:
55
+ self.cursor = '>'
56
+ #clear packets
57
+ welcomeString = self.streamSock.recv(self.maxRxBytes).rstrip()
58
+
59
+
60
+ def connect(self, connectionMessage = True):
61
+ '''
62
+ Connect() tries to open a socket on the host and port specified in the objects variables
63
+ If successful it returns the backends welcome string. If it fails it returns a string saying unable to connect
64
+ The backend should be running and host and port set before running this function. Normally it should be called at the beggining
65
+ of talking to the backend and left open until finished talking when the disconnect() function should be ran
66
+
67
+ Param:
68
+ connectionMessage: boolean, optional
69
+ Set to False if you don't want a warning message to appear when an instance is already running on that port. Useful when using isQisRunning() from qisFuncs
70
+ '''
71
+ try:
72
+ self.deviceDictSetup('QIS')
73
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
74
+ self.sock.settimeout(5)
75
+ self.sock.connect((self.host, self.port))
76
+
77
+ #clear packets
78
+ try:
79
+ welcomeString = self.sock.recv(self.maxRxBytes).rstrip()
80
+ welcomeString = 'Connected@' + str(self.host) + ':' + str(self.port) + ' ' + '\n ' + str(welcomeString)
81
+ self.deviceDict['QIS'][0:3] = [False, 'Connected', welcomeString]
82
+ return welcomeString
83
+ except Exception as e:
84
+ logging.error('No welcome received. Unable to connect to Quarch backend on specified host and port (' + self.host + ':' + str(self.port) + ')')
85
+ logging.error('Is backend running and host accessible?')
86
+ self.deviceDict['QIS'][0:3] = [True, 'Disconnected', 'Unable to connect to QIS']
87
+ raise e
88
+ except Exception as e:
89
+ self.deviceDictSetup('QIS')
90
+ if connectionMessage:
91
+ logging.error('Unable to connect to Quarch backend on specified host and port (' + self.host + ':' + str(self.port) + ').')
92
+ logging.error('Is backend running and host accessible?')
93
+ self.deviceDict['QIS'][0:3] = [True, 'Disconnected', 'Unable to connect to QIS']
94
+ raise e
95
+
96
+ # Tries to close the socket to specified host and port.
97
+ def disconnect(self):
98
+ res = 'Disconnecting from backend'
99
+ try:
100
+ self.sock.shutdown(socket.SHUT_RDWR)
101
+ self.sock.close()
102
+ self.deviceDict['QIS'][0:3] = [False, "Disconnected", 'Successfully disconnected from QIS']
103
+ except Exception as e:
104
+ exc_type, exc_obj, exc_tb = sys.exc_info()
105
+ fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
106
+ message = 'Unable to end connection. ' + self.host + ':' + str(self.port) + ' \r\n' + str(exc_type) + ' ' + str(fname) + ' ' + str(exc_tb.tb_lineno)
107
+ self.deviceDict['QIS'][0:3] = [True, "Connected", message]
108
+ raise e
109
+ return res
110
+
111
+ def closeConnection(self, sock=None, conString=None):
112
+ if sock == None:
113
+ sock = self.sock
114
+
115
+ if conString is None:
116
+ cmd="close"
117
+ else:
118
+ cmd =conString+" close"
119
+
120
+ if self.pythonVersion == '3':
121
+ response = self.sendAndReceiveText(sock, cmd).decode()
122
+ else:
123
+ response = self.sendAndReceiveText(sock, cmd)
124
+ return response
125
+
126
+ def startStream(self, module, fileName, fileMaxMB, streamName, streamAverage, releaseOnData, separator, streamDuration = None):
127
+ self.StreamRunSentSemaphore.acquire()
128
+ self.deviceDictSetup('QIS')
129
+ i = self.deviceMulti(module)
130
+ self.stopFlagList[i] = True
131
+ self.stripesEvent.set()
132
+ self.module_xml_header = None
133
+
134
+ # Create the thread
135
+ t1 = threading.Thread(target=self.startStreamThread, name=module,
136
+ args=(module, fileName, fileMaxMB, streamName, streamAverage, releaseOnData, separator, streamDuration))
137
+ # Start the thread
138
+ t1.start()
139
+
140
+ # count = 0
141
+ while (self.stripesEvent.is_set()):
142
+ # count += 1 --debugging to show delay
143
+ pass
144
+ # just wait until event is cleared
145
+
146
+ def startStreamQPS(self, module, fileName, fileMaxMB, streamName, streamAverage, releaseOnData, separator):
147
+ self.StreamRunSentSemaphore.acquire()
148
+ self.deviceDictSetup('QIS')
149
+ i = self.deviceMulti(module)
150
+ self.stopFlagList[i] = True
151
+ self.stripesEvent.set()
152
+ self.module_xml_header = None
153
+
154
+ # Create the thread
155
+ t1 = threading.Thread(target=self.startStreamThreadQPS, name=module,
156
+ args=(module, fileName, fileMaxMB, streamName, streamAverage, releaseOnData, separator))
157
+ # Start the thread
158
+ t1.start()
159
+
160
+ # count = 0
161
+ while (self.stripesEvent.is_set()):
162
+ # count += 1 --debugging to show delay
163
+ pass
164
+ # just wait until event is cleared
165
+
166
+
167
+ def stopStream(self, module, blocking = True):
168
+ moduleName=module.ConString
169
+ i = self.deviceMulti(moduleName)
170
+ self.stopFlagList[i] = False
171
+ # Wait until the stream thread is finished before returning to user.
172
+ # This means this function will block until the QIS buffer is emptied by the second while
173
+ # loop in startStreanThread. This may take some time, especially at low averaging but
174
+ # should gurantee the data won't be lost and QIS buffer is emptied.
175
+ if blocking:
176
+ running = True
177
+ while running:
178
+ threadNameList = []
179
+ for t1 in threading.enumerate():
180
+ threadNameList.append(t1.name)
181
+ moduleStreaming= module.sendCommand("rec stream?").lower() #checking if module thinks its streaming.
182
+ moduleStreaming2= module.sendCommand("stream?").lower() #checking if the module has told qis it has stopped streaming.
183
+
184
+ #print("thread list = " + str(threadNameList))
185
+ #print("moduleStreaming rec stream? = " + str(moduleStreaming))
186
+ #print("moduleStreaming stream? = " + str(moduleStreaming2))
187
+
188
+ if (moduleName in threadNameList or "running" in moduleStreaming or "running" in moduleStreaming2):
189
+ time.sleep(0.1)
190
+
191
+ else:
192
+ running = False
193
+ time.sleep(0.1)
194
+
195
+ # This is the function that is run when t1 is created. It is run in a seperate thread from
196
+ # the main application so streaming can happen without blocking the main application from
197
+ # doing other things. Within this function/thread you have to be very careful not to try
198
+ # and 'communicate' with anything from other threads. If you do, you MUST use a thread safe
199
+ # way of communicating. The thread creates it's own socket and should use that, NOT the objects socket
200
+ # (which some of the comms with module functions will use by default).
201
+ def startStreamThread(self, module, fileName, fileMaxMB, streamName, streamAverage, releaseOnData, separator, streamDuration = None):
202
+ #Start module streaming and then read stream data
203
+
204
+ stripes = ['Empty Header']
205
+ #Send stream command so module starts streaming data into the backends buffer
206
+ streamRes = self.sendAndReceiveCmd(self.streamSock, 'rec stream', device=module, betweenCommandDelay = 0)
207
+ #printText(streamRes)
208
+ if ('rec stream : OK' in streamRes):
209
+ if (releaseOnData == False):
210
+ self.StreamRunSentSemaphore.release()
211
+ self.stripesEvent.clear()
212
+ self.deviceDict[module][0:3] = [False, 'Running', 'Stream Running']
213
+ else:
214
+ self.StreamRunSentSemaphore.release()
215
+ self.stripesEvent.clear()
216
+ self.deviceDict[module][0:3] = [True, 'Stopped', module + " couldn't start because " + streamRes]
217
+ return
218
+ #If recording to file then get header for file
219
+ if(fileName is not None):
220
+
221
+ baseSamplePeriod = self.streamHeaderAverage(device=module, sock=self.streamSock)
222
+ count=0
223
+ maxTries=10
224
+ while 'Header Not Available' in baseSamplePeriod:
225
+ baseSamplePeriod = self.streamHeaderAverage(device=module, sock=self.streamSock)
226
+ time.sleep(0.1)
227
+ count += 1
228
+ if count > maxTries:
229
+ self.deviceDict[module][0:3] = [True, 'Stopped', 'Header not available']
230
+ exit()
231
+ version = self.streamHeaderVersion(device=module, sock=self.streamSock)
232
+ with open(fileName, 'w') as f:
233
+ timeStampHeader = datetime.datetime.now().strftime("%H:%M:%S:%f %d/%m/%y")
234
+ formatHeader = self.streamHeaderFormat(device=module, sock=self.streamSock)
235
+ formatHeader = formatHeader.replace(", ", separator)
236
+ f.write(formatHeader + '\n')
237
+ numStripesPerRead = 4096
238
+ maxFileExceeded = False
239
+ openAttempts = 0
240
+ leftover = 0
241
+ remainingStripes = []
242
+ streamOverrun = False
243
+ streamComplete = False
244
+
245
+ # baseSamplePeriod is a string in the form [int][unit]
246
+ # where the unit can be S,mS,uS,nS
247
+ # we need to convert it to a float number of seconds
248
+ # and we also derive the baseSampleUnits in string and numeric form
249
+ if 'ns' in baseSamplePeriod.lower():
250
+ baseSampleUnitText = 'ns'
251
+ baseSampleUnitExponent = -9
252
+ elif 'us' in baseSamplePeriod.lower():
253
+ baseSampleUnitText = 'us'
254
+ baseSampleUnitExponent = -6
255
+ elif 'ms' in baseSamplePeriod.lower():
256
+ baseSampleUnitText = 'ms'
257
+ baseSampleUnitExponent = -3
258
+ elif 'S' in baseSamplePeriod.lower():
259
+ baseSampleUnitText = 's'
260
+ baseSampleUnitExponent = 0
261
+ else:
262
+ raise ValueError("couldn't decode samplePeriod")
263
+
264
+ baseSamplePeriodS = int(re.search('^\d*\.?\d*', baseSamplePeriod).group())*(10**baseSampleUnitExponent)
265
+
266
+ # TODO: MD Thinks this implements software averaging, is unused and now performed in QIS
267
+ if streamAverage != None:
268
+ #Matt converting streamAveraging into number
269
+ streamAverage = self.convertStreamAverage(streamAverage)
270
+
271
+ stripesPerAverage = float(streamAverage) / (float(baseSamplePeriod) * 4e-6)
272
+ isRun = True
273
+ while isRun:
274
+ try:
275
+ with open(fileName, 'ab') as f:
276
+ # Until the event threadRunEvent is set externally to this thread,
277
+ # loop and read from the stream
278
+ i = self.deviceMulti(module)
279
+ while self.stopFlagList[i] and (not streamOverrun) and (not streamComplete):
280
+ #now = time.time()
281
+ streamOverrun, removeChar, newStripes = self.streamGetStripesText(self.streamSock, module, numStripesPerRead)
282
+ newStripes = newStripes.replace(b' ', str.encode(separator))
283
+ #print (time.time() - now)
284
+ if streamOverrun:
285
+ self.deviceDict[module][0:3] = [True, 'Stopped', 'Device buffer overrun']
286
+ # TODO: MD Why don't we return isEmpty in the tuple, instead of having this confusing test?
287
+ if (removeChar == -6 and len(newStripes) == 6):
288
+ isEmpty = True
289
+ else:
290
+ isEmpty = False
291
+ if isEmpty == False:
292
+ #Writes in file if not too big else stops streaming
293
+ statInfo = os.stat(fileName)
294
+ fileMB = statInfo.st_size / 1048576
295
+ try:
296
+ int(fileMaxMB)
297
+ except:
298
+ continue
299
+ if int(fileMB) < int(fileMaxMB):
300
+ if (releaseOnData == True):
301
+ self.StreamRunSentSemaphore.release()
302
+ self.stripesEvent.clear()
303
+ releaseOnData = False
304
+ # TODO: MD Thinks this implements software averaging, is unused and now performed in QIS where required
305
+ if(streamAverage != None):
306
+ leftover, remainingStripes = self.averageStripes(leftover, stripesPerAverage, newStripes[:removeChar], f, remainingStripes)
307
+ else:
308
+ # if we have a fixed streamDuration
309
+ if streamDuration != None:
310
+ # Get the last data line in the file
311
+ lastLine = newStripes.splitlines()[-3] # the last data line is followed by 'eof' and '>'
312
+ lastTime = lastLine.decode().split(separator)[0] # get the first (time) entry
313
+
314
+ # if the last entry is still within the required stream length, write the whole lot
315
+ if int(lastTime) < int(streamDuration/(10**baseSampleUnitExponent)): # < rather than <= because we start at 0
316
+ f.write(newStripes[:removeChar])
317
+ # else write each line individually until we have reached the desired endpoint
318
+ else:
319
+ for thisLine in newStripes.splitlines()[:-2]:
320
+ lastTime = thisLine.decode().split(separator)[0]
321
+ if int(lastTime) < int(streamDuration/(10**baseSampleUnitExponent)):
322
+ f.write(thisLine + b'\r' + b'\n') # Put the CR back on the end
323
+ else:
324
+ streamComplete = True
325
+ break
326
+ else:
327
+ f.write(newStripes[:removeChar])
328
+
329
+ else:
330
+ maxFileExceeded = True
331
+ #printText('QisInterface file size exceeded in loop 1- breaking')
332
+ maxFileStatus = self.streamBufferStatus(device=module, sock=self.streamSock)
333
+ f.write('Warning: Max file size exceeded before end of stream.\n')
334
+ f.write('Unrecorded stripes in buffer when file full: ' + maxFileStatus + '.')
335
+ self.deviceDict[module][0:3] = [True, 'Stopped', 'User defined max filesize reached']
336
+ break
337
+ else:
338
+ # there's no stripes in the buffer - it's not filling up fast -
339
+ # sleeps so we don't spam qis with requests (seems to make QIS crash)
340
+ # it might be clever to change the sleep time accoring to the situation
341
+ # e.g. wait longer with higher averaging or lots of no stripes in a row
342
+ time.sleep(0.1)
343
+ streamStatus = self.streamRunningStatus(device=module, sock=self.streamSock)
344
+ if streamOverrun:
345
+ #printText('QisInterface overrun - breaking')
346
+ break
347
+ elif "Stopped" in streamStatus:
348
+ self.deviceDict[module][0:3] = [True, 'Stopped', 'User halted stream']
349
+ break
350
+ #printText('Left while 1')
351
+ self.sendAndReceiveCmd(self.streamSock, 'rec stop', device=module, betweenCommandDelay = 0)
352
+ streamState = self.sendAndReceiveCmd(self.streamSock, 'stream?', device=module, betweenCommandDelay=0) # use "stream?" rather than "rec stream?" as it checks both QIS AND the device.
353
+ while "stopped" not in streamState.lower():
354
+ logging.debug("waiting for stream? to return stopped")
355
+ time.sleep(0.1)
356
+ streamState = self.sendAndReceiveCmd(self.streamSock, 'stream?', device=module, betweenCommandDelay=0) # use "stream?" rather than "rec stream?" as it checks both QIS AND the device.
357
+
358
+ if (not streamOverrun) and (not maxFileExceeded):
359
+ self.deviceDict[module][0:3] = [False, 'Stopped', 'Stream stopped - emptying buffer']
360
+ # print self.streamBufferStatus(device=module, sock=self.streamSock)
361
+ if (not maxFileExceeded):
362
+ #If the backend buffer still has data then keep reading it out
363
+ #printText('Streaming stopped. Emptying data left in QIS buffer to file (' + self.streamBufferStatus(device=module, sock=self.streamSock) + ')')
364
+ streamOverrun, removeChar, newStripes = self.streamGetStripesText(self.streamSock, module, numStripesPerRead)
365
+ # TODO: Why don't we return isEmpty in the tuple, instead of having this confusing test?
366
+ if (removeChar == -6 and len(newStripes) == 6):
367
+ isEmpty = True
368
+ else:
369
+ isEmpty = False
370
+ while isEmpty == False: # if newStripes has length 6 then it only contains 'eof\r\n'
371
+ statInfo = os.stat(fileName)
372
+ fileMB = statInfo.st_size / 1048576
373
+ try:
374
+ int(fileMaxMB)
375
+ except:
376
+ continue
377
+ if int(fileMB) < int(fileMaxMB):
378
+ if streamComplete != True:
379
+ if(streamAverage != None):
380
+ leftover, remainingStripes = self.averageStripes(leftover, stripesPerAverage, newStripes[:removeChar], f, remainingStripes)
381
+ else:
382
+ newStripes = newStripes.replace(b' ', str.encode(separator))
383
+ f.write(newStripes[:removeChar])
384
+ else:
385
+ if not maxFileExceeded:
386
+ maxFileStatus = self.streamBufferStatus(device=module, sock=self.streamSock)
387
+ maxFileExceeded = True
388
+ self.deviceDict[module][0:3] = [True, 'Stopped', 'User defined max filesize reached']
389
+ break
390
+ #time.sleep(0.01) #reduce speed of loop to stop spamming qis
391
+ streamOverrun, removeChar, newStripes = self.streamGetStripesText(self.streamSock, module, numStripesPerRead, skipStatusCheck=True)
392
+ if removeChar == -6:
393
+ if len(newStripes) == 6:
394
+ isEmpty = True
395
+ if maxFileExceeded:
396
+ f.write(b'Warning: Max file size exceeded before end of stream.\n')
397
+ f.write(b'Unrecorded stripes in buffer when file full: ' + maxFileStatus + '.')
398
+ logging.warning('Max file size exceeded. Some data has not been saved to file: ' + maxFileStatus + '.')
399
+
400
+ #printText('Stripes in buffer now: ' + self.streamBufferStatus(device=module, sock=self.streamSock))
401
+
402
+ if streamOverrun:
403
+ self.deviceDict[module][0:3] = [True, 'Stopped', 'Device buffer overrun - QIS buffer empty']
404
+ elif not maxFileExceeded:
405
+ self.deviceDict[module][0:3] = [False, 'Stopped', 'Stream stopped']
406
+ time.sleep(0.2)
407
+ isRun = False
408
+ except IOError as err:
409
+ #printText('\n\n!!!!!!!!!!!!!!!!!!!! IO Error in QisInterface !!!!!!!!!!!!!!!!!!!!\n\n')
410
+ time.sleep(0.5)
411
+ openAttempts += 1
412
+ if openAttempts > 4:
413
+ logging.error('\n\n!!!!!!!!!!!!!!!!!!!! Too many IO Errors in QisInterface !!!!!!!!!!!!!!!!!!!!\n\n')
414
+ raise err
415
+
416
+ # This is the function that is ran when t1 is created. It is ran in a seperate thread from
417
+ # the main application so streaming can happen without blocking the main application from
418
+ # doing other things. Within this function/thread you have to be very careful not to try
419
+ # and 'communicate' with anything from other threads. If you do, you MUST use a thread safe
420
+ # way of communicating. The thread creates it's own socket and should use that NOT the objects socket
421
+ # (which some of the comms with module functions will use by default).
422
+
423
+ def startStreamThreadQPS(self, module, fileName, fileMaxMB, streamName, streamAverage, releaseOnData, separator):
424
+
425
+ # Start module streaming and then read stream data
426
+ # self.sendAndReceiveCmd(self.streamSock, 'stream mode resample 10mS', device=module, betweenCommandDelay=0)
427
+ self.sendAndReceiveCmd(self.streamSock, 'stream mode header v3', device=module, betweenCommandDelay=0)
428
+ self.sendAndReceiveCmd(self.streamSock, 'stream mode power enable', device=module, betweenCommandDelay=0)
429
+ self.sendAndReceiveCmd(self.streamSock, 'stream mode power total enable', device=module, betweenCommandDelay=0)
430
+
431
+ self.qps_record_start_time = time.time() * 1000
432
+
433
+ stripes = ['Empty Header']
434
+ # Send stream command so module starts streaming data into the backends buffer
435
+ streamRes = self.sendAndReceiveCmd(self.streamSock, 'rec stream', device=module, betweenCommandDelay=0)
436
+ # printText(streamRes)
437
+ if ('rec stream : OK' in streamRes):
438
+ if (releaseOnData == False):
439
+ self.StreamRunSentSemaphore.release()
440
+ self.stripesEvent.clear()
441
+ self.deviceDict[module][0:3] = [False, 'Running', 'Stream Running']
442
+ else:
443
+ self.StreamRunSentSemaphore.release()
444
+ self.stripesEvent.clear()
445
+ self.deviceDict[module][0:3] = [True, 'Stopped', module + " couldn't start because " + streamRes]
446
+ return
447
+
448
+ # If recording to file then get header for file
449
+ if (fileName is not None):
450
+
451
+ baseSamplePeriod = self.streamHeaderAverage(device=module, sock=self.streamSock)
452
+ count = 0
453
+ maxTries = 10
454
+ while 'Header Not Available' in baseSamplePeriod:
455
+ baseSamplePeriod = self.streamHeaderAverage(device=module, sock=self.streamSock)
456
+ time.sleep(0.1)
457
+ count += 1
458
+ if count > maxTries:
459
+ self.deviceDict[module][0:3] = [True, 'Stopped', 'Header not available']
460
+ exit()
461
+ version = self.streamHeaderVersion(device=module, sock=self.streamSock)
462
+
463
+ numStripesPerRead = 4096
464
+ maxFileExceeded = False
465
+ openAttempts = 0
466
+ leftover = 0
467
+ remainingStripes = []
468
+ streamOverrun = False
469
+ if streamAverage != None:
470
+ # Matt converting streamAveraging into number
471
+ streamAverage = self.convertStreamAverage(streamAverage)
472
+
473
+ stripesPerAverage = float(streamAverage) / (float(baseSamplePeriod) * 4e-6)
474
+
475
+ isRun = True
476
+
477
+ self.create_dir_structure(module, fileName)
478
+
479
+ while isRun:
480
+ try:
481
+ # with open(fileName, 'ab') as f:
482
+ # Until the event threadRunEvent is set externally to this thread,
483
+ # loop and read from the stream
484
+ i = self.deviceMulti(module)
485
+ while self.stopFlagList[i] and (not streamOverrun):
486
+ # now = time.time()
487
+ streamOverrun, removeChar, newStripes = self.streamGetStripesText(self.streamSock, module,
488
+ numStripesPerRead)
489
+ newStripes = newStripes.replace(b' ', str.encode(separator))
490
+ # print(newStripes)
491
+ # print(len(newStripes))
492
+
493
+ # print (time.time() - now)
494
+ if streamOverrun:
495
+ self.deviceDict[module][0:3] = [True, 'Stopped', 'Device buffer overrun']
496
+ if (removeChar == -6 and len(newStripes) == 6):
497
+ isEmpty = True
498
+ else:
499
+ isEmpty = False
500
+ if isEmpty == False:
501
+ # Writes in file if not too big else stops streaming
502
+ # print(newStripes)
503
+
504
+ x = newStripes[:removeChar]
505
+ y = x.decode("utf-8")
506
+
507
+ print(f"decoded stripe : {y}")
508
+
509
+ # Writing multiple stripes
510
+ if "\r\n" in y:
511
+ y = y.split("\r\n")
512
+
513
+ if self.has_digitals:
514
+ # Write qps files for PAM
515
+ for stripes in y:
516
+ if stripes:
517
+ stripe = stripes.split(",")
518
+ self.write_stripe_to_files_PAM(stripe)
519
+ else:
520
+ # Write qps files for PPM
521
+ for stripes in y:
522
+ if stripes:
523
+ stripe = stripes.split(",")
524
+ self.write_stripe_to_files_HD(stripe)
525
+
526
+ else:
527
+ if self.has_digitals:
528
+ # Write qps files for PAM
529
+ for stripes in y:
530
+ if stripes:
531
+ stripe = stripes.split(",")
532
+ self.write_stripe_to_files_PAM(stripe)
533
+ else:
534
+ # Write qps files for PPM
535
+ for stripes in y:
536
+ if stripes:
537
+ stripe = stripes.split(",")
538
+ self.write_stripe_to_files_HD(stripe)
539
+
540
+
541
+ else:
542
+ # there's no stripes in the buffer - it's not filling up fast -
543
+ # sleeps so we don't spam qis with requests (seems to make QIS crash)
544
+ # it might be clever to change the sleep time accoring to the situation
545
+ # e.g. wait longer with higher averaging or lots of no stripes in a row
546
+ time.sleep(0.1)
547
+ streamStatus = self.streamRunningStatus(device=module, sock=self.streamSock)
548
+ if streamOverrun:
549
+ # printText('QisInterface overrun - breaking')
550
+ break
551
+ elif "Stopped" in streamStatus:
552
+ self.deviceDict[module][0:3] = [True, 'Stopped', 'User halted stream']
553
+ break
554
+
555
+ # printText('Left while 1')
556
+ self.sendAndReceiveCmd(self.streamSock, 'rec stop', device=module, betweenCommandDelay=0)
557
+ # streamState = self.sendAndReceiveCmd(self.streamSock, 'stream?', device=module, betweenCommandDelay=0) # use "stream?" rather than "rec stream?" as it checks both QIS AND the device.
558
+ # while "stopped" not in streamState.lower():
559
+ # logging.debug("waiting for stream? to contained stopped")
560
+ # time.sleep(0.1)
561
+ # streamState = self.sendAndReceiveCmd(self.streamSock, 'stream?', device=module,betweenCommandDelay=0) # use "stream?" rather than "rec stream?" as it checks both QIS AND the device.
562
+
563
+ isRun = False
564
+ except IOError as err:
565
+ # printText('\n\n!!!!!!!!!!!!!!!!!!!! IO Error in QisInterface !!!!!!!!!!!!!!!!!!!!\n\n')
566
+ time.sleep(0.5)
567
+ openAttempts += 1
568
+ if openAttempts > 4:
569
+ logging.error(
570
+ '\n\n!!!!!!!!!!!!!!!!!!!! Too many IO Errors in QisInterface !!!!!!!!!!!!!!!!!!!!\n\n')
571
+ raise err
572
+
573
+ self.create_index_file()
574
+ if self.has_digitals:
575
+ self.create_index_file_digitals()
576
+
577
+ self.create_qps_file(module)
578
+
579
+ def write_stripe_to_files_HD(self, stripe):
580
+ # Cycle through items in stripe
581
+ for index, item in enumerate(stripe):
582
+ if index == 0:
583
+ continue
584
+ with open(os.path.join(self.qps_record_dir_path, "data000",
585
+ f"data000_00{index - 1}_000000000"),
586
+ "ab") as file1:
587
+
588
+ x = struct.pack(">d", int(item))
589
+ # logging.debug(item, x)
590
+ file1.write(x)
591
+
592
+ def write_stripe_to_files_PAM(self, stripe):
593
+ # Note to reader - List should be ordered 1>x on analogue and digitals
594
+ counter = 0
595
+ for group in self.streamGroups.groups:
596
+ for i, channel in enumerate(group.channels):
597
+ # incrementing here so we skip stripe[0] which is time
598
+ counter += 1
599
+
600
+ x = i
601
+ while len(str(x)) < 3:
602
+ x = "0" + str(x)
603
+
604
+ # Write all in group 0 to analogue
605
+ if group.group_id == 0:
606
+
607
+ with open(os.path.join(self.qps_record_dir_path, "data000",
608
+ f"data000_{x}_000000000"),
609
+ "ab") as file1:
610
+ x = struct.pack(">d", int(stripe[counter]))
611
+ # logging.debug(item, x)
612
+ file1.write(x)
613
+ else:
614
+ # Write all in group 1 to digital
615
+ with open(os.path.join(self.qps_record_dir_path, "data101",
616
+ f"data101_{x}_000000000"),
617
+ "ab") as file1:
618
+ x = struct.pack(">d", int(stripe[counter]))
619
+ # logging.debug(item, x)
620
+ file1.write(x)
621
+
622
+
623
+ # Send text and get the backends response. - acts as wrapper to the sendAndReceiveText, intended to provide some extra convenience
624
+ # when sending commands to module (as opposed to back end)
625
+ # If read until cursor is set to True (which is default) then keep reading response until a cursor is returned as the last character of result string
626
+ # After command is sent wait for betweenCommandDelay which defaults to 0 but can be specified to add a delay between commands
627
+ # The objects connection needs to be opened (connect()) before this is used
628
+ def sendCmd(self, device='', cmd='$help', sock=None, readUntilCursor=True, betweenCommandDelay=0.0, expectedResponse = True):
629
+ if sock==None:
630
+ sock = self.sock
631
+ if not (device == ''):
632
+ self.deviceDictSetup(device)
633
+
634
+ if expectedResponse is False:
635
+ self.sendText(sock, cmd, device)
636
+ return
637
+
638
+ res = self.sendAndReceiveText(sock, cmd, device, readUntilCursor)
639
+ if (betweenCommandDelay > 0):
640
+ time.sleep(betweenCommandDelay)
641
+ #If ends with cursor get rid of it
642
+ if res[-1:] == self.cursor:
643
+ res = res[:-3] #remove last three chars - hopefully '\r\n>'
644
+ # time.sleep(0.1)
645
+ return res.decode()
646
+
647
+
648
+ def sendAndReceiveCmd(self, sock=None, cmd='$help', device='', readUntilCursor=True, betweenCommandDelay=0.0):
649
+ if sock==None:
650
+ sock = self.sock
651
+ if not (device == ''):
652
+ self.deviceDictSetup(device)
653
+ if self.pythonVersion == '3':
654
+ res = self.sendAndReceiveText(sock, cmd, device, readUntilCursor).decode()
655
+ else:
656
+ res = self.sendAndReceiveText(sock, cmd, device, readUntilCursor)
657
+ if (betweenCommandDelay > 0):
658
+ time.sleep(betweenCommandDelay)
659
+ #If ends with cursor get rid of it
660
+ if res[-1:] == '>':
661
+ res = res[:-3] #remove last three chars - hopefully '\r\n>'
662
+ return cmd + ' : ' + res
663
+
664
+ # Send text to the back end then read it's response
665
+ # The objects connection needs to be opened (connect()) before this is used
666
+ # If read until cursor is set to True (which is default) then keep reading response until a cursor is returned as the last character of result string
667
+ def sendAndReceiveText(self, sock, sentText='$help', device='', readUntilCursor=True):
668
+ self.sockSemaphore.acquire()
669
+ try:
670
+ self.sendText(sock, sentText, device)
671
+ if self.pythonVersion == '3':
672
+ res = bytearray()
673
+ res.extend(self.rxBytes(sock))
674
+ #Somtimes we just get one cursor back of currently unknown origins
675
+ #If that happens discard it and read again
676
+ if len(res)==0:
677
+ logging.warning("empty response from QIS. Retrying.")
678
+ streamResp=self.sendAndReceiveText(sock, "stream?",device,readUntilCursor).lower()
679
+ if len(streamResp)==0:
680
+ raise("Empty response from QIS twice in a row")
681
+ else:
682
+ logging.warning("Response recovered Successfully. Continuing.")
683
+ res = bytearray()
684
+ res.extend(self.rxBytes(sock))
685
+ if len(res) == 0:
686
+ raise ("empty response from QIS after second ")
687
+ if res[0] == self.cursor:
688
+ #res[0] = self.rxBytes(sock)
689
+ logging.warning('Only Returned Cursor!!!!!')
690
+ #If create socked fail (between backend and tcp/ip module)
691
+ cba = 'Create Socket Fail'
692
+ if cba.encode() == res[0]:
693
+ logging.warning(res[0].decode())
694
+ cba = 'Connection Timeout'
695
+ if cba.encode() == res[0]:
696
+ logging.warning(res[0].decode())
697
+ #If reading until a cursor comes back then keep reading until a cursor appears or max tries exceeded
698
+ if readUntilCursor:
699
+ maxReads = 1000
700
+ count = 1
701
+ #check for cursor at end of read and if not there read again
702
+ while res[-1:] != self.cursor:
703
+ res.extend(self.rxBytes(sock))
704
+ count += 1
705
+ if count >= maxReads:
706
+ raise Exception(' Count = Error: max reads exceeded before cursor returned')
707
+ return res
708
+ else:
709
+ res = self.rxBytes(sock)
710
+ #Somtimes we just get one cursor back of currently unknown origins
711
+ #If that happens discard it and read again
712
+ if res == self.cursor:
713
+ #printText(" CURSOR ONLY")
714
+ res = self.rxBytes(sock)
715
+ #If create socked fail (between backend and tcp/ip module)
716
+ if 'Create Socket Fail' in res:
717
+ raise Exception(res)
718
+ if 'Connection Timeout' in res:
719
+ raise Exception(res)
720
+ #If reading until a cursor comes back then keep reading until a cursor appears or max tries exceeded
721
+ if readUntilCursor:
722
+ maxReads = 1000
723
+ count = 1
724
+ #check for cursor at end of read and if not there read again
725
+ while res[-1:] != self.cursor:
726
+ res += self.rxBytes(sock)
727
+ count += 1
728
+ if count >= maxReads:
729
+ raise Exception(' Count = Error: max reads exceeded before cursor returned')
730
+ return res
731
+
732
+ except Exception as e:
733
+ raise e
734
+ finally:
735
+ self.sockSemaphore.release()
736
+
737
+ def rxBytes(self,sock):
738
+ #sock.setblocking(0) #make socket non-blocking
739
+ #printText('rxBytes')
740
+ maxExceptions=10
741
+ exceptions=0
742
+ maxReadRepeats=50
743
+ readRepeats=0
744
+ timeout_in_seconds = 10
745
+ #Keep trying to read bytes until we get some, unless number of read repeads or exceptions is exceeded
746
+ while True:
747
+ try:
748
+ #select.select returns a list of waitable objects which are ready. On windows it has to be sockets.
749
+ #The first arguement is a list of objects to wait for reading, second writing, third 'exceptional condition'
750
+ #We only use the read list and our socket to check if it is readable. if no timeout is specified then it blocks until it becomes readable.
751
+ ready = select.select([sock], [], [], timeout_in_seconds)
752
+ #time.sleep(0.1)
753
+ #ready = [1,2]
754
+ if ready[0]:
755
+ ret = sock.recv(self.maxRxBytes)
756
+ #time.sleep(0.1)
757
+ return ret
758
+ else:
759
+ #printText('rxBytes - readRepeats + 1')
760
+
761
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
762
+ sock.connect((self.host, self.port))
763
+ sock.settimeout(5)
764
+
765
+ try:
766
+ welcomeString = self.sock.recv(self.maxRxBytes).rstrip()
767
+ welcomeString = 'Connected@' + self.host + ':' + str(self.port) + ' ' + '\n ' + welcomeString
768
+ printText('New Welcome:' + welcomeString)
769
+ except Exception as e:
770
+ logging.error('tried and failed to get new welcome')
771
+ raise e
772
+
773
+ readRepeats=readRepeats+1
774
+ time.sleep(0.5)
775
+
776
+ except Exception as e:
777
+ #printText('rxBytes - exceptions + 1')
778
+ exceptions=exceptions+1
779
+ time.sleep(0.5)
780
+ raise e
781
+
782
+ #If read repeats has been exceeded we failed to get any data on this read.
783
+ # !!! This is likely to break whatever called us !!!
784
+ if readRepeats >= maxReadRepeats:
785
+ logging.error('Max read repeats exceeded - returning.')
786
+ return 'No data received from QIS'
787
+ #If number of exceptions exceeded then give up by exiting
788
+ if exceptions >= maxExceptions:
789
+ logging.error('Max exceptions exceeded - exiting') #exceptions are probably 10035 non-blocking socket could not complete immediatley
790
+ exit()
791
+
792
+ # Send text to the back end don't read it's response
793
+ # The objects connection needs to be opened (connect()) before this is used
794
+ def sendText(self, sock, message='$help', device=''):
795
+ if device != '':
796
+ specialTimeout = '%500000'
797
+ message = device + specialTimeout + ' ' + message
798
+ #printText('Sending: "' + message + '" ' + self.host + ':' + str(self.port))
799
+
800
+ if self.pythonVersion == 2:
801
+ sock.sendall(message + '\r\n')
802
+ else:
803
+ convM = message + '\r\n'
804
+ sock.sendall(convM.encode('utf-8'))
805
+ return 'Sent:' + message
806
+
807
+ # Query the backend for a list of connected modules. A $scan command is sent to refresh the list of devices,
808
+ # Then a wait occurs while the backend discovers devices (network ones can take a while) and then a list of device name strings is returned
809
+ # The objects connection needs to be opened (connect()) before this is used
810
+ def getDeviceList(self, sock=None):
811
+
812
+ if sock == None:
813
+ sock = self.sock
814
+ scanWait = 2
815
+ #printText('Scanning for devices and waiting ' + str(scanWait) + ' seconds.')
816
+ if self.pythonVersion == '3':
817
+ #devString = self.sendAndReceiveText(sock, '$scan').decode
818
+ #time.sleep(scanWait)
819
+ devString = self.sendAndReceiveText(sock, '$list').decode()
820
+ else:
821
+ #devString = self.sendAndReceiveText(sock, '$scan')
822
+ #time.sleep(scanWait)
823
+ devString = self.sendAndReceiveText(sock, '$list')
824
+
825
+ devString = devString.replace('>', '')
826
+ devString = devString.replace(r'\d+\) ', '')
827
+
828
+ #printText('"' + devString + '"')
829
+ devString = devString.split('\r\n')
830
+ devString = filter(None, devString) #remove empty elements
831
+ return devString
832
+
833
+ def get_list_details(self, sock=None):
834
+ if sock == None:
835
+ sock = self.sock
836
+ scanWait = 2
837
+ # printText('Scanning for devices and waiting ' + str(scanWait) + ' seconds.')
838
+ if self.pythonVersion == '3':
839
+ # devString = self.sendAndReceiveText(sock, '$scan').decode
840
+ # time.sleep(scanWait)
841
+ devString = self.sendAndReceiveText(sock, '$list details').decode()
842
+ else:
843
+ # devString = self.sendAndReceiveText(sock, '$scan')
844
+ # time.sleep(scanWait)
845
+ devString = self.sendAndReceiveText(sock, '$list details')
846
+
847
+ devString = devString.replace('>', '')
848
+ devString = devString.replace(r'\d+\) ', '')
849
+
850
+ # printText('"' + devString + '"')
851
+ devString = devString.split('\r\n')
852
+ devString = [x for x in devString if x] # remove empty elements
853
+ return devString
854
+
855
+ def scanIP(QisConnection, ipAddress):
856
+ """
857
+ Triggers QIS to look at a specific IP address for a quarch module
858
+
859
+ Parameters
860
+ ----------
861
+ QisConnection : QpsInterface
862
+ The interface to the instance of QPS you would like to use for the scan.
863
+ ipAddress : str
864
+ The IP address of the module you are looking for eg '192.168.123.123'
865
+ sleep : int, optional
866
+ This optional variable sleeps to allow the network to scan for the module before allowing new commands to be sent to QIS.
867
+ """
868
+
869
+ logging.debug("Starting QIS IP Address Lookup at " + ipAddress)
870
+ if not ipAddress.lower().__contains__("tcp::"):
871
+ ipAddress = "TCP::" + ipAddress
872
+ response = "No response from QIS Scan"
873
+ try:
874
+ response = QisConnection.sendCmd(cmd="$scan " + ipAddress, expectedResponse=True)
875
+ # valid response is "Located device: 192.168.1.2"
876
+ if "located" in response.lower():
877
+ logging.debug(response)
878
+ # return the valid response
879
+ return response
880
+ else:
881
+ logging.warning("No module found at " + ipAddress)
882
+ logging.warning(response)
883
+ return response
884
+
885
+ except Exception as e:
886
+ logging.warning(e)
887
+ logging.warning("No module found at " + ipAddress)
888
+
889
+
890
+
891
+
892
+ def GetQisModuleSelection(self, favouriteOnly=True , additionalOptions=['rescan', 'all con types', 'ip scan'], scan=True):
893
+ tableHeaders =["Modules"]
894
+ ip_address = None
895
+ favourite = favouriteOnly
896
+ while True:
897
+ printText("Scanning for modules...")
898
+ if scan and ip_address is None:
899
+ foundDevices = self.qis_scan_devices(scan=scan, favouriteOnly=favourite)
900
+ elif scan and ip_address is not None:
901
+ foundDevices = self.qis_scan_devices(scan=scan, favouriteOnly=favourite, ipAddress=ip_address)
902
+
903
+ myDeviceID = listSelection(title="Select a module",message="Select a module",selectionList=foundDevices,
904
+ additionalOptions= additionalOptions, nice=True, tableHeaders=tableHeaders,
905
+ indexReq=True)
906
+ if myDeviceID in 'rescan':
907
+ favourite = True
908
+ ip_address = None
909
+ continue
910
+ elif myDeviceID in 'all con types':
911
+ favourite = False
912
+ printText("Displaying all connection types...")
913
+ continue
914
+ elif myDeviceID in 'ip scan':
915
+ ip_address = requestDialog(title="Please input the IP Address you would like to scan")
916
+ favourite = False
917
+ continue
918
+ break
919
+
920
+ return myDeviceID
921
+
922
+ def qis_scan_devices(self, scan=True, favouriteOnly=True, ipAddress=None):
923
+ deviceList = []
924
+ foundDevices = "1"
925
+ foundDevices2 = "2" # this is used to check if new modules are being discovered or if all have been found.
926
+ scanWait = 2 # The number of seconds waited between the two scans.
927
+ if self.pythonVersion == '3':
928
+ if scan:
929
+ if ipAddress == None:
930
+ devString = self.sendAndReceiveText(self.sock, '$scan').decode
931
+ else:
932
+ devString = self.sendAndReceiveText(self.sock, '$scan TCP::' + ipAddress).decode
933
+ time.sleep(scanWait)
934
+ while foundDevices not in foundDevices2:
935
+ foundDevices = self.sendAndReceiveText(self.sock, '$list').decode()
936
+ time.sleep(scanWait)
937
+ foundDevices2 = self.sendAndReceiveText(self.sock, '$list').decode()
938
+ else:
939
+ foundDevices = self.sendAndReceiveText(self.sock, '$list').decode()
940
+
941
+ else:
942
+ if scan:
943
+ if ipAddress == None:
944
+ devString = self.sendAndReceiveText(self.sock, '$scan').decode
945
+ else:
946
+ devString = self.sendAndReceiveText(self.sock, '$scan TCP::' + ipAddress).decode
947
+ time.sleep(scanWait)
948
+ while foundDevices not in foundDevices2:
949
+ foundDevices = self.sendAndReceiveText(self.sock, '$list')
950
+ time.sleep(scanWait)
951
+ foundDevices2 = self.sendAndReceiveText(self.sock, '$list')
952
+ else:
953
+ foundDevices = self.sendAndReceiveText(self.sock, '$list')
954
+
955
+ if not "no devices found" in foundDevices.lower():
956
+ foundDevices = foundDevices.replace('>', '')
957
+ #foundDevices = foundDevices.replace(r'\d\) ', '')
958
+ # printText('"' + devString + '"')
959
+ foundDevices = foundDevices.split('\r\n')
960
+ #Can't stream over REST! Removing all REST connections.
961
+ tempList= list()
962
+ for item in foundDevices:
963
+ if item is None or "rest" in item.lower() or item == "":
964
+ pass
965
+ else:
966
+ tempList.append(item.split(")")[1].strip())
967
+ foundDevices = tempList
968
+
969
+ #If favourite only is True then only show one connection type for each module connected.
970
+ #First order the devices in preference type and then pick the first con type found for each module.
971
+ if (favouriteOnly):
972
+ foundDevices = self.sortFavourite(foundDevices)
973
+ else:
974
+ foundDevices = ["***No Devices Found***"]
975
+
976
+ return foundDevices
977
+
978
+ def sortFavourite(self, foundDevices):
979
+ index = 0
980
+ sortedFoundDevices = []
981
+ conPref = ["USB", "TCP", "SERIAL", "REST", "TELNET"]
982
+ while len(sortedFoundDevices) != len(foundDevices):
983
+ for device in foundDevices:
984
+ if conPref[index] in device.upper():
985
+ sortedFoundDevices.append(device)
986
+ index += 1
987
+ foundDevices = sortedFoundDevices
988
+ # new dictionary only containing one favourite connection to each device.
989
+ favConFoundDevices = []
990
+ index = 0
991
+ for device in sortedFoundDevices:
992
+ if (favConFoundDevices == [] or not device.split("::")[1] in str(favConFoundDevices)):
993
+ favConFoundDevices.append(device)
994
+ foundDevices = favConFoundDevices
995
+ return foundDevices
996
+
997
+ # Query stream status for a device attached to backend
998
+ # The objects connection needs to be opened (connect()) before this is used
999
+ def streamRunningStatus(self, device, sock=None):
1000
+ if sock == None:
1001
+ sock = self.sock
1002
+ index = 0 # index of relevant line in split string
1003
+ if self.pythonVersion == '3':
1004
+ streamStatus = self.sendAndReceiveText(sock, 'stream?', device).decode()
1005
+ else:
1006
+ streamStatus = self.sendAndReceiveText(sock, 'stream?', device)
1007
+ streamStatus = streamStatus.split('\r\n')
1008
+ streamStatus[index] = re.sub(r':', '', streamStatus[index]) #remove :
1009
+ return streamStatus[index]
1010
+
1011
+ # Query stream buffer status for a device attached to backend
1012
+ # The objects connection needs to be opened (connect()) before this is used
1013
+ def streamBufferStatus(self, device, sock=None):
1014
+ if sock == None:
1015
+ sock = self.sock
1016
+ index = 1 # index of relevant line in split string
1017
+ if self.pythonVersion == '3':
1018
+ streamStatus = self.sendAndReceiveText(sock, 'stream?', device).decode()
1019
+ else:
1020
+ streamStatus = self.sendAndReceiveText(sock, 'stream?', device)
1021
+ streamStatus = streamStatus.split('\r\n')
1022
+ streamStatus[index] = re.sub(r'^Stripes Buffered: ', '', streamStatus[index])
1023
+ return streamStatus[index]
1024
+
1025
+ # TODO: MD - This function should be replaced with a more generic method of accessing the header
1026
+ # The return of a string with concatenated value and units should be replaced with something easier to parse
1027
+ #
1028
+ # Get the averaging used on the last/current stream
1029
+ # The objects connection needs to be opened (connect()) before this is used
1030
+ def streamHeaderAverage(self, device, sock=None):
1031
+ try:
1032
+ if sock == None:
1033
+ sock = self.sock
1034
+ index = 2 # index of relevant line in split string
1035
+ if self.pythonVersion == '3':
1036
+ streamStatus = self.sendAndReceiveText(sock, sentText='stream text header', device=device).decode()
1037
+ else:
1038
+ streamStatus = self.sendAndReceiveText(sock, sentText='stream text header', device=device)
1039
+
1040
+ self.qps_stream_header = streamStatus
1041
+
1042
+ # Check for the header format. If XML, process here
1043
+ if (self.isXmlHeader(streamStatus)):
1044
+ # Get the basic averaging rate (V3 header)
1045
+ xml_root = self.getStreamXmlHeader(device=device, sock=sock)
1046
+
1047
+ # For QPS streaming, stream header v3 command has already been issued before this
1048
+ self.module_xml_header = xml_root
1049
+
1050
+ # Return the time based averaging string
1051
+ device_period = xml_root.find('.//devicePeriod')
1052
+ if device_period == None:
1053
+ device_period = xml_root.find('.//devicePerioduS')
1054
+ if device_period == None:
1055
+ device_period = xml_root.find('.//mainPeriod')
1056
+ averageStr = device_period.text
1057
+ return averageStr
1058
+ # For legacy text headers, process here
1059
+ else:
1060
+ streamStatus = streamStatus.split('\r\n')
1061
+ if('Header Not Available' in streamStatus[0]):
1062
+ dummy = streamStatus[0] + '. Check stream has been run on device.'
1063
+ return dummy
1064
+ streamStatus[index] = re.sub(r'^Average: ', '', streamStatus[index])
1065
+ avg = streamStatus[index]
1066
+ avg = 2 ** int(avg)
1067
+ return '{}'.format(avg)
1068
+ except Exception as e:
1069
+ logging.error(device + ' Unable to get stream average.' + self.host + ':' + str(self.port))
1070
+ raise e
1071
+
1072
+ # Get the version of the stream and convert to string for the specified device
1073
+ # The objects connection needs to be opened (connect()) before this is used
1074
+ def streamHeaderVersion(self, device, sock=None):
1075
+ try:
1076
+ if sock == None:
1077
+ sock = self.sock
1078
+ index = 0 # index of relevant line in split string
1079
+ if self.pythonVersion == '3':
1080
+ streamStatus = self.sendAndReceiveText(sock,'stream text header', device).decode()
1081
+ else:
1082
+ streamStatus = self.sendAndReceiveText(sock,'stream text header', device)
1083
+ streamStatus = streamStatus.split('\r\n')
1084
+ if 'Header Not Available' in streamStatus[0]:
1085
+ str = streamStatus[0] + '. Check stream has been ran on device.'
1086
+ logging.error(str)
1087
+ return str
1088
+ version = re.sub(r'^Version: ', '', streamStatus[index])
1089
+ if version == '3':
1090
+ version = 'Original PPM'
1091
+ elif version == '4':
1092
+ version = 'XLC PPM'
1093
+ elif version == '5':
1094
+ version = 'HD PPM'
1095
+ else:
1096
+ version = 'Unknown stream version'
1097
+ return version
1098
+ except Exception as e:
1099
+ logging.error(device + ' Unable to get stream version.' + self.host + ':' + str(self.port))
1100
+ raise e
1101
+
1102
+ # Get a header string giving which measurements are returned in the string for the specified device
1103
+ # The objects connection needs to be opened (connect()) before this is used
1104
+ def streamHeaderFormat(self, device, sock=None):
1105
+ try:
1106
+ if sock == None:
1107
+ sock = self.sock
1108
+ index = 1 # index of relevant line in split string STREAM MODE HEADER [?|V1,V2,V3]
1109
+ if self.pythonVersion == '3':
1110
+ streamStatus = self.sendAndReceiveText(sock,'stream text header', device).decode()
1111
+ else:
1112
+ streamStatus = self.sendAndReceiveText(sock,'stream text header', device)
1113
+ # Check if this is a new XML form header
1114
+ if (self.isXmlHeader (streamStatus)):
1115
+ # Get the basic averaging rate (V3 header)
1116
+ xml_root = self.getStreamXmlHeader (device=device, sock=sock)
1117
+ # Return the time based averaging string
1118
+ device_period = xml_root.find('.//devicePeriod')
1119
+ time_unit = 'uS'
1120
+ if device_period == None:
1121
+ device_period = xml_root.find('.//devicePerioduS')
1122
+ if device_period == None:
1123
+ device_period = xml_root.find('.//mainPeriod')
1124
+ if ('ns' in device_period.text):
1125
+ time_unit = 'nS'
1126
+ averageStr = device_period.text
1127
+
1128
+ # Time column always first
1129
+ formatHeader = 'Time ' + time_unit + ','
1130
+ # Find the channels section of each group and iterate through it to add the channel columns
1131
+ for group in xml_root.iter():
1132
+ if (group.tag == "channels"):
1133
+ for chan in group:
1134
+ # Avoid children that are not named channels
1135
+ if (chan.find('.//name') is not None):
1136
+ nameStr = chan.find('.//name').text
1137
+ unitStr = chan.find('.//units').text
1138
+ formatHeader = formatHeader + nameStr + " " + unitStr + ","
1139
+ return formatHeader
1140
+ # Handle legacy text headers here
1141
+ else:
1142
+ streamStatus = streamStatus.split('\r\n')
1143
+ if 'Header Not Available' in streamStatus[0]:
1144
+ str = streamStatus[0] + '. Check stream has been ran on device.'
1145
+ logging.error(str)
1146
+ return str
1147
+ if self.pythonVersion == '3':
1148
+ outputMode = self.sendAndReceiveText(sock,'Config Output Mode?', device).decode()
1149
+ powerMode = self.sendAndReceiveText(sock,'stream mode power?', device).decode()
1150
+ else:
1151
+ outputMode = self.sendAndReceiveText(sock,'Config Output Mode?', device)
1152
+ powerMode = self.sendAndReceiveText(sock,'stream mode power?', device)
1153
+ format = int(re.sub(r'^Format: ', '', streamStatus[index]))
1154
+ b0 = 1 #12V_I
1155
+ b1 = 1 << 1 #12V_V
1156
+ b2 = 1 << 2 #5V_I
1157
+ b3 = 1 << 3 #5V_V
1158
+ formatHeader = 'StripeNum, Trig, '
1159
+ if format & b3:
1160
+ if ('3V3' in outputMode):
1161
+ formatHeader = formatHeader + '3V3_V,'
1162
+ else:
1163
+ formatHeader = formatHeader + '5V_V,'
1164
+ if format & b2:
1165
+ if ('3V3' in outputMode):
1166
+ formatHeader = formatHeader + ' 3V3_I,'
1167
+ else:
1168
+ formatHeader = formatHeader + ' 5V_I,'
1169
+
1170
+ if format & b1:
1171
+ formatHeader = formatHeader + ' 12V_V,'
1172
+ if format & b0:
1173
+ formatHeader = formatHeader + ' 12V_I'
1174
+ if 'Enabled' in powerMode:
1175
+ if ('3V3' in outputMode):
1176
+ formatHeader = formatHeader + ' 3V3_P'
1177
+ else:
1178
+ formatHeader = formatHeader + ' 5V_P'
1179
+ if ((format & b1) or (format & b0)):
1180
+ formatHeader = formatHeader + ' 12V_P'
1181
+ return formatHeader
1182
+ except Exception as e:
1183
+ logging.error(device + ' Unable to get stream format.' + self.host + ':' + '{}'.format(self.port))
1184
+ raise e
1185
+
1186
+ # Get stripes out of the backends stream buffer for the specified device using text commands
1187
+ # The objects connection needs to be opened (connect()) before this is used
1188
+ def streamGetStripesText(self, sock, device, numStripes=4096, skipStatusCheck=False):
1189
+
1190
+ bufferStatus = False
1191
+ # Allows the status check to be skipped when emptying the buffer after streaming has stopped (saving time)
1192
+ if (skipStatusCheck == False):
1193
+ if self.pythonVersion == '3':
1194
+ streamStatus = self.sendAndReceiveText(sock, 'stream?', device).decode()
1195
+ else:
1196
+ streamStatus = self.sendAndReceiveText(sock, 'stream?', device)
1197
+ if ('Overrun' in streamStatus) or ('8388608 of 8388608' in streamStatus):
1198
+ bufferStatus = True
1199
+ stripes = self.sendAndReceiveText(sock, 'stream text all', device, readUntilCursor=True)
1200
+ # time.sleep(0.001)
1201
+ if stripes[-1:] != self.cursor:
1202
+ return "Error no cursor returned."
1203
+ else:
1204
+ if self.pythonVersion == '3':
1205
+ endOfFile = 'eof\r\n>'
1206
+ genEndOfFile = endOfFile.encode()
1207
+ else:
1208
+ genEndOfFile = 'eof\r\n>'
1209
+ if stripes[-6:] == genEndOfFile:
1210
+ removeChar = -6
1211
+ else:
1212
+ removeChar = -1
1213
+
1214
+ # stripes = stripes.split('\r\n')
1215
+ # stripes = filter(None, stripes) #remove empty sting elements
1216
+ #printText(stripes)
1217
+ return bufferStatus, removeChar, stripes
1218
+
1219
+ def avgStringFromPwr(self, avgPwrTwo):
1220
+ if(avgPwrTwo==0):
1221
+ return '0'
1222
+ elif(avgPwrTwo==1):
1223
+ return '2'
1224
+ elif(avgPwrTwo > 1 and avgPwrTwo < 10 ):
1225
+ avg = 2 ** int(avgPwrTwo)
1226
+ return '{}'.format(avg)
1227
+ elif(avgPwrTwo==10):
1228
+ return '1k'
1229
+ elif(avgPwrTwo==11):
1230
+ return '2k'
1231
+ elif(avgPwrTwo==12):
1232
+ return '4k'
1233
+ elif(avgPwrTwo==13):
1234
+ return '8k'
1235
+ elif(avgPwrTwo==14):
1236
+ return '16k'
1237
+ elif(avgPwrTwo==15):
1238
+ return '32k'
1239
+ else:
1240
+ return 'Invalid Average Value'
1241
+
1242
+ # TODO: MD Thinks this implements software averaging, is unused and now performed in QIS
1243
+ # Works out average values of timescales longer than max device averaging
1244
+ def averageStripes(self, leftover, streamAverage, newStripes, f, remainingStripes = []):
1245
+ newString = str(newStripes)
1246
+ newList = []
1247
+ if remainingStripes == []:
1248
+ newList = newString.split('\r\n')
1249
+ else:
1250
+ newList = remainingStripes
1251
+ newList.extend(newString.split('\r\n'))
1252
+ numElements = newList[0].count(' ') + 1
1253
+ streamTotalAverage = leftover + streamAverage
1254
+ splitList = [] * numElements
1255
+ if len(newList) < streamTotalAverage:
1256
+ remainingStripes = newList[:-1]
1257
+ return leftover, remainingStripes
1258
+ runningAverage = [0] * (len(newList[0].split(' ')) - 2)
1259
+ j = 0
1260
+ z = 1
1261
+ for i in newList[:-1]:
1262
+ splitList = i.split(' ')
1263
+ splitNumbers = [int(x) for x in splitList[2:]]
1264
+ runningAverage = [sum(x) for x in zip(runningAverage, splitNumbers)]
1265
+ if z == math.floor(streamTotalAverage):
1266
+ finalAverage = splitList[0:2] + [str(round(x / streamAverage)) for x in runningAverage]
1267
+ for counter in xrange(len(finalAverage)-1):
1268
+ finalAverage[counter] = finalAverage[counter] + ' '
1269
+ if self.pythonVersion == '3':
1270
+ finalAverage = finalAverage.encode
1271
+ for x in finalAverage:
1272
+ f.write(x)
1273
+ f.write('\r\n')
1274
+ streamTotalAverage += streamAverage
1275
+ j += 1
1276
+ z += 1
1277
+ remainingStripes = newList[int(math.floor(j * streamAverage + leftover)):-1]
1278
+ leftover = (streamTotalAverage - streamAverage) % 1
1279
+ return leftover, remainingStripes
1280
+
1281
+ def deviceMulti(self, device):
1282
+ if (device in self.deviceList):
1283
+ return self.deviceList.index(device)
1284
+ else:
1285
+ self.listSemaphore.acquire()
1286
+ self.deviceList.append(device)
1287
+ self.stopFlagList.append(True)
1288
+ self.listSemaphore.release()
1289
+ return self.deviceList.index(device)
1290
+
1291
+ def deviceDictSetup(self, module):
1292
+ if module in self.deviceDict.keys():
1293
+ return
1294
+ elif module == 'QIS':
1295
+ self.dictSemaphore.acquire()
1296
+ self.deviceDict[module] = [False, 'Disconnected', "No attempt to connect to QIS yet"]
1297
+ self.dictSemaphore.release()
1298
+ else:
1299
+ self.dictSemaphore.acquire()
1300
+ self.deviceDict[module] = [False, 'Stopped', "User hasn't started stream"]
1301
+ self.dictSemaphore.release()
1302
+
1303
+ def streamInterrupt(self):
1304
+ for key in self.deviceDict.keys():
1305
+ if self.deviceDict[key][0]:
1306
+ return True
1307
+ return False
1308
+
1309
+ def interruptList(self):
1310
+ streamIssueList = []
1311
+ for key in self.deviceDict.keys():
1312
+ if self.deviceDict[key][0]:
1313
+ streamIssue = [key]
1314
+ streamIssue.append(self.deviceDict[key][1])
1315
+ streamIssue.append(self.deviceDict[key][2])
1316
+ streamIssueList.append(streamIssue)
1317
+ return streamIssueList
1318
+
1319
+ def waitStop(self):
1320
+ running = 1
1321
+ while running != 0:
1322
+ threadNameList = []
1323
+ for t1 in threading.enumerate():
1324
+ threadNameList.append(t1.name)
1325
+ running = 0
1326
+ for module in self.deviceList:
1327
+ if (module in threadNameList):
1328
+ running += 1
1329
+ time.sleep(0.5)
1330
+ time.sleep(1)
1331
+
1332
+ def convertStreamAverage (self, streamAveraging):
1333
+ returnValue = 32000;
1334
+ if ("k" in streamAveraging):
1335
+ returnValue = streamAveraging.replace("k", "000")
1336
+ else:
1337
+ returnValue = streamAveraging
1338
+
1339
+ return returnValue
1340
+
1341
+ # Pass in a stream header and we check if it is XML or legacy format
1342
+ def isXmlHeader (self, headerText):
1343
+ if('?xml version=' not in headerText):
1344
+ return False;
1345
+ else:
1346
+ return True
1347
+
1348
+ # Internal function. Gets the stream header and parses it into useful information
1349
+ def getStreamXmlHeader (self, device, sock=None):
1350
+ try:
1351
+ if sock == None:
1352
+ sock = self.sock
1353
+
1354
+ # Get the raw data
1355
+ if self.pythonVersion == '3':
1356
+ headerData = self.sendAndReceiveText(sock, sentText='stream text header', device=device).decode()
1357
+ else:
1358
+ headerData = self.sendAndReceiveText(sock, sentText='stream text header', device=device)
1359
+
1360
+ # The XML can contain the cursor on the end! Trap and remove it here TODO: Needs fixed in the command layer above
1361
+ if ('\r\n>' in headerData):
1362
+ headerData = headerData[:-1]
1363
+
1364
+ # Check for no header (no stream started)
1365
+ if('Header Not Available' in headerData):
1366
+ logging.error(device + ' Stream header not available.' + self.host + ':' + str(self.port))
1367
+ return None;
1368
+
1369
+ # Check for XML format
1370
+ if('?xml version=' not in headerData):
1371
+ logging.error(device + ' Header not in XML form.' + self.host + ':' + str(self.port))
1372
+ return None;
1373
+
1374
+ # Parse XML into structured format
1375
+ xml_root = ET.fromstring(headerData)
1376
+
1377
+ # Check header format is supported by quarchpy
1378
+ versionStr = xml_root.find('.//version').text
1379
+ if ('V3' not in versionStr):
1380
+ logging.error(device + ' Stream header version not compatible: ' + xml_root['version'].text + '.' + self.host + ':' + str(self.port))
1381
+ raise Exception ("Stream header version not supported");
1382
+
1383
+ # Return the XML structure for the code to use
1384
+ return xml_root
1385
+
1386
+ except Exception as e:
1387
+ logging.error(device + ' Exception while parsing stream header XML.' + self.host + ':' + str(self.port))
1388
+ raise e
1389
+
1390
+ def create_dir_structure(self, module, directory=None):
1391
+ """
1392
+ Creates the QPS directory structure and (empty) files to be written to
1393
+
1394
+ I've put a bunch of try-except just to be sure the directory is correctly created.
1395
+ ( There's probably a better way of doing this than this )
1396
+
1397
+ :param: module: String - Module string
1398
+ :param: directory: String - Name of directory for QPS stream (defaults to default recording location if invalid)
1399
+ :return: success: Boolean - Was the file structure created successfully?
1400
+ """
1401
+
1402
+ directory = self.create_qps_directory(directory)
1403
+
1404
+ digital_count = 0
1405
+ non_dig_counter = 0
1406
+ self.streamGroups = StreamGroups()
1407
+ for index, i in enumerate(self.module_xml_header.findall('.//channels')):
1408
+ self.streamGroups.add_group(index)
1409
+ for item in i.findall('.//channel'):
1410
+ self.streamGroups.groups[index].add_channel(item.find(".//name"), item.find(".//group"), item.find(".//dataPosition"))
1411
+ if item.find(".//group").text == "Digital":
1412
+ digital_count += 1
1413
+ self.has_digitals = True
1414
+ else:
1415
+ non_dig_counter += 1
1416
+
1417
+ # Inner folders for analogue and digital signals streaming
1418
+ in_folder_analogue = "data000"
1419
+ try:
1420
+ inner_path_analogues = os.path.join(directory, in_folder_analogue)
1421
+ os.mkdir(inner_path_analogues)
1422
+ except:
1423
+ logging.warning(f"Failed to make inner directory for analogue signals {inner_path_analogues}")
1424
+ return False
1425
+
1426
+ in_folder_digitals = "data101"
1427
+ if self.has_digitals:
1428
+ try:
1429
+ inner_path_digitals = os.path.join(directory, in_folder_digitals)
1430
+ os.mkdir(inner_path_digitals)
1431
+ except:
1432
+ logging.warning(f"Failed to make inner directory for digital signals {inner_path_digitals}")
1433
+ return False
1434
+
1435
+ logging.debug(f"Steaming to : {self.qps_record_dir_path}")
1436
+
1437
+ logging.debug("Creating qps data files")
1438
+ try:
1439
+ for i in range(non_dig_counter):
1440
+ file_name = f"data000_00{i}_000000000"
1441
+ f = open(os.path.join(inner_path_analogues, file_name), "w")
1442
+ f.close()
1443
+ for i in range(digital_count):
1444
+ x = i
1445
+ while len(str(x)) < 3:
1446
+ x = "0" + str(x)
1447
+ file_name = f"data101_{x}_000000000"
1448
+ f = open(os.path.join(inner_path_digitals, file_name), "w")
1449
+ f.close()
1450
+ except:
1451
+ logging.warning(f"failed to create qps data files for analogue signals")
1452
+ return False
1453
+
1454
+ logging.debug("Finished creating qps data files")
1455
+
1456
+ logging.debug("Creating qps upper level files")
1457
+ try:
1458
+ file_names = ["annotations.xml", "notes.txt", "triggers.txt"]
1459
+ for file_nome in file_names:
1460
+ f = open(os.path.join(self.qps_record_dir_path, file_nome), "w")
1461
+ f.close()
1462
+ except Exception as err:
1463
+ logging.warning(f"failed to create qps upper level files, {err}")
1464
+ return False
1465
+
1466
+ try:
1467
+ # Adding data000.idx separate as it's written in bytes not normal text
1468
+ f = open(os.path.join(self.qps_record_dir_path, "data000.idx"), "wb")
1469
+ f.close()
1470
+ if digital_count > 0:
1471
+ f = open(os.path.join(self.qps_record_dir_path, "data101.idx"), "wb")
1472
+ f.close()
1473
+ except Exception as err:
1474
+ logging.warning(f"failed to create data000.idx file, {err}")
1475
+ return False
1476
+
1477
+ logging.debug("Finished creating QPS dir structure")
1478
+
1479
+ return True
1480
+
1481
+ def create_qps_directory(self, directory):
1482
+ folder_name = None
1483
+ # Checking if there was a directory passed; and if it's a valid directory
1484
+ if not directory:
1485
+ directory = os.path.join(str(Path.home()), "AppData", "Local", "Quarch", "QPS", "Recordings")
1486
+ logging.debug("No directory specified")
1487
+ elif not os.path.isdir(directory):
1488
+ new_dir = os.path.join(str(Path.home()), "AppData", "Local", "Quarch", "QPS", "Recordings")
1489
+ logging.warning(f"{directory} was not a valid directory, streaming to default location: \n{new_dir}")
1490
+ directory = new_dir
1491
+ else:
1492
+ # Split the directory into a path of folders
1493
+ folder_name = str(directory).split(os.sep)
1494
+ # last folder name is the name we want
1495
+ folder_name = folder_name[-1]
1496
+ # Make it known to the entire class that the path we're streaming to is the one sent across by the user
1497
+ self.qps_record_dir_path = directory
1498
+
1499
+ # If no folder name for the stream was passed, then default to 'quarchpy_recording' and a timestamp
1500
+ if not folder_name:
1501
+ folder_name = "quarchpy_recording"
1502
+ folder_name = f"{folder_name}-{time.time()}"
1503
+ path = os.path.join(directory, self.qps_stream_folder_name)
1504
+ os.mkdir(path)
1505
+ self.qps_record_dir_path = path
1506
+
1507
+ self.qps_stream_folder_name = folder_name
1508
+
1509
+ return directory
1510
+
1511
+ def create_index_file(self):
1512
+ """
1513
+ Create the necessary index file for QPS data000.idx
1514
+
1515
+ For future revisions, this should be updated if there are file limits on each data file
1516
+ Current implementation assumes only 1 of each data file are made.
1517
+
1518
+ No Return./
1519
+ """
1520
+
1521
+ stream_header_size = -1
1522
+
1523
+ my_byte_array = []
1524
+
1525
+ # tree = ET.ElementTree(ET.fromstring(self.module_xml_header[:-1]))
1526
+ tree = self.module_xml_header
1527
+
1528
+ return_b_array = []
1529
+ outBuffer = []
1530
+ x = 20
1531
+ stream_header_size = 20
1532
+
1533
+ temp_dict = {"channels": 0}
1534
+
1535
+ return_b_array, stream_header_size = self.add_header_to_byte_array(return_b_array, stream_header_size,
1536
+ temp_dict, tree, is_digital=False)
1537
+
1538
+ self.add_header_to_buffer(outBuffer, return_b_array, stream_header_size, temp_dict)
1539
+
1540
+ # Attempting to read the size of the first file in data files
1541
+ file = os.path.join(self.qps_record_dir_path, "data000", "data000_000_000000000")
1542
+ data = None
1543
+ with open(file, "rb") as f:
1544
+ data = f.read() # if you only wanted to read 512 bytes, do .read(512)
1545
+
1546
+ if not data:
1547
+ raise "No data written to file"
1548
+
1549
+ num_records = len(data) / 8
1550
+ logging.debug(f"num_record = {num_records}")
1551
+ return_b_array.append(int(num_records).to_bytes(4, byteorder='big'))
1552
+
1553
+ start_number = 0
1554
+ logging.debug(f"start_record = {start_number}")
1555
+ return_b_array.append(start_number.to_bytes(8, byteorder='big'))
1556
+
1557
+ num_records = num_records - 1
1558
+ logging.debug(f"last_Record_number = {num_records}")
1559
+ return_b_array.append(int(num_records).to_bytes(8, byteorder='big'))
1560
+
1561
+ # Add names of every file in data000 dir here.
1562
+ files = os.listdir(os.path.join(self.qps_record_dir_path, "data000"))
1563
+ for file3 in files:
1564
+ # print(file)
1565
+ item = strToBb(file3, False)
1566
+ # print(item)
1567
+ while len(item) < 32:
1568
+ item.append("\x00")
1569
+ # print(item)
1570
+ return_b_array.append(item)
1571
+
1572
+ with open(os.path.join(self.qps_record_dir_path, "data000.idx"), "ab") as f:
1573
+ for item in outBuffer:
1574
+ # print(item)
1575
+ # print(type(item))
1576
+ f.write(bytes(item))
1577
+ # f.write(outBuffer)
1578
+
1579
+ with open(os.path.join(self.qps_record_dir_path, "data000.idx"), "ab") as f:
1580
+ self.write_b_array_to_idx_file(f, return_b_array)
1581
+
1582
+ def create_index_file_digitals(self):
1583
+ """
1584
+ Create the necessary index file for QPS data101.idx
1585
+
1586
+ For future revisions, this should be updated if there are file limits on each data file
1587
+ Current implementation assumes only 1 of each data file are made.
1588
+
1589
+ No Return.
1590
+ """
1591
+
1592
+ stream_header_size = -1
1593
+ my_byte_array = []
1594
+ tree = self.module_xml_header
1595
+ return_b_array = []
1596
+ outBuffer = []
1597
+ temp_dict = {}
1598
+
1599
+ return_b_array, stream_header_size = self.add_header_to_byte_array(return_b_array, stream_header_size,
1600
+ temp_dict, tree, is_digital=True)
1601
+
1602
+ self.add_header_to_buffer(outBuffer, return_b_array, stream_header_size, temp_dict)
1603
+
1604
+ # Attempting to read the size of the first file in data files
1605
+ file = os.path.join(self.qps_record_dir_path, "data101", "data101_000_000000000")
1606
+ data = None
1607
+ with open(file, "rb") as f:
1608
+ data = f.read() # if you only wanted to read 512 bytes, do .read(512)
1609
+
1610
+ if not data:
1611
+ raise "No data written to file"
1612
+
1613
+ num_records = len(data) / 8
1614
+ logging.debug(f"num_record = {num_records}")
1615
+ return_b_array.append(int(num_records).to_bytes(4, byteorder='big'))
1616
+
1617
+ start_number = 0
1618
+ logging.debug(f"start_record = {start_number}")
1619
+ return_b_array.append(start_number.to_bytes(8, byteorder='big'))
1620
+
1621
+ num_records = num_records - 1
1622
+ logging.debug(f"last_Record_number = {num_records}")
1623
+ return_b_array.append(int(num_records).to_bytes(8, byteorder='big'))
1624
+
1625
+ # Add names of every file in data000 dir here.
1626
+ files = os.listdir(os.path.join(self.qps_record_dir_path, "data101"))
1627
+ for file3 in files:
1628
+ # print(file)
1629
+ item = strToBb(file3, False)
1630
+ # print(item)
1631
+ while len(item) < 32:
1632
+ item.append("\x00")
1633
+ # print(item)
1634
+ return_b_array.append(item)
1635
+
1636
+ with open(os.path.join(self.qps_record_dir_path, "data101.idx"), "ab") as f:
1637
+ for item in outBuffer:
1638
+ f.write(bytes(item))
1639
+
1640
+ with open(os.path.join(self.qps_record_dir_path, "data101.idx"), "ab") as f:
1641
+ self.write_b_array_to_idx_file(f, return_b_array)
1642
+
1643
+ def add_header_to_byte_array(self, return_b_array, stream_header_size, temp_dict, tree, is_digital=False):
1644
+ for element in tree:
1645
+ if "legacyVersion" in element.tag:
1646
+ intItem = element.text
1647
+ temp_dict[element.tag] = intItem
1648
+ # my_byte_array.append(int.to_bytes(intItem, 'big'))
1649
+ if "legacyAverage" in element.tag:
1650
+ intItem = element.text
1651
+ temp_dict[element.tag] = intItem
1652
+ # my_byte_array.append(int.to_bytes(intItem, 'big'))
1653
+ if "legacyFormat" in element.tag:
1654
+ intItem = element.text
1655
+ temp_dict[element.tag] = intItem
1656
+ # my_byte_array.append(int.to_bytes(intItem, 'big'))
1657
+ if "mainPeriod" in element.tag:
1658
+ intItem = element.text
1659
+ intItem = intItem[:-2]
1660
+ temp_dict[element.tag] = intItem
1661
+ if "channels" in element.tag:
1662
+ counter = 0
1663
+ for child in element:
1664
+ for child2 in child:
1665
+ if "group" in child2.tag:
1666
+ if is_digital:
1667
+ if str(child2.text).lower() == "digital":
1668
+ counter += 1
1669
+ else:
1670
+ if str(child2.text).lower() != "digital":
1671
+ counter += 1
1672
+
1673
+ temp_dict[element.tag] = counter
1674
+
1675
+ return_b_array = []
1676
+
1677
+ stream_header_size = 20
1678
+
1679
+ # Cycle through all the channels.
1680
+ for child in element:
1681
+
1682
+ if child.tag == "groupId":
1683
+ continue
1684
+
1685
+ if is_digital:
1686
+ # skip channel if we're only looking for digitals
1687
+ if not str(child.find(".//group").text).lower() == "digital":
1688
+ continue
1689
+ else:
1690
+ # skip if we're looking for analogues
1691
+ if str(child.find(".//group").text).lower() == "digital":
1692
+ continue
1693
+
1694
+ # my_byte_array.append(int.to_bytes(5, 'big'))
1695
+ return_b_array.append(int(5).to_bytes(4, byteorder='big'))
1696
+ stream_header_size += 4
1697
+ name = None
1698
+
1699
+ for child2 in child:
1700
+
1701
+ if "group" in child2.tag:
1702
+ my_byte_array = strToBb(str(child2.text))
1703
+ return_b_array.append(my_byte_array)
1704
+ # QPS index file requires name tag come after group tag.
1705
+ return_b_array.append(name)
1706
+ stream_header_size += len(my_byte_array)
1707
+
1708
+ if "name" in child2.tag:
1709
+ my_byte_array = strToBb(str(child2.text))
1710
+ name = my_byte_array
1711
+ stream_header_size += len(my_byte_array)
1712
+
1713
+ if "units" in child2.tag:
1714
+ my_byte_array = strToBb(str(child2.text))
1715
+ return_b_array.append(my_byte_array)
1716
+ stream_header_size += len(my_byte_array)
1717
+
1718
+ """
1719
+ # Unclear if the only thing here is TRUE
1720
+ bb = strToBB( Boolean.toString( cdr.isUsePrefixStr() ));
1721
+ bbList.add(bb);
1722
+ retVal += bb.capacity();
1723
+ """
1724
+ my_byte_array = strToBb(str("true"))
1725
+ return_b_array.append(my_byte_array)
1726
+ stream_header_size += len(my_byte_array)
1727
+
1728
+ if "maxTValue" in child2.tag:
1729
+ my_byte_array = strToBb(str(child2.text))
1730
+ return_b_array.append(my_byte_array)
1731
+ stream_header_size += len(my_byte_array)
1732
+
1733
+ return return_b_array, stream_header_size
1734
+
1735
+ def add_header_to_buffer(self, outBuffer, return_b_array, stream_header_size, temp_dict):
1736
+ number = 2
1737
+ outBuffer.append(number.to_bytes(4, byteorder='big'))
1738
+ logging.debug(f"indexVersion : {number}")
1739
+
1740
+ number = 1 if self.has_digitals else 0
1741
+ outBuffer.append(number.to_bytes(4, byteorder='big'))
1742
+ logging.debug(f"value0 : {number}")
1743
+ number = stream_header_size
1744
+ outBuffer.append(number.to_bytes(4, byteorder='big'))
1745
+ logging.debug(f"header_size : {number}")
1746
+ logging.debug(f"legacyVersion : {temp_dict['legacyVersion']}")
1747
+ outBuffer.append(int(temp_dict["legacyVersion"]).to_bytes(4, byteorder='big'))
1748
+ logging.debug(f"legacyAverage : {temp_dict['legacyAverage']}")
1749
+ outBuffer.append(int(temp_dict["legacyAverage"]).to_bytes(4, byteorder='big'))
1750
+ logging.debug(f"legacyFormat : {temp_dict['legacyFormat']}")
1751
+ outBuffer.append(int(temp_dict["legacyFormat"]).to_bytes(4, byteorder='big'))
1752
+ logging.debug(f"mainPeriod : {temp_dict['mainPeriod']}")
1753
+ outBuffer.append(int(temp_dict["mainPeriod"]).to_bytes(4, byteorder='big'))
1754
+ logging.debug(f"channels : {temp_dict['channels']}")
1755
+ outBuffer.append(int(temp_dict["channels"]).to_bytes(4, byteorder='big'))
1756
+ return_b_array.append(int(self.qps_record_start_time).to_bytes(8, byteorder='big'))
1757
+ index_record_state = True
1758
+ logging.debug(int(1))
1759
+ return_b_array.append(int(1).to_bytes(1, byteorder='big'))
1760
+ record_type = 1
1761
+ logging.debug(f"record type : {int(index_record_state)}")
1762
+ return_b_array.append(int(record_type).to_bytes(1, byteorder='big'))
1763
+
1764
+ def write_b_array_to_idx_file(self, f, return_b_array):
1765
+ # print(return_b_array)
1766
+ for item in return_b_array:
1767
+ # print(item)
1768
+ if isinstance(item, int):
1769
+ # 'f.write(str(item).encode())
1770
+ # print(item)
1771
+ f.write(bytes([item]))
1772
+ continue
1773
+ if isinstance(item, bytes):
1774
+ # print(item)
1775
+ f.write(bytes(item))
1776
+ continue
1777
+ if isinstance(item, list):
1778
+ for character in item:
1779
+ if isinstance(character, int):
1780
+ f.write(bytes([character]))
1781
+ continue
1782
+ elif isinstance(item, bytes):
1783
+ f.write(item)
1784
+ continue
1785
+ else:
1786
+ f.write(str(character).encode())
1787
+ continue
1788
+
1789
+ def create_qps_file(self, module):
1790
+ """
1791
+ Creates the end QPS file that is used to open QPS
1792
+
1793
+ :param module: Module QTL number that was used for the stream
1794
+ :return:
1795
+ """
1796
+
1797
+ with open(os.path.join(self.qps_record_dir_path, self.qps_stream_folder_name + ".qps"), "w") as f:
1798
+ x = datetime.datetime.fromtimestamp(self.qps_record_start_time / 1000.0)
1799
+ x = str(x).split(".")
1800
+ x = x[0]
1801
+ x = x.replace("-", " ")
1802
+ f.write(f"Started: {x}\n")
1803
+ f.write(f"Device: {module}\n")
1804
+ f.write("Fixture: \n")
1805
+
1806
+ x = datetime.datetime.now()
1807
+ x = str(x).split(".")
1808
+ x = x[0]
1809
+ x = x.replace("-", " ")
1810
+ f.write(f"Saved: {x}\n")
1811
+
1812
+
1813
+ def strToBb(string_in, add_length=True):
1814
+ length = len(str(string_in))
1815
+ b_array = []
1816
+ if add_length:
1817
+ b_array.append(length)
1818
+ for character in str(string_in):
1819
+ b_array.append(character)
1820
+
1821
+ return b_array