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