quarchpy 2.1.11.dev8__py3-none-any.whl → 2.2.17.dev2__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 (733) hide show
  1. quarchpy/__init__.py +60 -8
  2. quarchpy/_version.py +1 -1
  3. quarchpy/config_files/Cable_Modules/QTL1253-01 - Mini SAS Module Config v3.5 c1.3.qfg +196 -196
  4. quarchpy/config_files/Cable_Modules/QTL1253-01 - Mini SAS Module Config v4.000 c1.3.qfg +198 -198
  5. quarchpy/config_files/Cable_Modules/QTL1253-02 - Mini SAS Module Config v3.5 c1.3.qfg +196 -196
  6. quarchpy/config_files/Cable_Modules/QTL1253-02 - Mini SAS Module Config v4.000 c1.3.qfg +198 -198
  7. quarchpy/config_files/Cable_Modules/QTL1253-xx - Mini SAS Module Config v4.003 c1.6.qfg +197 -197
  8. quarchpy/config_files/Cable_Modules/QTL1271-xx - Ethernet Module Config v3.5 c1.2.qfg +188 -188
  9. quarchpy/config_files/Cable_Modules/QTL1271-xx - Ethernet Module Config v4.000 c1.2.qfg +190 -190
  10. quarchpy/config_files/Cable_Modules/QTL1271-xx - Ethernet Module Config v4.100 c1.3.qfg +189 -189
  11. quarchpy/config_files/Cable_Modules/QTL1292-xx - SFP+ Cable Pull Module Config v4.000 c1.1.qfg +154 -154
  12. quarchpy/config_files/Cable_Modules/QTL1309-04 - USB 3.0 Module Config v4.003 c1.2.qfg +187 -187
  13. quarchpy/config_files/Cable_Modules/QTL1309-xx - USB 3.0 Module Config v3.5 c1.1.qfg +187 -187
  14. quarchpy/config_files/Cable_Modules/QTL1309-xx - USB 3.0 Module Config v4.000 c1.1.qfg +188 -188
  15. quarchpy/config_files/Cable_Modules/QTL1366-xx - QSFP Cable Pull Module Config v4.000 c1.1.qfg +209 -209
  16. quarchpy/config_files/Cable_Modules/QTL1383-xx - eSATAp Module Config v4.000 c1.3.qfg +190 -190
  17. quarchpy/config_files/Cable_Modules/QTL1521-02 - Mini SAS HD Module Config v4.000 c1.1.qfg +210 -210
  18. quarchpy/config_files/Cable_Modules/QTL1521-03 - Mini SAS HD Module Config v4.000 c1.1.qfg +210 -210
  19. quarchpy/config_files/Cable_Modules/QTL1521-05 - Mini SAS HD Module Config v4.005 c1.1.qfg +209 -209
  20. quarchpy/config_files/Cable_Modules/QTL1521-05 - Mini SAS HD Module Config v4.007 c1.2.qfg +209 -209
  21. quarchpy/config_files/Cable_Modules/QTL1521-xx - Mini SAS HD Module Config v4.003 c1.5.qfg +209 -209
  22. quarchpy/config_files/Cable_Modules/QTL1663-xx - Quad QSFP Cable Pull Module Config v4.000 c1.1.qfg +308 -308
  23. quarchpy/config_files/Cable_Modules/QTL1675-05 - Mini SAS HD Module w Triggering Config v4.007 c1.3.qfg +211 -211
  24. quarchpy/config_files/Cable_Modules/QTL1675-xx - Mini SAS HD Module w Triggering Config v4.000 c1.1.qfg +210 -210
  25. quarchpy/config_files/Cable_Modules/QTL1917-xx - Dual SFP+ Cable Pull Module Config v4.000 c1.1.qfg +174 -174
  26. quarchpy/config_files/Cable_Modules/QTL1971-01 - USB TypeC Module Config v4.000 c1.1.qfg +203 -203
  27. quarchpy/config_files/Cable_Modules/QTL1971-02 - USB TypeC Module Config v4.000 c1.1.qfg +203 -203
  28. quarchpy/config_files/Cable_Modules/QTL2022-xx - RJ-45 Cable Module Config v4.104 c1.3.QFG +189 -189
  29. quarchpy/config_files/Cable_Modules/QTL2058-xx - External PCIe Module Config v4.001 c1.1.qfg +209 -209
  30. quarchpy/config_files/Cable_Modules/QTL2138-01 - SFP28 Cable Pull Module Config v4.000 c1.1.qfg +154 -154
  31. quarchpy/config_files/Cable_Modules/QTL2146-01 - Gen4 OCuLink Cable Module Config v4.001 c1.1.qfg +212 -212
  32. quarchpy/config_files/Cable_Modules/QTL2162-01 - 24G mini SAS HD Cable Break Module Config v4.000 c1.qfg +209 -209
  33. quarchpy/config_files/Cable_Modules/QTL2171-01 - QSFP28 Cable Module Config v4.000 c1.qfg +198 -198
  34. quarchpy/config_files/Cable_Modules/QTL2171-02 - QSFP28 Cable Module Config v4.000 c1.qfg +198 -198
  35. quarchpy/config_files/Cable_Modules/QTL2341-01 - Gen4 External PCIe Cable Module v4.000.qfg +209 -209
  36. quarchpy/config_files/Cable_Modules/QTL2602-xx - Multiprotocol Link Breaker.qfg +178 -178
  37. quarchpy/config_files/Cable_Modules/QTL2834-xx - -48V DC Breaker Module v5.000 c1.0.qfg +177 -177
  38. quarchpy/config_files/Card_Modules/QTL1069-xx - SBB 2.0 Module Config v3.5 c1.1.qfg +366 -366
  39. quarchpy/config_files/Card_Modules/QTL1630-01 - PCIe Card Module v4.000 c1.0.qfg +280 -280
  40. quarchpy/config_files/Card_Modules/QTL1630-02 - PCIe Card Module Config v4.004 c1.2.qfg +283 -283
  41. quarchpy/config_files/Card_Modules/QTL1630-02 - PCIe Card Module Config v4.005 c1.2.qfg +283 -283
  42. quarchpy/config_files/Card_Modules/QTL1630-02 - PCIe Card Module Config v4.100 c1.4.qfg +284 -284
  43. quarchpy/config_files/Card_Modules/QTL1630-04 - PCIe Card Module Config v4.001 c1.1.qfg +287 -287
  44. quarchpy/config_files/Card_Modules/QTL1630-04 - PCIe Card Module Config v4.004 c1.2.qfg +287 -287
  45. quarchpy/config_files/Card_Modules/QTL1688-01 - PCIe Card Module w Triggering Config v4.004 c1.2.qfg +284 -284
  46. quarchpy/config_files/Card_Modules/QTL1688-01 - PCIe Card Module w Triggering Config v4.006 c1.3.qfg +285 -285
  47. quarchpy/config_files/Card_Modules/QTL1688-03 - PCIe Card Module w Triggering Config v4.001 c1.1.qfg +286 -286
  48. quarchpy/config_files/Card_Modules/QTL1688-03 - PCIe Card Module w Triggering Config v4.004 c1.2.qfg +288 -288
  49. quarchpy/config_files/Card_Modules/QTL1688-04 - PCIe Card Module w Triggering Config v4.005 c1.5.qfg +288 -288
  50. quarchpy/config_files/Card_Modules/QTL1688-05 - PCIe Card Module w Triggering Config v4.005 c1.qfg +288 -288
  51. quarchpy/config_files/Card_Modules/QTL1848-01 - PCIe Lite Card Module Config v4.000.qfg +122 -122
  52. quarchpy/config_files/Card_Modules/QTL1848-02 - PCIe Lite Card Module Config v4.000.qfg +134 -134
  53. quarchpy/config_files/Card_Modules/QTL1919-01 - PCIe x8 Card Module Config v4.001 c1.4.qfg +247 -247
  54. quarchpy/config_files/Card_Modules/QTL1920-01 - PCIe x8 Card Module w Triggering Config v4.000 c1.3.qfg +247 -247
  55. quarchpy/config_files/Card_Modules/QTL2019-xx - M.2 Horizontal Card Module Config v4.002 c1.1.qfg +219 -219
  56. quarchpy/config_files/Card_Modules/QTL2034-xx - M.2 M-Key Vertical Module Config v4.001 c1.1.qfg +221 -221
  57. quarchpy/config_files/Card_Modules/QTL2067-xx - PCIe SFF Module Config v4.000 c1.1.qfg +248 -248
  58. quarchpy/config_files/Card_Modules/QTL2067-xx - Sanblaze 2 Drive Riser Card Config v4.006 c1.4.qfg +256 -256
  59. quarchpy/config_files/Card_Modules/QTL2067-xx - Sanblaze 2 Drive Riser Card Config v4.007 c1.4.qfg +256 -256
  60. quarchpy/config_files/Card_Modules/QTL2073-01 - GEN3 PCIe Lite Card Module Config v4.001.qfg +134 -134
  61. quarchpy/config_files/Card_Modules/QTL2073-01 - PCIe Lite Card Module Config v4.000.qfg +134 -134
  62. quarchpy/config_files/Card_Modules/QTL2074-01 - GEN3 PCIe HS Card Module v4.005 c1.4.qfg +287 -287
  63. quarchpy/config_files/Card_Modules/QTL2074-01 - PCIe Card Module Config CurrentLimit v4.004 c1.2.qfg +287 -287
  64. quarchpy/config_files/Card_Modules/QTL2087-xx - PCIe GEN4 Card Module w Triggering Config v4.001 c1.1.qfg +287 -287
  65. quarchpy/config_files/Card_Modules/QTL2087-xx - PCIe GEN4 Card Module w Triggering Config v4.001 c1.3.qfg +277 -277
  66. quarchpy/config_files/Card_Modules/QTL2087-xx - PCIe GEN4 Card Module w Triggering Config v5.000 c1.4.qfg +277 -277
  67. quarchpy/config_files/Card_Modules/QTL2094-01 - Sanblaze Dualport Riser Card Config v4.001 c1.1.qfg +223 -223
  68. quarchpy/config_files/Card_Modules/QTL2128-xx - PCIe GEN4 Card Module w Triggering Config v4.000 c1.1.qfg +287 -287
  69. quarchpy/config_files/Card_Modules/QTL2128-xx - PCIe GEN4 Card Module w Triggering Config v4.001 c1.4.qfg +277 -277
  70. quarchpy/config_files/Card_Modules/QTL2135-xx - PCIe GEN4 Card Module w Triggering Config - Inrush Limit v4.001 c1.4.qfg +277 -277
  71. quarchpy/config_files/Card_Modules/QTL2161-01 - EDSFF x8 Module Config v4.000 c1.1.qfg +210 -210
  72. quarchpy/config_files/Card_Modules/QTL2171-xx - EDSFF x8 Module w Triggering Config 4.001 c1.1.qfg +213 -213
  73. quarchpy/config_files/Card_Modules/QTL2203-01 - Sanblaze Dualport Rack Riser Card Config v4.000 c1.1.qfg +218 -218
  74. quarchpy/config_files/Card_Modules/QTL2203-01 - Sanblaze Dualport Rack Riser Card Config v4.005 c1.5.qfg +218 -218
  75. quarchpy/config_files/Card_Modules/QTL2272-01 - GEN4 EDSFF x8 Module Config v4.000 c1.1.qfg +211 -211
  76. quarchpy/config_files/Card_Modules/QTL2282-01 - Sanblaze Singleport Rack Riser Card Config v4.000 c1.1.qfg +212 -212
  77. quarchpy/config_files/Card_Modules/QTL2282-01 - Sanblaze Singleport Rack Riser Card Config v4.002 c1.2.qfg +212 -212
  78. quarchpy/config_files/Card_Modules/QTL2318-01 - SANBlaze U.2 Rack Riser Card Config v4.000 c1.1.qfg +218 -218
  79. quarchpy/config_files/Card_Modules/QTL2322-03 - GEN4 M.2 M-Key Vertical Breaker Module Config v5.001 c1.3.qfg +220 -220
  80. quarchpy/config_files/Card_Modules/QTL2322-xx - GEN4 M.2 M-Key Vertical Breaker Module Config v5.000 c1.1.qfg +208 -208
  81. quarchpy/config_files/Card_Modules/QTL2334-xx - Gen4 EDSFF x4 Card Module Config v4.000 c1.1.qfg +187 -187
  82. quarchpy/config_files/Card_Modules/QTL2351-xx - GEN4 EDSFF x4 Card Module +Triggering Config v4.000 c1.1.qfg +189 -189
  83. quarchpy/config_files/Card_Modules/QTL2351-xx - GEN4 EDSFF x4 Card Module +Triggering Config v4.001 c1.2.qfg +190 -190
  84. quarchpy/config_files/Card_Modules/QTL2357-xx - PCIe GEN5 Card Module Config v5.000 c1.1.qfg +282 -282
  85. quarchpy/config_files/Card_Modules/QTL2358-xx - PCIe GEN5 Card Module w Triggering Config v5.000 c1.1.qfg +283 -283
  86. quarchpy/config_files/Card_Modules/QTL2378-xx - SANBlaze U.3 Rack Riser Card Config v4.000 c1.1.qfg +215 -215
  87. quarchpy/config_files/Card_Modules/QTL2378-xx - SANBlaze U.3 Rack Riser Card Config v4.001 c1.2.qfg +217 -217
  88. quarchpy/config_files/Card_Modules/QTL2395-01 - GEN4 M.2 M-Key Vertical Breaker Module w Triggering Config v5.000 c1.1.qfg +208 -208
  89. quarchpy/config_files/Card_Modules/QTL2395-02 - GEN4 M.2 M-Key Vertical Breaker Module w Triggering Config v5.001 c1.3.qfg +220 -220
  90. quarchpy/config_files/Card_Modules/QTL2395-03 - GEN4 M.2 M-Key Vertical Breaker Module w Triggering Config v5.001 c1.3.qfg +220 -220
  91. quarchpy/config_files/Card_Modules/QTL2396-xx - PCIe GEN5 Card Module w inrush Config v5.000 c1.1.qfg +282 -282
  92. quarchpy/config_files/Card_Modules/QTL2403-xx - Gen4 PCIe Lite Module Config v4.000.qfg +135 -135
  93. quarchpy/config_files/Card_Modules/QTL2515-xx - PCIe GEN4 Card Module w Triggering - Inrush Limit Config v4.001 c1.3.qfg +277 -277
  94. quarchpy/config_files/Card_Modules/QTL2652-xx - Gen5 PCIe Lite Module Config v4.000.qfg +130 -130
  95. quarchpy/config_files/Card_Modules/QTL2652-xx - Gen5 PCIe Lite Module Config v4.003.qfg +137 -137
  96. quarchpy/config_files/Card_Modules/QTL2658-xx - Gen5 PCIe Lite Module w Inrush Config v4.000.qfg +130 -130
  97. quarchpy/config_files/Card_Modules/QTL2661-xx - GEN5 PCIe U.3 Breaker Config w Triggering v5.000 c1.1.qfg +229 -229
  98. quarchpy/config_files/Card_Modules/QTL2662-xx - GEN5 PCIe U.3 Breaker Config v5.000 c1.1.qfg +228 -228
  99. quarchpy/config_files/Card_Modules/QTL2686-xx - Gen5 EDSFF E3 x4 Breaker Config v5.000.qfg +230 -230
  100. quarchpy/config_files/Card_Modules/QTL2686-xx - Gen5 EDSFF E3 x4 Breaker Config v5.001 c1.2.qfg +233 -233
  101. quarchpy/config_files/Card_Modules/QTL2686-xx - Gen5 EDSFF E3 x4 Breaker v5.000 c1.1.qfg +230 -230
  102. quarchpy/config_files/Card_Modules/QTL2692-xx - Gen5 EDSFF E3 x4 Breaker +Triggering Config v5.000.qfg +230 -230
  103. quarchpy/config_files/Card_Modules/QTL2692-xx - Gen5 EDSFF E3 x4 Breaker w Triggering Config v5.001 c1.2.qfg +233 -233
  104. quarchpy/config_files/Card_Modules/QTL2692-xx - Gen5 EDSFF E3 x4 Breaker w Triggering v5.000 c1.1.qfg +230 -230
  105. quarchpy/config_files/Card_Modules/QTL2766-xx - GEN4 EDSFF E1 x8 Breaker Config v5.000.qfg +253 -253
  106. quarchpy/config_files/Card_Modules/QTL2767-xx - GEN4 EDSFF E3 x8 Breaker Config v5.000.qfg +253 -253
  107. quarchpy/config_files/Card_Modules/QTL2768-xx - GEN4 EDSFF E3 2T x8 Breaker Config v5.000.qfg +253 -253
  108. quarchpy/config_files/Card_Modules/QTL2776-xx - GEN4 EDSFF E1 x8 Breaker +Triggering Config v5.000.qfg +253 -253
  109. quarchpy/config_files/Card_Modules/QTL2777-xx - GEN4 EDSFF E3 x8 Breaker +Triggering Config v5.000.qfg +253 -253
  110. quarchpy/config_files/Card_Modules/QTL2778-xx - GEN4 EDSFF E3 2T x8 Breaker +Triggering Config v5.000.qfg +253 -253
  111. quarchpy/config_files/Card_Modules/QTL2798-xx - PCIe GEN5 Card Module w Triggering w Inrush Limit Config v5.000 c1.1.qfg +283 -283
  112. quarchpy/config_files/Card_Modules/QTL2804-xx - GEN5 MCIO x4 to U.2 Breaker Config v5.000 c1.1.qfg +234 -234
  113. quarchpy/config_files/Card_Modules/QTL2805-xx - GEN5 MCIO x4 to U.2 Breaker + Triggering Config v5.000 c1.1.qfg +234 -234
  114. quarchpy/config_files/Card_Modules/QTL2814-xx - GEN5 AIC to U.2 Breaker + Triggering Config v5.000 c1.1 .qfg +233 -233
  115. quarchpy/config_files/Card_Modules/QTL2892-xx - GEN5 EDSFF E1 x4 Breaker.qfg +233 -233
  116. quarchpy/config_files/Card_Modules/QTL2925-xx - GEN5 EDSFF E1 x4 Breaker +Triggering.qfg +233 -233
  117. quarchpy/config_files/Drive_Modules/QTL1177-xx - HS Module Config v3.5 c1.5.qfg +198 -198
  118. quarchpy/config_files/Drive_Modules/QTL1177-xx - HS Module Config v4.000 c1.5.qfg +200 -200
  119. quarchpy/config_files/Drive_Modules/QTL1177-xx - HS Module Config v4.006 c1.8.qfg +199 -199
  120. quarchpy/config_files/Drive_Modules/QTL1215-xx - Lite Module Config v3.50.qfg +118 -118
  121. quarchpy/config_files/Drive_Modules/QTL1215-xx - Lite Module Config v4.000.qfg +118 -118
  122. quarchpy/config_files/Drive_Modules/QTL1301-xx - HS Lite Module Config v3.50.qfg +129 -129
  123. quarchpy/config_files/Drive_Modules/QTL1301-xx - HS Lite Module Config v4.000.qfg +129 -129
  124. quarchpy/config_files/Drive_Modules/QTL1429-01 - EMC HS Lite Module Config v4.000.qfg +129 -129
  125. quarchpy/config_files/Drive_Modules/QTL1429-02 - EMC HS Lite Module Config v4.002.qfg +120 -120
  126. quarchpy/config_files/Drive_Modules/QTL1623-03 - 12G HS Lite Module Config v4.001.qfg +128 -128
  127. quarchpy/config_files/Drive_Modules/QTL1623-04 - 12G HS Lite Module Config v4.001.qfg +128 -128
  128. quarchpy/config_files/Drive_Modules/QTL1680-xx - SCA2 Lite Module Config v4.001.qfg +135 -135
  129. quarchpy/config_files/Drive_Modules/QTL1689-01 - 12G HS Module Config v4.001 c1.1.qfg +199 -199
  130. quarchpy/config_files/Drive_Modules/QTL1689-04 - 12G HS Module Config v4.002 c1.1.qfg +196 -196
  131. quarchpy/config_files/Drive_Modules/QTL1689-05 - 12G HS Module Config v4.002 c1.1.qfg +196 -196
  132. quarchpy/config_files/Drive_Modules/QTL1743-02 - PCIe SFF Module Config v4.003 c1.3.qfg +222 -222
  133. quarchpy/config_files/Drive_Modules/QTL1743-02 - PCIe SFF Module Config v4.006 c1.4.qfg +213 -213
  134. quarchpy/config_files/Drive_Modules/QTL1743-xx - PCIe SFF Module Config v4.000 c1.1.qfg +221 -221
  135. quarchpy/config_files/Drive_Modules/QTL1753-xx - 12G Lite Module Config v4.000.qfg +116 -116
  136. quarchpy/config_files/Drive_Modules/QTL1921-01 - EMC 12G HS Lite Module Config v4.000.qfg +120 -120
  137. quarchpy/config_files/Drive_Modules/QTL2207-01 - GEN 4 PCIe U.2 Drive Module.qfg +217 -217
  138. quarchpy/config_files/Drive_Modules/QTL2207-02 - GEN 4 PCIe U.2 Drive Module v5.000.qfg +220 -220
  139. quarchpy/config_files/Drive_Modules/QTL2207-03 - GEN 4 PCIe SFF HS Drive Module Triggering v5.001.qfg +221 -221
  140. quarchpy/config_files/Drive_Modules/QTL2207-xx - GEN 4 PCIe U.2 Drive Module v4.001.qfg +219 -219
  141. quarchpy/config_files/Drive_Modules/QTL2207-xx - GEN 4 PCIe U.2 Drive Module.qfg +219 -219
  142. quarchpy/config_files/Drive_Modules/QTL2245-01 - GEN 4 PCIe U.3 HS Drive Module v4.000.qfg +223 -223
  143. quarchpy/config_files/Drive_Modules/QTL2245-xx - GEN 4 PCIe U.3 HS Drive Module.qfg +229 -229
  144. quarchpy/config_files/Drive_Modules/QTL2266-01 - GEN 4 PCIe SFF HS Drive Module Triggering.qfg +219 -219
  145. quarchpy/config_files/Drive_Modules/QTL2266-02 - GEN 4 PCIe SFF HS Drive Module Triggering v4.002.qfg +220 -220
  146. quarchpy/config_files/Drive_Modules/QTL2266-03 - GEN 4 PCIe SFF HS Drive Module Triggering v5.001.qfg +221 -221
  147. quarchpy/config_files/Drive_Modules/QTL2266-XX - GEN 4 PCIe SFF HS Drive Module Triggering.qfg +219 -219
  148. quarchpy/config_files/Drive_Modules/QTL2270-01 - GEN 4 PCIe U.3 HS Drive Module Triggering v4.000.qfg +225 -225
  149. quarchpy/config_files/Drive_Modules/QTL2270-xx - GEN 4 PCIe U.3 HS Drive Module Triggering.qfg +230 -230
  150. quarchpy/config_files/Drive_Modules/QTL2542-02 - 24G SAS Drive Breaker Module v5.001.qfg +194 -194
  151. quarchpy/config_files/Drive_Modules/QTL2542-xx - 24G SAS Drive Breaker v5.000 c1.1.qfg +194 -194
  152. quarchpy/config_files/Drive_Modules/QTL2543-xx - 24G SAS Drive Breaker +Triggering v5.000 c1.1.qfg +195 -195
  153. quarchpy/config_files/Drive_Modules/QTL2602-xx - Multiprotocol Link Breaker.qfg +178 -178
  154. quarchpy/config_files/Drive_Modules/QTL2645-xx - Gen5 PCIe U.2 Drive Module v5.000.qfg +227 -227
  155. quarchpy/config_files/Drive_Modules/QTL2645-xx - Gen5 PCIe U.2 Drive Module v5.001.qfg +227 -227
  156. quarchpy/config_files/Drive_Modules/QTL2645-xx - Gen5 PCIe U.2 Drive Module v5.003.qfg +227 -227
  157. quarchpy/config_files/Drive_Modules/QTL2645-xx - Gen5 PCIe U.2 Drive Module v5.007.qfg +229 -229
  158. quarchpy/config_files/Drive_Modules/QTL2651-xx - Gen5 PCIe U.2 Drive Module + Triggering v5.000.qfg +228 -228
  159. quarchpy/config_files/Drive_Modules/QTL2651-xx - Gen5 PCIe U.2 Drive Module + Triggering v5.001.qfg +228 -228
  160. quarchpy/config_files/Drive_Modules/QTL2651-xx - Gen5 PCIe U.2 Drive Module + Triggering v5.003.qfg +228 -228
  161. quarchpy/config_files/Drive_Modules/QTL2651-xx - Gen5 PCIe U.2 Drive Module v5.007.qfg +230 -230
  162. quarchpy/config_files/Drive_Modules/QTL2661-xx - Gen5 U.3 Drive Module + Triggering v5.000.qfg +229 -229
  163. quarchpy/config_files/Drive_Modules/QTL2662-xx - Gen5 PCIe U.3 Drive Module v5.000.qfg +228 -228
  164. quarchpy/config_files/Drive_Modules/QTL2757-xx - Gen5 SFF Lite Breaker Module Config v4.000.qfg +140 -140
  165. quarchpy/config_files/Drive_Modules/QTL2804-xx - Gen5 MCIO to U.2 Breaker +Triggering v5.003.qfg +253 -253
  166. quarchpy/config_files/Drive_Modules/QTL2804-xx - Gen5 MCIO to U.2 Breaker v5.001.qfg +234 -234
  167. quarchpy/config_files/Drive_Modules/QTL2805-xx - Gen5 MCIO to U.2 Breaker +Triggering v5.001.qfg +234 -234
  168. quarchpy/config_files/Drive_Modules/QTL2805-xx - Gen5 MCIO to U.2 Breaker +Triggering v5.002.qfg +253 -253
  169. quarchpy/config_files/Drive_Modules/QTL2805-xx - Gen5 MCIO to U.2 Breaker +Triggering v5.003.qfg +253 -253
  170. quarchpy/config_files/Drive_Modules/QTL2813-xx - Gen5 AIC to U.2 Breaker v5.001 c1.2.qfg +231 -0
  171. quarchpy/config_files/{Card_Modules/QTL2813-xx - Gen5 AIC to U.2 Breaker v5.002 c1.3.qfg → Drive_Modules/QTL2813-xx - Gen5 AIC to U.2 Breaker with Triggering v5.002 c1.3.qfg } +253 -253
  172. quarchpy/config_files/Drive_Modules/QTL2814-xx - Gen5 AIC to U.2 Breaker with Triggering v5.001 c1.2.qfg +231 -0
  173. quarchpy/config_files/{Card_Modules/QTL2814-xx - Gen5 AIC to U.2 Breaker + Triggering Config v5.002 c1.3.qfg → Drive_Modules/QTL2814-xx - Gen5 AIC to U.2 Breaker with Triggering v5.002 c1.3.qfg } +253 -253
  174. quarchpy/config_files/Drive_Modules/QTL2892-xx - Gen5 EDSFF E1 x4 Breaker v5.001.qfg +233 -233
  175. quarchpy/config_files/Drive_Modules/Standard Drive Module Config v3.5 c1.1.qfg +171 -171
  176. quarchpy/config_files/Power_Margining/HD/QTL1944-xx - HD Power Module v5.000.qfg +123 -123
  177. quarchpy/config_files/Power_Margining/HD/QTL1944-xx - HD Power Module v5.003.qfg +124 -124
  178. quarchpy/config_files/Power_Margining/HD/QTL1995-xx - x6 HD Power Module v5.002.qfg +122 -122
  179. quarchpy/config_files/Power_Margining/HD/QTL1995-xx - x6 HD Power Module v5.003.qfg +123 -123
  180. quarchpy/config_files/Power_Margining/HD/QTL1999-xx - Single HD Power Module v5.002.qfg +122 -122
  181. quarchpy/config_files/Power_Margining/HD/QTL1999-xx - Single HD Power Module v5.003.qfg +123 -123
  182. quarchpy/config_files/Power_Margining/HD/QTL1999-xx - Single HD Power Module v6.000.qfg +124 -124
  183. quarchpy/config_files/Power_Margining/PPM/QTL1455-02 - Power Margining Module v4.101.qfg +78 -78
  184. quarchpy/config_files/Power_Margining/PPM/QTL1455-02 - Power Margining Module v4.200.qfg +80 -80
  185. quarchpy/config_files/Power_Margining/PPM/QTL1455-02 - Power Margining Module v4.201.qfg +82 -82
  186. quarchpy/config_files/Power_Margining/PPM/QTL1455-02 - Test Power Margining Module v4.004.qfg +73 -73
  187. quarchpy/config_files/Power_Margining/PPM/QTL1658-01 - Power Margining Module w. Triggering v4.101.qfg +78 -78
  188. quarchpy/config_files/Power_Margining/PPM/QTL1658-01 - Power Margining Module w. Triggering v4.200.qfg +80 -80
  189. quarchpy/config_files/Power_Margining/PPM/QTL1658-01 - Power Margining Module w. Triggering v4.201.qfg +82 -82
  190. quarchpy/config_files/Power_Margining/PPM/QTL1727-01 - 3v3 Power Margining Module w. Triggering v4.105.qfg +78 -78
  191. quarchpy/config_files/Power_Margining/PPM/QTL1727-01 - 3v3 Power Margining Module w. Triggering v4.200.qfg +80 -80
  192. quarchpy/config_files/Power_Margining/PPM/QTL1727-01 - 3v3 Power Margining Module w. Triggering v4.201.qfg +82 -82
  193. quarchpy/config_files/Power_Margining/PPM/QTL1730-01 - 3v3 Power Margining Module v4.105.qfg +78 -78
  194. quarchpy/config_files/Power_Margining/PPM/QTL1730-01 - 3v3 Power Margining Module v4.200.qfg +80 -80
  195. quarchpy/config_files/Power_Margining/PPM/QTL1730-01 - 3v3 Power Margining Module v4.201.qfg +82 -82
  196. quarchpy/config_files/Power_Margining/XLC/QTL1824-01 - Power Margining Module V2 - Triggering v4.000.qfg +80 -80
  197. quarchpy/config_files/Power_Margining/XLC/QTL1824-01 - Power Margining Module V2 - Triggering v4.200.qfg +81 -81
  198. quarchpy/config_files/Power_Margining/XLC/QTL1824-01 - Power Margining Module V2 - Triggering v4.202.qfg +87 -87
  199. quarchpy/config_files/Power_Margining/XLC/QTL1824-01 - Power Margining Module V2 - Triggering v4.210.qfg +88 -88
  200. quarchpy/config_files/Power_Margining/XLC/QTL1824-01 - Power Margining Module V2 - Triggering v4.211.qfg +121 -121
  201. quarchpy/config_files/Power_Margining/XLC/QTL1824-03 - Power Margining Module V2 - Triggering v4.213.qfg +121 -121
  202. quarchpy/config_files/Power_Margining/XLC/QTL1847-01 - Power Margining Module V2 v4.213.qfg +121 -121
  203. quarchpy/config_files/Switch_Modules/QTL1390-xx - 4-8 SATA MUX Module Config v4.002.qfg +186 -186
  204. quarchpy/config_files/Switch_Modules/QTL1443-xx - 1-8 USB3 MUX Module Config v4.002.qfg +147 -147
  205. quarchpy/config_files/Switch_Modules/QTL1443-xx - 1-8 USB3 MUX Module Config v4.003.qfg +147 -147
  206. quarchpy/config_files/Switch_Modules/QTL1443-xx - 1-8 USB3 MUX Module Config v4.100.qfg +147 -147
  207. quarchpy/config_files/Switch_Modules/QTL1449-xx - 1-8 PCIe MUX Module Config v4.000.qfg +145 -145
  208. quarchpy/config_files/Switch_Modules/QTL1490-xx - SATA Keyed HS Module Config v4.003 c1.6.qfg +194 -194
  209. quarchpy/config_files/Switch_Modules/QTL1490-xx - SATA Keyed HS Module Config v4.006 c1.8.qfg +193 -193
  210. quarchpy/config_files/Switch_Modules/QTL1530-xx - 1-8 DP MUX Module Config v4.000.qfg +145 -145
  211. quarchpy/config_files/Switch_Modules/QTL1564-xx - 12 Port Mini SAS HD MUX Module Config v4.000.qfg +184 -184
  212. quarchpy/config_files/Switch_Modules/QTL1584-xx - 1-4 ExpressCard MUX Module Config v4.000.qfg +81 -81
  213. quarchpy/config_files/quarch_config_parser.py +16 -15
  214. quarchpy/connection_specific/QPS/{License/argparse4j-license.txt → 3rdPartyLicenses/argparse4j-LICENSE.txt} +23 -25
  215. quarchpy/connection_specific/QPS/3rdPartyLicenses/com.sun.istack-license.html +59 -0
  216. quarchpy/connection_specific/QPS/{qis/License/LICENSE.txt → 3rdPartyLicenses/commons-io-LICENSE-2.0.txt} +202 -202
  217. quarchpy/connection_specific/QPS/3rdPartyLicenses/controlsfx-license.txt +29 -0
  218. quarchpy/connection_specific/QPS/{License/LICENSE.txt → 3rdPartyLicenses/evalex-LICENSE.txt} +202 -202
  219. quarchpy/connection_specific/QPS/3rdPartyLicenses/gson-license.txt +202 -0
  220. quarchpy/connection_specific/QPS/3rdPartyLicenses/guava-license.txt +202 -0
  221. quarchpy/connection_specific/QPS/3rdPartyLicenses/jakarta.activation-license.html +59 -0
  222. quarchpy/connection_specific/QPS/3rdPartyLicenses/jakarta.xml.bind-api-license.html +59 -0
  223. quarchpy/connection_specific/QPS/3rdPartyLicenses/janino-LICENSE.txt +31 -0
  224. quarchpy/connection_specific/QPS/3rdPartyLicenses/jfx-license.txt +347 -0
  225. quarchpy/connection_specific/QPS/3rdPartyLicenses/log4j-license.txt +202 -0
  226. quarchpy/connection_specific/QPS/3rdPartyLicenses/netty-LICENSE.txt +202 -0
  227. quarchpy/connection_specific/QPS/3rdPartyLicenses/netty-NOTICE.txt +239 -0
  228. quarchpy/connection_specific/QPS/3rdPartyLicenses/nsmenufx-LICENSE.txt +24 -0
  229. quarchpy/connection_specific/QPS/3rdPartyLicenses/slf4j-LICENSE.txt +24 -0
  230. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1253-01 - Mini SAS Module Config v3.5 c1.3.xml +597 -0
  231. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1253-01 - Mini SAS Module Config v4.000 c1.3.xml +597 -0
  232. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1253-02 - Mini SAS Module Config v3.5 c1.3.xml +597 -0
  233. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1253-02 - Mini SAS Module Config v4.000 c1.3.xml +597 -0
  234. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1253-xx - Mini SAS Module Config v4.003 c1.6.xml +599 -0
  235. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1271-xx - Ethernet Module Config v3.5 c1.2.xml +533 -0
  236. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1271-xx - Ethernet Module Config v4.000 c1.2.xml +533 -0
  237. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1271-xx - Ethernet Module Config v4.100 c1.3.xml +535 -0
  238. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1292-xx - SFP+ Cable Pull Module Config v4.000 c1.1.xml +449 -0
  239. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1309-04 - USB 3.0 Module Config v4.003 c1.2.xml +530 -0
  240. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1309-xx - USB 3.0 Module Config v3.5 c1.1.xml +528 -0
  241. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1309-xx - USB 3.0 Module Config v4.000 c1.1.xml +528 -0
  242. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1366-xx - QSFP Cable Pull Module Config v4.000 c1.1.xml +684 -0
  243. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1383-xx - eSATAp Module Config v4.000 c1.3.xml +542 -0
  244. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1521-02 - Mini SAS HD Module Config v4.000 c1.1.xml +670 -0
  245. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1521-03 - Mini SAS HD Module Config v4.000 c1.1.xml +670 -0
  246. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1521-05 - Mini SAS HD Module Config v4.005 c1.1.xml +672 -0
  247. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1521-05 - Mini SAS HD Module Config v4.007 c1.2.xml +672 -0
  248. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1521-xx - Mini SAS HD Module Config v4.003 c1.5.xml +672 -0
  249. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1663-xx - Quad QSFP Cable Pull Module Config v4.000 c1.1.xml +1523 -0
  250. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1675-05 - Mini SAS HD Module w Triggering Config v4.007 c1.3.xml +673 -0
  251. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1675-xx - Mini SAS HD Module w Triggering Config v4.000 c1.1.xml +673 -0
  252. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1917-xx - Dual SFP+ Cable Pull Module Config v4.000 c1.1.xml +614 -0
  253. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1971-01 - USB TypeC Module Config v4.000 c1.1.xml +637 -0
  254. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL1971-02 - USB TypeC Module Config v4.000 c1.1.xml +641 -0
  255. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL2022-xx - RJ-45 Cable Module Config v4.104 c1.3.xml +535 -0
  256. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL2058-xx - External PCIe Module Config v4.001 c1.1.xml +696 -0
  257. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL2138-01 - SFP28 Cable Pull Module Config v4.000 c1.1.xml +449 -0
  258. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL2146-01 - Gen4 OCuLink Cable Module Config v4.001 c1.1.xml +718 -0
  259. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL2162-01 - 24G mini SAS HD Cable Break Module Config v4.000 c1.xml +672 -0
  260. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL2171-01 - QSFP28 Cable Module Config v4.000 c1.xml +583 -0
  261. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL2171-02 - QSFP28 Cable Module Config v4.000 c1.xml +583 -0
  262. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL2341-01 - Gen4 External PCIe Cable Module v4.000.xml +625 -0
  263. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL2602-xx - Multiprotocol Link Breaker.xml +414 -0
  264. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL2834-xx - -48V DC Breaker Module v5.000 c1.0.xml +374 -0
  265. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Cable Modules/QTL3000-xx - RJ-45 Cable Module v5.000 c1.4.xml +539 -0
  266. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1069-xx - SBB 2.0 Module Config v3.5 c1.1.xml +1614 -0
  267. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1630-01 - PCIe Card Module v4.000 c1.0.xml +1271 -0
  268. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1630-02 - PCIe Card Module Config v4.004 c1.2.xml +1289 -0
  269. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1630-02 - PCIe Card Module Config v4.005 c1.2.xml +1289 -0
  270. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1630-02 - PCIe Card Module Config v4.100 c1.4.xml +1290 -0
  271. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1630-04 - PCIe Card Module Config v4.001 c1.1.xml +1297 -0
  272. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1630-04 - PCIe Card Module Config v4.004 c1.2.xml +1301 -0
  273. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1688-01 - PCIe Card Module w Triggering Config v4.004 c1.2.xml +1290 -0
  274. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1688-01 - PCIe Card Module w Triggering Config v4.006 c1.3.xml +1291 -0
  275. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1688-03 - PCIe Card Module w Triggering Config v4.001 c1.1.xml +1298 -0
  276. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1688-03 - PCIe Card Module w Triggering Config v4.004 c1.2.xml +1302 -0
  277. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1688-04 - PCIe Card Module w Triggering Config v4.005 c1.5.xml +1307 -0
  278. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1688-05 - PCIe Card Module w Triggering Config v4.005 c1.xml +1307 -0
  279. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1848-01 - PCIe Lite Card Module Config v4.000.xml +219 -0
  280. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1848-02 - PCIe Lite Card Module Config v4.000.xml +286 -0
  281. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1919-01 - PCIe x8 Card Module Config v4.001 c1.4.xml +965 -0
  282. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL1920-01 - PCIe x8 Card Module w Triggering Config v4.000 c1.3.xml +966 -0
  283. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2019-xx - M.2 Horizontal Card Module Config v4.002 c1.1.xml +703 -0
  284. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2034-xx - M.2 M-Key Vertical Module Config v4.001 c1.1.xml +737 -0
  285. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2067-xx - PCIe SFF Module Config v4.000 c1.1.xml +971 -0
  286. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2067-xx - Sanblaze 2 Drive Riser Card Config v4.006 c1.4.xml +1020 -0
  287. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2067-xx - Sanblaze 2 Drive Riser Card Config v4.007 c1.4.xml +1022 -0
  288. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2073-01 - GEN3 PCIe Lite Card Module Config v4.001.xml +286 -0
  289. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2073-01 - PCIe Lite Card Module Config v4.000.xml +286 -0
  290. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2074-01 - GEN3 PCIe HS Card Module v4.005 c1.4.xml +1301 -0
  291. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2074-01 - PCIe Card Module Config CurrentLimit v4.004 c1.2.xml +1301 -0
  292. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2087-xx - PCIe GEN4 Card Module w Triggering Config v4.001 c1.1.xml +1307 -0
  293. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2087-xx - PCIe GEN4 Card Module w Triggering Config v4.001 c1.3.xml +1212 -0
  294. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2087-xx - PCIe GEN4 Card Module w Triggering Config v5.000 c1.4.xml +1212 -0
  295. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2094-01 - Sanblaze Dualport Riser Card Config v4.001 c1.1.xml +773 -0
  296. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2128-xx - PCIe GEN4 Card Module w Triggering Config v4.000 c1.1.xml +1306 -0
  297. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2128-xx - PCIe GEN4 Card Module w Triggering Config v4.001 c1.4.xml +1211 -0
  298. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2135-xx - PCIe GEN4 Card Module w Triggering Config - Inrush Limit v4.001 c1.4.xml +1211 -0
  299. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2161-01 - EDSFF x8 Module Config v4.000 c1.1.xml +773 -0
  300. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2171-xx - EDSFF x8 Module w Triggering Config 4.001 c1.1.xml +785 -0
  301. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2203-01 - Sanblaze Dualport Rack Riser Card Config v4.000 c1.1.xml +715 -0
  302. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2203-01 - Sanblaze Dualport Rack Riser Card Config v4.005 c1.5.xml +715 -0
  303. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2272-01 - GEN4 EDSFF x8 Module Config v4.000 c1.1.xml +771 -0
  304. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2282-01 - Sanblaze Singleport Rack Riser Card Config v4.000 c1.1.xml +666 -0
  305. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2282-01 - Sanblaze Singleport Rack Riser Card Config v4.002 c1.2.xml +666 -0
  306. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2318-01 - SANBlaze U.2 Rack Riser Card Config v4.000 c1.1.xml +715 -0
  307. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2322-03 - GEN4 M.2 M-Key Vertical Breaker Module Config v5.001 c1.3.xml +738 -0
  308. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2322-xx - GEN4 M.2 M-Key Vertical Breaker Module Config v5.000 c1.1.xml +637 -0
  309. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2334-xx - Gen4 EDSFF x4 Card Module Config v4.000 c1.1.xml +615 -0
  310. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2351-xx - GEN4 EDSFF x4 Card Module +Triggering Config v4.000 c1.1.xml +621 -0
  311. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2351-xx - GEN4 EDSFF x4 Card Module +Triggering Config v4.001 c1.2.xml +624 -0
  312. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2357-xx - PCIe GEN5 Card Module Config v5.000 c1.1.xml +1242 -0
  313. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2358-xx - PCIe GEN5 Card Module w Triggering Config v5.000 c1.1.xml +1250 -0
  314. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2378-xx - SANBlaze U.3 Rack Riser Card Config v4.000 c1.1.xml +682 -0
  315. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2378-xx - SANBlaze U.3 Rack Riser Card Config v4.001 c1.2.xml +698 -0
  316. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2395-01 - GEN4 M.2 M-Key Vertical Breaker Module w Triggering Config v5.000 c1.1.xml +647 -0
  317. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2395-02 - GEN4 M.2 M-Key Vertical Breaker Module w Triggering Config v5.001 c1.3.xml +742 -0
  318. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2395-03 - GEN4 M.2 M-Key Vertical Breaker Module w Triggering Config v5.001 c1.3.xml +742 -0
  319. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2396-xx - PCIe GEN5 Card Module w inrush Config v5.000 c1.1.xml +1242 -0
  320. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2403-xx - Gen4 PCIe Lite Module Config v4.000.xml +293 -0
  321. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2515-xx - PCIe GEN4 Card Module w Triggering - Inrush Limit Config v4.001 c1.3.xml +1212 -0
  322. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2652-xx - Gen5 PCIe Lite Module Config v4.000.xml +259 -0
  323. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2652-xx - Gen5 PCIe Lite Module Config v4.003.xml +288 -0
  324. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2658-xx - Gen5 PCIe Lite Module w Inrush Config v4.000.xml +259 -0
  325. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2661-xx - GEN5 PCIe U.3 Breaker Config w Triggering v5.000 c1.1.xml +813 -0
  326. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2662-xx - GEN5 PCIe U.3 Breaker Config v5.000 c1.1.xml +801 -0
  327. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2686-xx - Gen5 EDSFF E3 x4 Breaker Config v5.000.xml +741 -0
  328. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2686-xx - Gen5 EDSFF E3 x4 Breaker Config v5.001 c1.2.xml +799 -0
  329. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2686-xx - Gen5 EDSFF E3 x4 Breaker Config v5.002 c1.4.xml +743 -0
  330. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2686-xx - Gen5 EDSFF E3 x4 Breaker v5.000 c1.1.xml +741 -0
  331. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2692-xx - Gen5 EDSFF E3 x4 Breaker +Triggering Config v5.000.xml +742 -0
  332. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2692-xx - Gen5 EDSFF E3 x4 Breaker w Triggering Config v5.001 c1.2.xml +800 -0
  333. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2692-xx - Gen5 EDSFF E3 x4 Breaker w Triggering v5.000 c1.1.xml +742 -0
  334. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2766-xx - GEN4 EDSFF E1 x8 Breaker Config v5.000.xml +982 -0
  335. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2767-xx - GEN4 EDSFF E3 x8 Breaker Config v5.000.xml +982 -0
  336. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2768-xx - GEN4 EDSFF E3 2T x8 Breaker Config v5.000.xml +982 -0
  337. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2776-xx - GEN4 EDSFF E1 x8 Breaker +Triggering Config v5.000.xml +983 -0
  338. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2777-xx - GEN4 EDSFF E3 x8 Breaker +Triggering Config v5.000.xml +983 -0
  339. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2778-xx - GEN4 EDSFF E3 2T x8 Breaker +Triggering Config v5.000.xml +983 -0
  340. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2798-xx - PCIe GEN5 Card Module w Triggering w Inrush Limit Config v5.000 c1.1.xml +1250 -0
  341. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2804-xx - GEN5 MCIO x4 to U.2 Breaker Config v5.000 c1.1.xml +813 -0
  342. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2805-xx - GEN5 MCIO x4 to U.2 Breaker + Triggering Config v5.000 c1.1.xml +814 -0
  343. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2813-xx - Gen5 AIC to U.2 Breaker v5.002 c1.3.xml +871 -0
  344. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2814-xx - GEN5 AIC to U.2 Breaker + Triggering Config v5.000 c1.1 .xml +807 -0
  345. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2814-xx - Gen5 AIC to U.2 Breaker + Triggering Config v5.002 c1.3.xml +872 -0
  346. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2892-xx - GEN5 EDSFF E1 x4 Breaker.xml +799 -0
  347. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2901-xx - GEN5 M.2 M-Key Horizontal Breaker Config v5.000 c1.1.xml +753 -0
  348. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2902-xx - GEN5 M.2 M-Key Horizontal Breaker w Triggering Config v5.000 c1.1.xml +762 -0
  349. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2902-xx- -Gen5 M.2 Horizontal Breaker +Triggering Config 5.000 c1.1.xml +762 -0
  350. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2925-xx - GEN5 EDSFF E1 x4 Breaker +Triggering.xml +800 -0
  351. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Card Modules/QTL2954-01 - fake delete.xml +947 -0
  352. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1177-xx - HS Module Config v3.5 c1.5.xml +592 -0
  353. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1177-xx - HS Module Config v4.000 c1.5.xml +592 -0
  354. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1177-xx - HS Module Config v4.006 c1.8.xml +594 -0
  355. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1215-xx - Lite Module Config v3.50.xml +203 -0
  356. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1215-xx - Lite Module Config v4.000.xml +203 -0
  357. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1301-xx - HS Lite Module Config v3.50.xml +281 -0
  358. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1301-xx - HS Lite Module Config v4.000.xml +281 -0
  359. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1429-01 - EMC HS Lite Module Config v4.000.xml +281 -0
  360. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1429-02 - EMC HS Lite Module Config v4.002.xml +234 -0
  361. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1623-03 - 12G HS Lite Module Config v4.001.xml +266 -0
  362. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1623-04 - 12G HS Lite Module Config v4.001.xml +266 -0
  363. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1680-xx - SCA2 Lite Module Config v4.001.xml +333 -0
  364. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1689-01 - 12G HS Module Config v4.001 c1.1.xml +594 -0
  365. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1689-04 - 12G HS Module Config v4.002 c1.1.xml +580 -0
  366. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1689-05 - 12G HS Module Config v4.002 c1.1.xml +579 -0
  367. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1743-02 - PCIe SFF Module Config v4.003 c1.3.xml +794 -0
  368. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1743-02 - PCIe SFF Module Config v4.006 c1.4.xml +698 -0
  369. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1743-xx - PCIe SFF Module Config v4.000 c1.1.xml +789 -0
  370. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1753-xx - 12G Lite Module Config v4.000.xml +192 -0
  371. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL1921-01 - EMC 12G HS Lite Module Config v4.000.xml +233 -0
  372. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2207-01 - GEN 4 PCIe U.2 Drive Module.xml +733 -0
  373. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2207-02 - GEN 4 PCIe U.2 Drive Module v5.000.xml +753 -0
  374. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2207-03 - GEN 4 PCIe SFF HS Drive Module Triggering v5.001.xml +763 -0
  375. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2207-xx - GEN 4 PCIe U.2 Drive Module v4.001.xml +751 -0
  376. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2207-xx - GEN 4 PCIe U.2 Drive Module.xml +746 -0
  377. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2245-01 - GEN 4 PCIe U.3 HS Drive Module v4.000.xml +789 -0
  378. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2245-xx - GEN 4 PCIe U.3 HS Drive Module.xml +823 -0
  379. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2266-01 - GEN 4 PCIe SFF HS Drive Module Triggering.xml +743 -0
  380. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2266-02 - GEN 4 PCIe SFF HS Drive Module Triggering v4.002.xml +758 -0
  381. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2266-03 - GEN 4 PCIe SFF HS Drive Module Triggering v5.001.xml +764 -0
  382. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2266-XX - GEN 4 PCIe SFF HS Drive Module Triggering.xml +747 -0
  383. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2270-01 - GEN 4 PCIe U.3 HS Drive Module Triggering v4.000.xml +798 -0
  384. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2270-xx - GEN 4 PCIe U.3 HS Drive Module Triggering.xml +835 -0
  385. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2542-02 - 24G SAS Drive Breaker Module v5.001.xml +530 -0
  386. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2542-xx - 24G SAS Drive Breaker v5.000 c1.1.xml +530 -0
  387. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2543-xx - 24G SAS Drive Breaker +Triggering v5.000 c1.1.xml +539 -0
  388. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2543-xx - 24G SAS Drive Breaker +Triggering v5.001 c1.3.xml +539 -0
  389. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2602-xx - Multiprotocol Link Breaker.xml +414 -0
  390. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2645-xx - Gen5 PCIe U.2 Drive Module v5.000.xml +792 -0
  391. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2645-xx - Gen5 PCIe U.2 Drive Module v5.001.xml +792 -0
  392. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2645-xx - Gen5 PCIe U.2 Drive Module v5.003.xml +793 -0
  393. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2645-xx - Gen5 PCIe U.2 Drive Module v5.007.xml +816 -0
  394. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2645-xx - Gen5 PCIe U.2 Drive Module v5.008.xml +818 -0
  395. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2651-xx - Gen5 PCIe U.2 Drive Module + Triggering v5.000.xml +801 -0
  396. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2651-xx - Gen5 PCIe U.2 Drive Module + Triggering v5.001.xml +801 -0
  397. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2651-xx - Gen5 PCIe U.2 Drive Module + Triggering v5.003.xml +802 -0
  398. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2651-xx - Gen5 PCIe U.2 Drive Module v5.007.xml +825 -0
  399. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2661-xx - Gen5 U.3 Drive Module + Triggering v5.000.xml +813 -0
  400. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2662-xx - Gen5 PCIe U.3 Drive Module v5.000.xml +801 -0
  401. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2757-xx - Gen5 SFF Lite Breaker Module Config v4.000.xml +272 -0
  402. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2804-xx - Gen5 MCIO to U.2 Breaker +Triggering v5.003.xml +818 -0
  403. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2804-xx - Gen5 MCIO to U.2 Breaker v5.001.xml +813 -0
  404. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2805-xx - Gen5 MCIO to U.2 Breaker +Triggering v5.001.xml +814 -0
  405. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2805-xx - Gen5 MCIO to U.2 Breaker +Triggering v5.002.xml +819 -0
  406. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2805-xx - Gen5 MCIO to U.2 Breaker +Triggering v5.003.xml +819 -0
  407. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2892-xx - Gen5 EDSFF E1 x4 Breaker v5.001.xml +799 -0
  408. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2892-xx - Gen5 EDSFF E1 x4 Breaker v5.002.xml +801 -0
  409. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2940-xx - Gen5 EDSFF E3 x8 Breaker v5.000.xml +1105 -0
  410. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/QTL2941-xx - Gen5 EDSFF E3 x8 Breaker +Triggering v5.000.xml +1106 -0
  411. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Drive Modules/Standard Drive Module Config v3.5 c1.1.xml +463 -0
  412. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Switch Modules/QTL1490-xx - SATA Keyed HS Module Config v4.003 c1.6.xml +555 -0
  413. quarchpy/connection_specific/QPS/DeviceConfig/breaker/Switch Modules/QTL1490-xx - SATA Keyed HS Module Config v4.006 c1.8.xml +557 -0
  414. quarchpy/connection_specific/QPS/DeviceConfig/colors/default-colors.properties +64 -0
  415. quarchpy/connection_specific/QPS/DeviceConfig/colors/qtl2789-colors.properties +2 -0
  416. quarchpy/connection_specific/QPS/InstallType.dat +1 -0
  417. quarchpy/connection_specific/QPS/Power-Studio-QuickStart.pdf +0 -0
  418. quarchpy/connection_specific/QPS/QuarchPowerStudio.properties.xml +0 -1
  419. quarchpy/connection_specific/QPS/app.jar +0 -0
  420. quarchpy/connection_specific/QPS/app.properties +7 -0
  421. quarchpy/connection_specific/QPS/commandLineOptions.txt +19 -10
  422. quarchpy/connection_specific/QPS/license.txt +1 -0
  423. quarchpy/connection_specific/QPS/qis/3rdPartyLicenses/com.sun.istack-license.html +59 -0
  424. quarchpy/connection_specific/QPS/qis/3rdPartyLicenses/dorkbox-LICENSE.Apachev2 +218 -0
  425. quarchpy/connection_specific/QPS/qis/3rdPartyLicenses/jSerialComm-LICENSE-APACHE-2.0 +202 -0
  426. quarchpy/connection_specific/QPS/qis/3rdPartyLicenses/jSerialComm-LICENSE-LGPL-3.0 +165 -0
  427. quarchpy/connection_specific/QPS/qis/3rdPartyLicenses/jakarta.activation-license.html +59 -0
  428. quarchpy/connection_specific/QPS/qis/3rdPartyLicenses/jakarta.xml.bind-api-license.html +59 -0
  429. quarchpy/connection_specific/QPS/qis/3rdPartyLicenses/javassist-License.html +381 -0
  430. quarchpy/connection_specific/QPS/qis/3rdPartyLicenses/jmdns-LICENSE.txt +202 -0
  431. quarchpy/connection_specific/QPS/qis/3rdPartyLicenses/jna-AL2.0 +177 -0
  432. quarchpy/connection_specific/QPS/qis/3rdPartyLicenses/kotlin-stdlib-LICENSE-2.0.txt +202 -0
  433. quarchpy/connection_specific/QPS/qis/3rdPartyLicenses/netty-LICENSE.txt +202 -0
  434. quarchpy/connection_specific/QPS/qis/3rdPartyLicenses/netty-NOTICE.txt +239 -0
  435. quarchpy/connection_specific/QPS/qis/3rdPartyLicenses/slf4j-LICENSE.txt +24 -0
  436. quarchpy/connection_specific/QPS/qis/3rdPartyLicenses/usb4java-LICENSE.md +20 -0
  437. quarchpy/connection_specific/QPS/qis/Comms.properties.xml +102 -0
  438. quarchpy/connection_specific/QPS/qis/QuarchInstrumentServer.properties.xml +1 -0
  439. quarchpy/connection_specific/QPS/qis/README.txt +5 -5
  440. quarchpy/connection_specific/QPS/qis/help.txt +106 -61
  441. quarchpy/connection_specific/QPS/qis/license.txt +1 -0
  442. quarchpy/connection_specific/QPS/qis/qis-NoGUI.bat +1 -1
  443. quarchpy/connection_specific/QPS/qis/qis-NoGUI.sh +1 -1
  444. quarchpy/connection_specific/QPS/qis/qis.bat +1 -1
  445. quarchpy/connection_specific/QPS/qis/qis.jar +0 -0
  446. quarchpy/connection_specific/QPS/qis/qis.sh +1 -1
  447. quarchpy/connection_specific/QPS/qis/qis_lib/Collections-2.4.jar +0 -0
  448. quarchpy/connection_specific/QPS/qis/qis_lib/Desktop-1.1.jar +0 -0
  449. quarchpy/connection_specific/QPS/qis/qis_lib/EvalEx-3.5.0.jar +0 -0
  450. quarchpy/connection_specific/QPS/qis/qis_lib/Executor-3.13.jar +0 -0
  451. quarchpy/connection_specific/QPS/qis/qis_lib/JNA-1.2.jar +0 -0
  452. quarchpy/connection_specific/QPS/qis/qis_lib/OS-1.8.jar +0 -0
  453. quarchpy/connection_specific/QPS/qis/qis_lib/SystemTray-4.4.jar +0 -0
  454. quarchpy/connection_specific/QPS/qis/qis_lib/Updates-1.1.jar +0 -0
  455. quarchpy/connection_specific/QPS/qis/qis_lib/Utilities-1.46.jar +0 -0
  456. quarchpy/connection_specific/QPS/qis/qis_lib/cinterface-2.5.6.jar +0 -0
  457. quarchpy/connection_specific/QPS/qis/qis_lib/commons-compiler-3.1.12.jar +0 -0
  458. quarchpy/connection_specific/QPS/qis/qis_lib/commons-compiler-jdk-3.1.12.jar +0 -0
  459. quarchpy/connection_specific/QPS/qis/qis_lib/gson-2.13.1.jar +0 -0
  460. quarchpy/connection_specific/QPS/qis/qis_lib/istack-commons-runtime-3.0.11.jar +0 -0
  461. quarchpy/connection_specific/QPS/qis/qis_lib/jakarta.activation-api-2.1.0.jar +0 -0
  462. quarchpy/connection_specific/QPS/qis/qis_lib/jakarta.xml.bind-api-4.0.0.jar +0 -0
  463. quarchpy/connection_specific/QPS/qis/qis_lib/janino-3.1.12.jar +0 -0
  464. quarchpy/connection_specific/QPS/qis/qis_lib/javassist-3.29.2-GA.jar +0 -0
  465. quarchpy/connection_specific/QPS/qis/qis_lib/jaxb-core-4.0.0.jar +0 -0
  466. quarchpy/connection_specific/QPS/qis/qis_lib/jaxb-runtime-4.0.0.jar +0 -0
  467. quarchpy/connection_specific/QPS/qis/qis_lib/jmdns-3.6.0.jar +0 -0
  468. quarchpy/connection_specific/QPS/qis/qis_lib/jna-jpms-5.12.1.jar +0 -0
  469. quarchpy/connection_specific/QPS/qis/qis_lib/jna-platform-jpms-5.12.1.jar +0 -0
  470. quarchpy/connection_specific/QPS/qis/qis_lib/jul-to-slf4j-2.0.17.jar +0 -0
  471. quarchpy/connection_specific/QPS/qis/qis_lib/kotlin-stdlib-1.9.21.jar +0 -0
  472. quarchpy/connection_specific/QPS/qis/qis_lib/log4j-api-2.25.0.jar +0 -0
  473. quarchpy/connection_specific/QPS/qis/qis_lib/log4j-core-2.25.0.jar +0 -0
  474. quarchpy/connection_specific/QPS/qis/qis_lib/log4j-layout-template-json-2.25.0.jar +0 -0
  475. quarchpy/connection_specific/QPS/qis/qis_lib/log4j-slf4j2-impl-2.25.0.jar +0 -0
  476. quarchpy/connection_specific/QPS/qis/qis_lib/netty-buffer-4.2.2.Final.jar +0 -0
  477. quarchpy/connection_specific/QPS/qis/qis_lib/netty-codec-base-4.2.2.Final.jar +0 -0
  478. quarchpy/connection_specific/QPS/qis/qis_lib/netty-codec-http-4.2.2.Final.jar +0 -0
  479. quarchpy/connection_specific/QPS/qis/qis_lib/netty-common-4.2.2.Final.jar +0 -0
  480. quarchpy/connection_specific/QPS/qis/qis_lib/netty-handler-4.2.2.Final.jar +0 -0
  481. quarchpy/connection_specific/QPS/qis/qis_lib/netty-transport-4.2.2.Final.jar +0 -0
  482. quarchpy/connection_specific/QPS/qis/qis_lib/quarchcommon-2.1.9.jar +0 -0
  483. quarchpy/connection_specific/QPS/qis/qis_lib/slf4j-api-2.0.17.jar +0 -0
  484. quarchpy/connection_specific/QPS/qis/qis_lib/torridoncommon-1.2.8.jar +0 -0
  485. quarchpy/connection_specific/QPS/qis/qis_lib/usb4java-1.3.1.jar +0 -0
  486. quarchpy/connection_specific/QPS/qis/resources/filters/Example.txt +36 -0
  487. quarchpy/connection_specific/QPS/qis/resources/filters/iec_filters.xml +17 -0
  488. quarchpy/connection_specific/QPS/qis/resources/org/usb4java/darwin-x86-64/libusb4java.dylib +0 -0
  489. quarchpy/connection_specific/QPS/qis/resources/org/usb4java/linux-aarch64/libusb4java.so +0 -0
  490. quarchpy/connection_specific/QPS/qis/resources/org/usb4java/linux-arm/libusb4java.so +0 -0
  491. quarchpy/connection_specific/QPS/qis/resources/org/usb4java/linux-x86/libusb4java.so +0 -0
  492. quarchpy/connection_specific/QPS/qis/resources/org/usb4java/linux-x86-64/libusb4java.so +0 -0
  493. quarchpy/connection_specific/QPS/qis/resources/org/usb4java/win32-x86/libusb4java.dll +0 -0
  494. quarchpy/connection_specific/QPS/qis/resources/org/usb4java/win32-x86-64/libusb4java.dll +0 -0
  495. quarchpy/connection_specific/QPS/qps-command-reference.html +339 -0
  496. quarchpy/connection_specific/QPS/qps.jar +0 -0
  497. quarchpy/connection_specific/QPS/qps_lib/EvalEx-3.5.0.jar +0 -0
  498. quarchpy/connection_specific/QPS/qps_lib/commons-compiler-3.1.12.jar +0 -0
  499. quarchpy/connection_specific/QPS/qps_lib/commons-compiler-jdk-3.1.12.jar +0 -0
  500. quarchpy/connection_specific/QPS/qps_lib/commons-io-2.19.0.jar +0 -0
  501. quarchpy/connection_specific/QPS/qps_lib/controlsfx-11.2.2.jar +0 -0
  502. quarchpy/connection_specific/QPS/qps_lib/gson-2.13.1.jar +0 -0
  503. quarchpy/connection_specific/QPS/qps_lib/guava-33.4.8-jre.jar +0 -0
  504. quarchpy/connection_specific/QPS/qps_lib/istack-commons-runtime-3.0.11.jar +0 -0
  505. quarchpy/connection_specific/QPS/qps_lib/jakarta.activation-api-2.1.0.jar +0 -0
  506. quarchpy/connection_specific/QPS/qps_lib/jakarta.xml.bind-api-4.0.0.jar +0 -0
  507. quarchpy/connection_specific/QPS/qps_lib/janino-3.1.12.jar +0 -0
  508. quarchpy/connection_specific/QPS/qps_lib/jaxb-core-4.0.0.jar +0 -0
  509. quarchpy/connection_specific/QPS/qps_lib/jaxb-runtime-4.0.0.jar +0 -0
  510. quarchpy/connection_specific/QPS/qps_lib/jfa-1.2.0.jar +0 -0
  511. quarchpy/connection_specific/QPS/qps_lib/jfxutilities-1.0.5.jar +0 -0
  512. quarchpy/connection_specific/QPS/qps_lib/jna-5.17.0.jar +0 -0
  513. quarchpy/connection_specific/QPS/qps_lib/jul-to-slf4j-2.0.17.jar +0 -0
  514. quarchpy/connection_specific/QPS/qps_lib/log4j-api-2.25.0.jar +0 -0
  515. quarchpy/connection_specific/QPS/qps_lib/log4j-core-2.25.0.jar +0 -0
  516. quarchpy/connection_specific/QPS/qps_lib/log4j-layout-template-json-2.25.0.jar +0 -0
  517. quarchpy/connection_specific/QPS/qps_lib/log4j-slf4j2-impl-2.25.0.jar +0 -0
  518. quarchpy/connection_specific/QPS/qps_lib/netty-buffer-4.2.2.Final.jar +0 -0
  519. quarchpy/connection_specific/QPS/qps_lib/netty-codec-base-4.2.2.Final.jar +0 -0
  520. quarchpy/connection_specific/QPS/qps_lib/netty-codec-http-4.2.2.Final.jar +0 -0
  521. quarchpy/connection_specific/QPS/qps_lib/netty-common-4.2.2.Final.jar +0 -0
  522. quarchpy/connection_specific/QPS/qps_lib/netty-handler-4.2.2.Final.jar +0 -0
  523. quarchpy/connection_specific/QPS/qps_lib/netty-transport-4.2.2.Final.jar +0 -0
  524. quarchpy/connection_specific/QPS/qps_lib/nsmenufx-3.1.0.jar +0 -0
  525. quarchpy/connection_specific/QPS/qps_lib/quarchcommon-2.1.9.jar +0 -0
  526. quarchpy/connection_specific/QPS/qps_lib/qutils-1.0.2.jar +0 -0
  527. quarchpy/connection_specific/QPS/qps_lib/slf4j-api-2.0.17.jar +0 -0
  528. quarchpy/connection_specific/QPS/qps_lib/txw2-4.0.0.jar +0 -0
  529. quarchpy/connection_specific/QPS/resources/InstallBanner.bmp +0 -0
  530. quarchpy/connection_specific/QPS/resources/QPS.icns +0 -0
  531. quarchpy/connection_specific/QPS/resources/QPS.ico +0 -0
  532. quarchpy/connection_specific/QPS/resources/QPS.png +0 -0
  533. quarchpy/connection_specific/QPS/resources/QuarchIcon_128x128.png +0 -0
  534. quarchpy/connection_specific/QPS/resources/QuarchIcon_16x16.png +0 -0
  535. quarchpy/connection_specific/QPS/resources/QuarchIcon_256x256.png +0 -0
  536. quarchpy/connection_specific/QPS/resources/QuarchIcon_32x32.png +0 -0
  537. quarchpy/connection_specific/QPS/resources/QuarchIcon_64x64.png +0 -0
  538. quarchpy/connection_specific/QPS/resources/profiles/3_PHASE_PAM_AC_DEFAULT.rcf +533 -0
  539. quarchpy/connection_specific/QPS/resources/profiles/3_PHASE_PAM_AC_DEFAULT.scf +90 -0
  540. quarchpy/connection_specific/QPS/resources/profiles/3_PHASE_PAM_AC_FULL.rcf +830 -0
  541. quarchpy/connection_specific/QPS/resources/profiles/3_PHASE_PAM_AC_FULL.scf +144 -0
  542. quarchpy/connection_specific/QPS/resources/profiles/PAM_EXAMPLE_CONFIG.rcf +193 -0
  543. quarchpy/connection_specific/QPS/scriptCommands.txt +161 -22
  544. quarchpy/connection_specific/QPS/whats-new.txt +27 -0
  545. quarchpy/connection_specific/connection_QIS.py +1513 -1777
  546. quarchpy/connection_specific/connection_QPS.py +96 -11
  547. quarchpy/connection_specific/connection_ReST.py +29 -16
  548. quarchpy/connection_specific/connection_TCP.py +1 -1
  549. quarchpy/connection_specific/connection_Telnet.py +8 -2
  550. quarchpy/connection_specific/connection_USB.py +12 -13
  551. quarchpy/connection_specific/jdk_jres/__init__.py +0 -0
  552. quarchpy/connection_specific/jdk_jres/fix_permissions.py +60 -0
  553. quarchpy/connection_specific/mDNS.py +139 -0
  554. quarchpy/connection_specific/serial/rfc2217.py +2 -2
  555. quarchpy/connection_specific/serial/tools/list_ports_common.py +1 -4
  556. quarchpy/connection_specific/serial/tools/list_ports_linux.py +0 -6
  557. quarchpy/connection_specific/serial/tools/list_ports_osx.py +1 -6
  558. quarchpy/connection_specific/serial/tools/list_ports_posix.py +0 -5
  559. quarchpy/connection_specific/serial/tools/list_ports_windows.py +0 -6
  560. quarchpy/connection_specific/serial/urlhandler/protocol_alt.py +0 -4
  561. quarchpy/connection_specific/serial/urlhandler/protocol_hwgrep.py +0 -5
  562. quarchpy/connection_specific/serial/urlhandler/protocol_loop.py +1 -1
  563. quarchpy/connection_specific/serial/urlhandler/protocol_socket.py +1 -1
  564. quarchpy/connection_specific/serial/urlhandler/protocol_spy.py +0 -5
  565. quarchpy/connection_specific/usb_libs/usb1.py +4 -4
  566. quarchpy/debug/SystemTest.py +152 -41
  567. quarchpy/debug/module_debug.py +30 -29
  568. quarchpy/debug/simple_terminal.py +37 -28
  569. quarchpy/debug/upgrade_quarchpy.py +10 -14
  570. quarchpy/debug/versionCompare.py +2 -3
  571. quarchpy/device/__init__.py +10 -4
  572. quarchpy/device/device.py +1279 -402
  573. quarchpy/device/device_fixture_idn_info.py +8 -0
  574. quarchpy/device/device_idn_info.py +100 -0
  575. quarchpy/device/device_network_info.py +18 -0
  576. quarchpy/device/discovered_device.py +150 -0
  577. quarchpy/device/packet_processing.py +88 -0
  578. quarchpy/device/quarchArray.py +10 -6
  579. quarchpy/device/quarchPPM.py +395 -40
  580. quarchpy/device/quarchQPS.py +771 -294
  581. quarchpy/device/scanDevices.py +329 -230
  582. quarchpy/docs/CHANGES.rst +478 -328
  583. quarchpy/docs/_build/doctrees/CHANGES.doctree +0 -0
  584. quarchpy/docs/_build/doctrees/environment.pickle +0 -0
  585. quarchpy/docs/_build/doctrees/index.doctree +0 -0
  586. quarchpy/docs/_build/doctrees/readme.doctree +0 -0
  587. quarchpy/docs/_build/doctrees/source/changelog.doctree +0 -0
  588. quarchpy/docs/_build/doctrees/source/licenses.doctree +0 -0
  589. quarchpy/docs/_build/doctrees/source/modules.doctree +0 -0
  590. quarchpy/docs/_build/doctrees/source/quarchpy.calibration.doctree +0 -0
  591. quarchpy/docs/_build/doctrees/source/quarchpy.config_files.doctree +0 -0
  592. quarchpy/docs/_build/doctrees/source/quarchpy.connection_specific.doctree +0 -0
  593. quarchpy/docs/_build/doctrees/source/quarchpy.debug.doctree +0 -0
  594. quarchpy/docs/_build/doctrees/source/quarchpy.device.doctree +0 -0
  595. quarchpy/docs/_build/doctrees/source/quarchpy.disk_test.doctree +0 -0
  596. quarchpy/docs/_build/doctrees/source/quarchpy.doctree +0 -0
  597. quarchpy/docs/_build/doctrees/source/quarchpy.fio.doctree +0 -0
  598. quarchpy/docs/_build/doctrees/source/quarchpy.iometer.doctree +0 -0
  599. quarchpy/docs/_build/doctrees/source/quarchpy.qis.doctree +0 -0
  600. quarchpy/docs/_build/doctrees/source/quarchpy.qps.doctree +0 -0
  601. quarchpy/docs/_build/doctrees/source/quarchpy.user_interface.doctree +0 -0
  602. quarchpy/docs/_build/doctrees/source/quarchpy.utilities.doctree +0 -0
  603. quarchpy/docs/_build/doctrees/source/readme.doctree +0 -0
  604. quarchpy/docs/_build/html/.buildinfo +4 -4
  605. quarchpy/docs/_build/html/CHANGES.html +717 -484
  606. quarchpy/docs/_build/html/_sources/CHANGES.rst.txt +478 -314
  607. quarchpy/docs/_build/html/_sources/index.rst.txt +23 -23
  608. quarchpy/docs/_build/html/_sources/readme.rst.txt +41 -41
  609. quarchpy/docs/_build/html/_sources/source/modules.rst.txt +7 -7
  610. quarchpy/docs/_build/html/_sources/source/quarchpy.calibration.rst.txt +78 -78
  611. quarchpy/docs/_build/html/_sources/source/quarchpy.config_files.rst.txt +22 -22
  612. quarchpy/docs/_build/html/_sources/source/quarchpy.connection_specific.rst.txt +76 -76
  613. quarchpy/docs/_build/html/_sources/source/quarchpy.debug.rst.txt +38 -38
  614. quarchpy/docs/_build/html/_sources/source/quarchpy.device.rst.txt +54 -54
  615. quarchpy/docs/_build/html/_sources/source/quarchpy.disk_test.rst.txt +126 -126
  616. quarchpy/docs/_build/html/_sources/source/quarchpy.fio.rst.txt +30 -30
  617. quarchpy/docs/_build/html/_sources/source/quarchpy.iometer.rst.txt +22 -22
  618. quarchpy/docs/_build/html/_sources/source/quarchpy.qis.rst.txt +22 -22
  619. quarchpy/docs/_build/html/_sources/source/quarchpy.qps.rst.txt +22 -22
  620. quarchpy/docs/_build/html/_sources/source/quarchpy.rst.txt +50 -50
  621. quarchpy/docs/_build/html/_sources/source/quarchpy.user_interface.rst.txt +22 -22
  622. quarchpy/docs/_build/html/_sources/source/quarchpy.utilities.rst.txt +22 -22
  623. quarchpy/docs/_build/html/_static/alabaster.css +662 -700
  624. quarchpy/docs/_build/html/_static/basic.css +905 -904
  625. quarchpy/docs/_build/html/_static/doctools.js +106 -280
  626. quarchpy/docs/_build/html/_static/documentation_options.js +12 -11
  627. quarchpy/docs/_build/html/_static/language_data.js +192 -297
  628. quarchpy/docs/_build/html/_static/pygments.css +84 -82
  629. quarchpy/docs/_build/html/_static/searchtools.js +510 -404
  630. quarchpy/docs/_build/html/_static/sphinx_highlight.js +154 -0
  631. quarchpy/docs/_build/html/genindex.html +1876 -1430
  632. quarchpy/docs/_build/html/index.html +223 -188
  633. quarchpy/docs/_build/html/objects.inv +0 -0
  634. quarchpy/docs/_build/html/py-modindex.html +300 -306
  635. quarchpy/docs/_build/html/readme.html +137 -137
  636. quarchpy/docs/_build/html/search.html +123 -125
  637. quarchpy/docs/_build/html/searchindex.js +1 -1
  638. quarchpy/docs/_build/html/source/changelog.html +805 -537
  639. quarchpy/docs/_build/html/source/licenses.html +181 -181
  640. quarchpy/docs/_build/html/source/modules.html +222 -214
  641. quarchpy/docs/_build/html/source/quarchpy.calibration.html +147 -147
  642. quarchpy/docs/_build/html/source/quarchpy.config_files.html +136 -136
  643. quarchpy/docs/_build/html/source/quarchpy.connection_specific.html +1072 -615
  644. quarchpy/docs/_build/html/source/quarchpy.debug.html +241 -247
  645. quarchpy/docs/_build/html/source/quarchpy.device.html +4087 -1024
  646. quarchpy/docs/_build/html/source/quarchpy.disk_test.html +220 -220
  647. quarchpy/docs/_build/html/source/quarchpy.fio.html +218 -164
  648. quarchpy/docs/_build/html/source/quarchpy.html +823 -250
  649. quarchpy/docs/_build/html/source/quarchpy.iometer.html +271 -184
  650. quarchpy/docs/_build/html/source/quarchpy.qis.html +985 -249
  651. quarchpy/docs/_build/html/source/quarchpy.qps.html +186 -213
  652. quarchpy/docs/_build/html/source/quarchpy.user_interface.html +278 -319
  653. quarchpy/docs/_build/html/source/quarchpy.utilities.html +228 -211
  654. quarchpy/docs/_build/html/source/readme.html +147 -147
  655. quarchpy/docs/conf.py +56 -56
  656. quarchpy/docs/index.rst +23 -23
  657. quarchpy/docs/make.bat +35 -35
  658. quarchpy/docs/readme.rst +41 -41
  659. quarchpy/docs/source/modules.rst +7 -7
  660. quarchpy/docs/source/quarchpy.calibration.rst +78 -78
  661. quarchpy/docs/source/quarchpy.config_files.rst +22 -22
  662. quarchpy/docs/source/quarchpy.connection_specific.rst +76 -76
  663. quarchpy/docs/source/quarchpy.debug.rst +38 -38
  664. quarchpy/docs/source/quarchpy.device.rst +54 -54
  665. quarchpy/docs/source/quarchpy.disk_test.rst +126 -126
  666. quarchpy/docs/source/quarchpy.fio.rst +30 -30
  667. quarchpy/docs/source/quarchpy.iometer.rst +22 -22
  668. quarchpy/docs/source/quarchpy.qis.rst +22 -22
  669. quarchpy/docs/source/quarchpy.qps.rst +22 -22
  670. quarchpy/docs/source/quarchpy.rst +50 -50
  671. quarchpy/docs/source/quarchpy.user_interface.rst +22 -22
  672. quarchpy/docs/source/quarchpy.utilities.rst +22 -22
  673. quarchpy/fio/FIO_interface.py +258 -9
  674. quarchpy/fio/{test_performance_class.py → HIDEtest_performance_class.py} +533 -533
  675. quarchpy/fio/fioDiskFinder.py +1 -1
  676. quarchpy/fio/performanceClass.py +5 -4
  677. quarchpy/install_qps.py +369 -0
  678. quarchpy/qis/StreamHeaderInfo.py +5 -4
  679. quarchpy/qis/qisFuncs.py +235 -96
  680. quarchpy/qps/__init__.py +2 -2
  681. quarchpy/qps/qpsFuncs.py +237 -137
  682. quarchpy/run.py +103 -56
  683. quarchpy/user_interface/user_interface.py +201 -158
  684. quarchpy/utilities/TestCenter.py +64 -45
  685. quarchpy/utilities/TimeValue.py +4 -3
  686. quarchpy/utilities/Version.py +50 -0
  687. {quarchpy-2.1.11.dev8.dist-info → quarchpy-2.2.17.dev2.dist-info}/METADATA +171 -7
  688. quarchpy-2.2.17.dev2.dist-info/RECORD +763 -0
  689. {quarchpy-2.1.11.dev8.dist-info → quarchpy-2.2.17.dev2.dist-info}/WHEEL +1 -1
  690. quarchpy/connection_specific/QPS/License/LGPL.txt +0 -167
  691. quarchpy/connection_specific/QPS/License/Material-Icons.txt +0 -1
  692. quarchpy/connection_specific/QPS/License/NOTICE.txt +0 -143
  693. quarchpy/connection_specific/QPS/License/Netty-license.txt +0 -143
  694. quarchpy/connection_specific/QPS/License/controlsfx-license.txt +0 -26
  695. quarchpy/connection_specific/QPS/License/usb4java-license.txt +0 -167
  696. quarchpy/connection_specific/QPS/qis/License/LGPL.txt +0 -167
  697. quarchpy/connection_specific/QPS/qis/License/NOTICE.txt +0 -143
  698. quarchpy/connection_specific/QPS/qis/qis_lib/CInterface-1.5.01.jar +0 -0
  699. quarchpy/connection_specific/QPS/qis/qis_lib/CInterface-1.5.jar +0 -0
  700. quarchpy/connection_specific/QPS/qis/qis_lib/QuarchCommon-0.2.3.jar +0 -0
  701. quarchpy/connection_specific/QPS/qis/qis_lib/QuarchCommon-0.2.4.jar +0 -0
  702. quarchpy/connection_specific/QPS/qis/qis_lib/commons-lang3-3.2.1.jar +0 -0
  703. quarchpy/connection_specific/QPS/qis/qis_lib/libusb4java-1.3.0-darwin-x86-64.jar +0 -0
  704. quarchpy/connection_specific/QPS/qis/qis_lib/libusb4java-1.3.0-linux-aarch64.jar +0 -0
  705. quarchpy/connection_specific/QPS/qis/qis_lib/libusb4java-1.3.0-linux-arm.jar +0 -0
  706. quarchpy/connection_specific/QPS/qis/qis_lib/libusb4java-1.3.0-linux-x86-64.jar +0 -0
  707. quarchpy/connection_specific/QPS/qis/qis_lib/libusb4java-1.3.0-linux-x86.jar +0 -0
  708. quarchpy/connection_specific/QPS/qis/qis_lib/libusb4java-1.3.0-win32-x86-64.jar +0 -0
  709. quarchpy/connection_specific/QPS/qis/qis_lib/libusb4java-1.3.0-win32-x86.jar +0 -0
  710. quarchpy/connection_specific/QPS/qis/qis_lib/netty-all-4.1.43.Final.jar +0 -0
  711. quarchpy/connection_specific/QPS/qis/qis_lib/usb4java-1.3.0.jar +0 -0
  712. quarchpy/connection_specific/QPS/qps.bat +0 -2
  713. quarchpy/connection_specific/QPS/qps.sh +0 -2
  714. quarchpy/connection_specific/QPS/qps_lib/QuarchCommon-0.2.2.jar +0 -0
  715. quarchpy/connection_specific/QPS/qps_lib/QuarchCommon-0.2.3.jar +0 -0
  716. quarchpy/connection_specific/QPS/qps_lib/controlsfx-8.40.13.jar +0 -0
  717. quarchpy/connection_specific/QPS/qps_lib/controlsfx-8.40.14.jar +0 -0
  718. quarchpy/connection_specific/QPS/qps_lib/controlsfx-samples-8.40.13.jar +0 -0
  719. quarchpy/connection_specific/QPS/qps_lib/fxsampler-1.0.10.jar +0 -0
  720. quarchpy/connection_specific/QPS/qps_lib/jfxtras-labs-8.0-r6-SNAPSHOT.jar +0 -0
  721. quarchpy/connection_specific/QPS/qps_lib/jfxtras-labs-8.0-r6.jar +0 -0
  722. quarchpy/connection_specific/QPS/qps_lib/netty-all-4.0.37.Final.jar +0 -0
  723. quarchpy/connection_specific/QPS/qps_lib/netty-all-4.1.22.Final.jar +0 -0
  724. quarchpy/connection_specific/QPS/qps_lib/netty-all-4.1.22.Final_2.jar +0 -0
  725. quarchpy/connection_specific/QPS/qps_lib/netty-all-4.1.43.Final.jar +0 -0
  726. quarchpy/connection_specific/QPS/qps_lib/org.eclipse.fx.ide.css.jfx8_1.2.0.201501301049.jar +0 -0
  727. quarchpy/connection_specific/QPS/qps_lib/org.eclipse.fx.ide.css.jfx8_2.4.0.201605112122.jar +0 -0
  728. quarchpy-2.1.11.dev8.dist-info/RECORD +0 -473
  729. /quarchpy/connection_specific/QPS/{License → 3rdPartyLicenses}/Material-Icons-license.txt +0 -0
  730. /quarchpy/connection_specific/QPS/DeviceConfig/{QTL1995-02.XML → QTL1995-02.xml} +0 -0
  731. /quarchpy/connection_specific/QPS/DeviceConfig/{QTL1999-02.XML → QTL1999-02.xml} +0 -0
  732. /quarchpy/connection_specific/QPS/DeviceConfig/{QTL2312-01.XML → QTL2312-01.xml} +0 -0
  733. {quarchpy-2.1.11.dev8.dist-info → quarchpy-2.2.17.dev2.dist-info}/top_level.txt +0 -0
@@ -1,1777 +1,1513 @@
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
-
169
- i = self.deviceMulti(module)
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
- if (module in threadNameList):
182
- time.sleep(0.5)
183
- else:
184
- running = False
185
- time.sleep(0.1)
186
-
187
- # This is the function that is run when t1 is created. It is run in a seperate thread from
188
- # the main application so streaming can happen without blocking the main application from
189
- # doing other things. Within this function/thread you have to be very careful not to try
190
- # and 'communicate' with anything from other threads. If you do, you MUST use a thread safe
191
- # way of communicating. The thread creates it's own socket and should use that, NOT the objects socket
192
- # (which some of the comms with module functions will use by default).
193
- def startStreamThread(self, module, fileName, fileMaxMB, streamName, streamAverage, releaseOnData, separator, streamDuration = None):
194
- #Start module streaming and then read stream data
195
-
196
- stripes = ['Empty Header']
197
- #Send stream command so module starts streaming data into the backends buffer
198
- streamRes = self.sendAndReceiveCmd(self.streamSock, 'rec stream', device=module, betweenCommandDelay = 0)
199
- #printText(streamRes)
200
- if ('rec stream : OK' in streamRes):
201
- if (releaseOnData == False):
202
- self.StreamRunSentSemaphore.release()
203
- self.stripesEvent.clear()
204
- self.deviceDict[module][0:3] = [False, 'Running', 'Stream Running']
205
- else:
206
- self.StreamRunSentSemaphore.release()
207
- self.stripesEvent.clear()
208
- self.deviceDict[module][0:3] = [True, 'Stopped', module + " couldn't start because " + streamRes]
209
- return
210
- #If recording to file then get header for file
211
- if(fileName is not None):
212
-
213
- baseSamplePeriod = self.streamHeaderAverage(device=module, sock=self.streamSock)
214
- count=0
215
- maxTries=10
216
- while 'Header Not Available' in baseSamplePeriod:
217
- baseSamplePeriod = self.streamHeaderAverage(device=module, sock=self.streamSock)
218
- time.sleep(0.1)
219
- count += 1
220
- if count > maxTries:
221
- self.deviceDict[module][0:3] = [True, 'Stopped', 'Header not available']
222
- exit()
223
- version = self.streamHeaderVersion(device=module, sock=self.streamSock)
224
- with open(fileName, 'w') as f:
225
- timeStampHeader = datetime.datetime.now().strftime("%H:%M:%S:%f %d/%m/%y")
226
- formatHeader = self.streamHeaderFormat(device=module, sock=self.streamSock)
227
- formatHeader = formatHeader.replace(", ", separator)
228
- f.write(formatHeader + '\n')
229
- numStripesPerRead = 4096
230
- maxFileExceeded = False
231
- openAttempts = 0
232
- leftover = 0
233
- remainingStripes = []
234
- streamOverrun = False
235
- streamComplete = False
236
-
237
- # baseSamplePeriod is a string in the form [int][unit]
238
- # where the unit can be S,mS,uS,nS
239
- # we need to convert it to a float number of seconds
240
- # and we also derive the baseSampleUnits in string and numeric form
241
- if 'ns' in baseSamplePeriod.lower():
242
- baseSampleUnitText = 'ns'
243
- baseSampleUnitExponent = -9
244
- elif 'us' in baseSamplePeriod.lower():
245
- baseSampleUnitText = 'us'
246
- baseSampleUnitExponent = -6
247
- elif 'ms' in baseSamplePeriod.lower():
248
- baseSampleUnitText = 'ms'
249
- baseSampleUnitExponent = -3
250
- elif 'S' in baseSamplePeriod.lower():
251
- baseSampleUnitText = 's'
252
- baseSampleUnitExponent = 0
253
- else:
254
- raise ValueError("couldn't decode samplePeriod")
255
-
256
- baseSamplePeriodS = int(re.search('^\d*\.?\d*', baseSamplePeriod).group())*(10**baseSampleUnitExponent)
257
-
258
- # TODO: MD Thinks this implements software averaging, is unused and now performed in QIS
259
- if streamAverage != None:
260
- #Matt converting streamAveraging into number
261
- streamAverage = self.convertStreamAverage(streamAverage)
262
-
263
- stripesPerAverage = float(streamAverage) / (float(baseSamplePeriod) * 4e-6)
264
- isRun = True
265
- while isRun:
266
- try:
267
- with open(fileName, 'ab') as f:
268
- # Until the event threadRunEvent is set externally to this thread,
269
- # loop and read from the stream
270
- i = self.deviceMulti(module)
271
- while self.stopFlagList[i] and (not streamOverrun) and (not streamComplete):
272
- #now = time.time()
273
- streamOverrun, removeChar, newStripes = self.streamGetStripesText(self.streamSock, module, numStripesPerRead)
274
- newStripes = newStripes.replace(b' ', str.encode(separator))
275
- #print (time.time() - now)
276
- if streamOverrun:
277
- self.deviceDict[module][0:3] = [True, 'Stopped', 'Device buffer overrun']
278
- # TODO: MD Why don't we return isEmpty in the tuple, instead of having this confusing test?
279
- if (removeChar == -6 and len(newStripes) == 6):
280
- isEmpty = True
281
- else:
282
- isEmpty = False
283
- if isEmpty == False:
284
- #Writes in file if not too big else stops streaming
285
- statInfo = os.stat(fileName)
286
- fileMB = statInfo.st_size / 1048576
287
- try:
288
- int(fileMaxMB)
289
- except:
290
- continue
291
- if int(fileMB) < int(fileMaxMB):
292
- if (releaseOnData == True):
293
- self.StreamRunSentSemaphore.release()
294
- self.stripesEvent.clear()
295
- releaseOnData = False
296
- # TODO: MD Thinks this implements software averaging, is unused and now performed in QIS where required
297
- if(streamAverage != None):
298
- leftover, remainingStripes = self.averageStripes(leftover, stripesPerAverage, newStripes[:removeChar], f, remainingStripes)
299
- else:
300
- # if we have a fixed streamDuration
301
- if streamDuration != None:
302
- # Get the last data line in the file
303
- lastLine = newStripes.splitlines()[-3] # the last data line is followed by 'eof' and '>'
304
- lastTime = lastLine.decode().split(separator)[0] # get the first (time) entry
305
-
306
- # if the last entry is still within the required stream length, write the whole lot
307
- if int(lastTime) < int(streamDuration/(10**baseSampleUnitExponent)): # < rather than <= because we start at 0
308
- f.write(newStripes[:removeChar])
309
- # else write each line individually until we have reached the desired endpoint
310
- else:
311
- for thisLine in newStripes.splitlines()[:-2]:
312
- lastTime = thisLine.decode().split(separator)[0]
313
- if int(lastTime) < int(streamDuration/(10**baseSampleUnitExponent)):
314
- f.write(thisLine + b'\r' + b'\n') # Put the CR back on the end
315
- else:
316
- streamComplete = True
317
- break
318
- else:
319
- f.write(newStripes[:removeChar])
320
-
321
- else:
322
- maxFileExceeded = True
323
- #printText('QisInterface file size exceeded in loop 1- breaking')
324
- maxFileStatus = self.streamBufferStatus(device=module, sock=self.streamSock)
325
- f.write('Warning: Max file size exceeded before end of stream.\n')
326
- f.write('Unrecorded stripes in buffer when file full: ' + maxFileStatus + '.')
327
- self.deviceDict[module][0:3] = [True, 'Stopped', 'User defined max filesize reached']
328
- break
329
- else:
330
- # there's no stripes in the buffer - it's not filling up fast -
331
- # sleeps so we don't spam qis with requests (seems to make QIS crash)
332
- # it might be clever to change the sleep time accoring to the situation
333
- # e.g. wait longer with higher averaging or lots of no stripes in a row
334
- time.sleep(0.1)
335
- streamStatus = self.streamRunningStatus(device=module, sock=self.streamSock)
336
- if streamOverrun:
337
- #printText('QisInterface overrun - breaking')
338
- break
339
- elif "Stopped" in streamStatus:
340
- self.deviceDict[module][0:3] = [True, 'Stopped', 'User halted stream']
341
- break
342
- #printText('Left while 1')
343
- self.sendAndReceiveCmd(self.streamSock, 'rec stop', device=module, betweenCommandDelay = 0)
344
- if (not streamOverrun) and (not maxFileExceeded):
345
- self.deviceDict[module][0:3] = [False, 'Stopped', 'Stream stopped - emptying buffer']
346
- # print self.streamBufferStatus(device=module, sock=self.streamSock)
347
- if (not maxFileExceeded):
348
- #If the backend buffer still has data then keep reading it out
349
- #printText('Streaming stopped. Emptying data left in QIS buffer to file (' + self.streamBufferStatus(device=module, sock=self.streamSock) + ')')
350
- streamOverrun, removeChar, newStripes = self.streamGetStripesText(self.streamSock, module, numStripesPerRead)
351
- # TODO: Why don't we return isEmpty in the tuple, instead of having this confusing test?
352
- if (removeChar == -6 and len(newStripes) == 6):
353
- isEmpty = True
354
- else:
355
- isEmpty = False
356
- while isEmpty == False: # if newStripes has length 6 then it only contains 'eof\r\n'
357
- statInfo = os.stat(fileName)
358
- fileMB = statInfo.st_size / 1048576
359
- try:
360
- int(fileMaxMB)
361
- except:
362
- continue
363
- if int(fileMB) < int(fileMaxMB):
364
- if streamComplete != True:
365
- if(streamAverage != None):
366
- leftover, remainingStripes = self.averageStripes(leftover, stripesPerAverage, newStripes[:removeChar], f, remainingStripes)
367
- else:
368
- newStripes = newStripes.replace(b' ', str.encode(separator))
369
- f.write(newStripes[:removeChar])
370
- else:
371
- if not maxFileExceeded:
372
- maxFileStatus = self.streamBufferStatus(device=module, sock=self.streamSock)
373
- maxFileExceeded = True
374
- self.deviceDict[module][0:3] = [True, 'Stopped', 'User defined max filesize reached']
375
- break
376
- #time.sleep(0.01) #reduce speed of loop to stop spamming qis
377
- streamOverrun, removeChar, newStripes = self.streamGetStripesText(self.streamSock, module, numStripesPerRead, skipStatusCheck=True)
378
- if removeChar == -6:
379
- if len(newStripes) == 6:
380
- isEmpty = True
381
- if maxFileExceeded:
382
- f.write(b'Warning: Max file size exceeded before end of stream.\n')
383
- f.write(b'Unrecorded stripes in buffer when file full: ' + maxFileStatus + '.')
384
- logging.warning('Max file size exceeded. Some data has not been saved to file: ' + maxFileStatus + '.')
385
-
386
- #printText('Stripes in buffer now: ' + self.streamBufferStatus(device=module, sock=self.streamSock))
387
-
388
- if streamOverrun:
389
- self.deviceDict[module][0:3] = [True, 'Stopped', 'Device buffer overrun - QIS buffer empty']
390
- elif not maxFileExceeded:
391
- self.deviceDict[module][0:3] = [False, 'Stopped', 'Stream stopped']
392
- time.sleep(0.2)
393
- isRun = False
394
- except IOError as err:
395
- #printText('\n\n!!!!!!!!!!!!!!!!!!!! IO Error in QisInterface !!!!!!!!!!!!!!!!!!!!\n\n')
396
- time.sleep(0.5)
397
- openAttempts += 1
398
- if openAttempts > 4:
399
- logging.error('\n\n!!!!!!!!!!!!!!!!!!!! Too many IO Errors in QisInterface !!!!!!!!!!!!!!!!!!!!\n\n')
400
- raise err
401
-
402
- # This is the function that is ran when t1 is created. It is ran in a seperate thread from
403
- # the main application so streaming can happen without blocking the main application from
404
- # doing other things. Within this function/thread you have to be very careful not to try
405
- # and 'communicate' with anything from other threads. If you do, you MUST use a thread safe
406
- # way of communicating. The thread creates it's own socket and should use that NOT the objects socket
407
- # (which some of the comms with module functions will use by default).
408
-
409
- def startStreamThreadQPS(self, module, fileName, fileMaxMB, streamName, streamAverage, releaseOnData, separator):
410
-
411
- # Start module streaming and then read stream data
412
- # self.sendAndReceiveCmd(self.streamSock, 'stream mode resample 10mS', device=module, betweenCommandDelay=0)
413
- self.sendAndReceiveCmd(self.streamSock, 'stream mode header v3', device=module, betweenCommandDelay=0)
414
- self.sendAndReceiveCmd(self.streamSock, 'stream mode power enable', device=module, betweenCommandDelay=0)
415
- self.sendAndReceiveCmd(self.streamSock, 'stream mode power total enable', device=module, betweenCommandDelay=0)
416
-
417
- self.qps_record_start_time = time.time() * 1000
418
-
419
- stripes = ['Empty Header']
420
- # Send stream command so module starts streaming data into the backends buffer
421
- streamRes = self.sendAndReceiveCmd(self.streamSock, 'rec stream', device=module, betweenCommandDelay=0)
422
- # printText(streamRes)
423
- if ('rec stream : OK' in streamRes):
424
- if (releaseOnData == False):
425
- self.StreamRunSentSemaphore.release()
426
- self.stripesEvent.clear()
427
- self.deviceDict[module][0:3] = [False, 'Running', 'Stream Running']
428
- else:
429
- self.StreamRunSentSemaphore.release()
430
- self.stripesEvent.clear()
431
- self.deviceDict[module][0:3] = [True, 'Stopped', module + " couldn't start because " + streamRes]
432
- return
433
-
434
- # If recording to file then get header for file
435
- if (fileName is not None):
436
-
437
- baseSamplePeriod = self.streamHeaderAverage(device=module, sock=self.streamSock)
438
- count = 0
439
- maxTries = 10
440
- while 'Header Not Available' in baseSamplePeriod:
441
- baseSamplePeriod = self.streamHeaderAverage(device=module, sock=self.streamSock)
442
- time.sleep(0.1)
443
- count += 1
444
- if count > maxTries:
445
- self.deviceDict[module][0:3] = [True, 'Stopped', 'Header not available']
446
- exit()
447
- version = self.streamHeaderVersion(device=module, sock=self.streamSock)
448
-
449
- numStripesPerRead = 4096
450
- maxFileExceeded = False
451
- openAttempts = 0
452
- leftover = 0
453
- remainingStripes = []
454
- streamOverrun = False
455
- if streamAverage != None:
456
- # Matt converting streamAveraging into number
457
- streamAverage = self.convertStreamAverage(streamAverage)
458
-
459
- stripesPerAverage = float(streamAverage) / (float(baseSamplePeriod) * 4e-6)
460
-
461
- isRun = True
462
-
463
- self.create_dir_structure(module, fileName)
464
-
465
- while isRun:
466
- try:
467
- # with open(fileName, 'ab') as f:
468
- # Until the event threadRunEvent is set externally to this thread,
469
- # loop and read from the stream
470
- i = self.deviceMulti(module)
471
- while self.stopFlagList[i] and (not streamOverrun):
472
- # now = time.time()
473
- streamOverrun, removeChar, newStripes = self.streamGetStripesText(self.streamSock, module,
474
- numStripesPerRead)
475
- newStripes = newStripes.replace(b' ', str.encode(separator))
476
- # print(newStripes)
477
- # print(len(newStripes))
478
-
479
- # print (time.time() - now)
480
- if streamOverrun:
481
- self.deviceDict[module][0:3] = [True, 'Stopped', 'Device buffer overrun']
482
- if (removeChar == -6 and len(newStripes) == 6):
483
- isEmpty = True
484
- else:
485
- isEmpty = False
486
- if isEmpty == False:
487
- # Writes in file if not too big else stops streaming
488
- # print(newStripes)
489
-
490
- x = newStripes[:removeChar]
491
- y = x.decode("utf-8")
492
-
493
- print(f"decoded stripe : {y}")
494
-
495
- # Writing multiple stripes
496
- if "\r\n" in y:
497
- y = y.split("\r\n")
498
-
499
- if self.has_digitals:
500
- # Write qps files for PAM
501
- for stripes in y:
502
- if stripes:
503
- stripe = stripes.split(",")
504
- self.write_stripe_to_files_PAM(stripe)
505
- else:
506
- # Write qps files for PPM
507
- for stripes in y:
508
- if stripes:
509
- stripe = stripes.split(",")
510
- self.write_stripe_to_files_HD(stripe)
511
-
512
- else:
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
-
527
- else:
528
- # there's no stripes in the buffer - it's not filling up fast -
529
- # sleeps so we don't spam qis with requests (seems to make QIS crash)
530
- # it might be clever to change the sleep time accoring to the situation
531
- # e.g. wait longer with higher averaging or lots of no stripes in a row
532
- time.sleep(0.1)
533
- streamStatus = self.streamRunningStatus(device=module, sock=self.streamSock)
534
- if streamOverrun:
535
- # printText('QisInterface overrun - breaking')
536
- break
537
- elif "Stopped" in streamStatus:
538
- self.deviceDict[module][0:3] = [True, 'Stopped', 'User halted stream']
539
- break
540
-
541
- # printText('Left while 1')
542
- self.sendAndReceiveCmd(self.streamSock, 'rec stop', device=module, betweenCommandDelay=0)
543
-
544
- time.sleep(0.2)
545
- isRun = False
546
- except IOError as err:
547
- # printText('\n\n!!!!!!!!!!!!!!!!!!!! IO Error in QisInterface !!!!!!!!!!!!!!!!!!!!\n\n')
548
- time.sleep(0.5)
549
- openAttempts += 1
550
- if openAttempts > 4:
551
- logging.error(
552
- '\n\n!!!!!!!!!!!!!!!!!!!! Too many IO Errors in QisInterface !!!!!!!!!!!!!!!!!!!!\n\n')
553
- raise err
554
-
555
- self.create_index_file()
556
- if self.has_digitals:
557
- self.create_index_file_digitals()
558
-
559
- self.create_qps_file(module)
560
-
561
- def write_stripe_to_files_HD(self, stripe):
562
- # Cycle through items in stripe
563
- for index, item in enumerate(stripe):
564
- if index == 0:
565
- continue
566
- with open(os.path.join(self.qps_record_dir_path, "data000",
567
- f"data000_00{index - 1}_000000000"),
568
- "ab") as file1:
569
-
570
- x = struct.pack(">d", int(item))
571
- # logging.debug(item, x)
572
- file1.write(x)
573
-
574
- def write_stripe_to_files_PAM(self, stripe):
575
- # Note to reader - List should be ordered 1>x on analogue and digitals
576
- counter = 0
577
- for group in self.streamGroups.groups:
578
- for i, channel in enumerate(group.channels):
579
- # incrementing here so we skip stripe[0] which is time
580
- counter += 1
581
-
582
- x = i
583
- while len(str(x)) < 3:
584
- x = "0" + str(x)
585
-
586
- # Write all in group 0 to analogue
587
- if group.group_id == 0:
588
-
589
- with open(os.path.join(self.qps_record_dir_path, "data000",
590
- f"data000_{x}_000000000"),
591
- "ab") as file1:
592
- x = struct.pack(">d", int(stripe[counter]))
593
- # logging.debug(item, x)
594
- file1.write(x)
595
- else:
596
- # Write all in group 1 to digital
597
- with open(os.path.join(self.qps_record_dir_path, "data101",
598
- f"data101_{x}_000000000"),
599
- "ab") as file1:
600
- x = struct.pack(">d", int(stripe[counter]))
601
- # logging.debug(item, x)
602
- file1.write(x)
603
-
604
-
605
- # Send text and get the backends response. - acts as wrapper to the sendAndReceiveText, intended to provide some extra convenience
606
- # when sending commands to module (as opposed to back end)
607
- # 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
608
- # After command is sent wait for betweenCommandDelay which defaults to 0 but can be specified to add a delay between commands
609
- # The objects connection needs to be opened (connect()) before this is used
610
- def sendCmd(self, device='', cmd='$help', sock=None, readUntilCursor=True, betweenCommandDelay=0.0, expectedResponse = True):
611
- if sock==None:
612
- sock = self.sock
613
- if not (device == ''):
614
- self.deviceDictSetup(device)
615
-
616
- if expectedResponse is False:
617
- self.sendText(sock, cmd, device)
618
- return
619
-
620
- res = self.sendAndReceiveText(sock, cmd, device, readUntilCursor)
621
- if (betweenCommandDelay > 0):
622
- time.sleep(betweenCommandDelay)
623
- #If ends with cursor get rid of it
624
- if res[-1:] == self.cursor:
625
- res = res[:-3] #remove last three chars - hopefully '\r\n>'
626
- # time.sleep(0.1)
627
- return res.decode()
628
-
629
-
630
- def sendAndReceiveCmd(self, sock=None, cmd='$help', device='', readUntilCursor=True, betweenCommandDelay=0.0):
631
- if sock==None:
632
- sock = self.sock
633
- if not (device == ''):
634
- self.deviceDictSetup(device)
635
- if self.pythonVersion == '3':
636
- res = self.sendAndReceiveText(sock, cmd, device, readUntilCursor).decode()
637
- else:
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:] == '>':
643
- res = res[:-3] #remove last three chars - hopefully '\r\n>'
644
- return cmd + ' : ' + res
645
-
646
- # Send text to the back end then read it's response
647
- # The objects connection needs to be opened (connect()) before this is used
648
- # 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
649
- def sendAndReceiveText(self, sock, sentText='$help', device='', readUntilCursor=True):
650
- self.sockSemaphore.acquire()
651
- try:
652
- self.sendText(sock, sentText, device)
653
- if self.pythonVersion == '3':
654
- res = bytearray()
655
- res.extend(self.rxBytes(sock))
656
- #Somtimes we just get one cursor back of currently unknown origins
657
- #If that happens discard it and read again
658
- if len(res)==0:
659
- logging.warning("empty response from QIS. Retrying.")
660
- streamResp=self.sendAndReceiveText(sock, "stream?",device,readUntilCursor).lower()
661
- if len(streamResp)==0:
662
- raise("Empty response from QIS twice in a row")
663
- else:
664
- logging.warning("Response recovered Successfully. Continuing.")
665
- res = bytearray()
666
- res.extend(self.rxBytes(sock))
667
- if len(res) == 0:
668
- raise ("empty response from QIS after second ")
669
- if res[0] == self.cursor:
670
- #res[0] = self.rxBytes(sock)
671
- logging.warning('Only Returned Cursor!!!!!')
672
- #If create socked fail (between backend and tcp/ip module)
673
- cba = 'Create Socket Fail'
674
- if cba.encode() == res[0]:
675
- logging.warning(res[0].decode())
676
- cba = 'Connection Timeout'
677
- if cba.encode() == res[0]:
678
- logging.warning(res[0].decode())
679
- #If reading until a cursor comes back then keep reading until a cursor appears or max tries exceeded
680
- if readUntilCursor:
681
- maxReads = 1000
682
- count = 1
683
- #check for cursor at end of read and if not there read again
684
- while res[-1:] != self.cursor:
685
- res.extend(self.rxBytes(sock))
686
- count += 1
687
- if count >= maxReads:
688
- raise Exception(' Count = Error: max reads exceeded before cursor returned')
689
- return res
690
- else:
691
- res = self.rxBytes(sock)
692
- #Somtimes we just get one cursor back of currently unknown origins
693
- #If that happens discard it and read again
694
- if res == self.cursor:
695
- #printText(" CURSOR ONLY")
696
- res = self.rxBytes(sock)
697
- #If create socked fail (between backend and tcp/ip module)
698
- if 'Create Socket Fail' in res:
699
- raise Exception(res)
700
- if 'Connection Timeout' in res:
701
- raise Exception(res)
702
- #If reading until a cursor comes back then keep reading until a cursor appears or max tries exceeded
703
- if readUntilCursor:
704
- maxReads = 1000
705
- count = 1
706
- #check for cursor at end of read and if not there read again
707
- while res[-1:] != self.cursor:
708
- res += self.rxBytes(sock)
709
- count += 1
710
- if count >= maxReads:
711
- raise Exception(' Count = Error: max reads exceeded before cursor returned')
712
- return res
713
-
714
- except Exception as e:
715
- raise e
716
- finally:
717
- self.sockSemaphore.release()
718
-
719
- def rxBytes(self,sock):
720
- #sock.setblocking(0) #make socket non-blocking
721
- #printText('rxBytes')
722
- maxExceptions=10
723
- exceptions=0
724
- maxReadRepeats=50
725
- readRepeats=0
726
- timeout_in_seconds = 10
727
- #Keep trying to read bytes until we get some, unless number of read repeads or exceptions is exceeded
728
- while True:
729
- try:
730
- #select.select returns a list of waitable objects which are ready. On windows it has to be sockets.
731
- #The first arguement is a list of objects to wait for reading, second writing, third 'exceptional condition'
732
- #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.
733
- ready = select.select([sock], [], [], timeout_in_seconds)
734
- #time.sleep(0.1)
735
- #ready = [1,2]
736
- if ready[0]:
737
- ret = sock.recv(self.maxRxBytes)
738
- #time.sleep(0.1)
739
- return ret
740
- else:
741
- #printText('rxBytes - readRepeats + 1')
742
-
743
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
744
- sock.connect((self.host, self.port))
745
- sock.settimeout(5)
746
-
747
- try:
748
- welcomeString = self.sock.recv(self.maxRxBytes).rstrip()
749
- welcomeString = 'Connected@' + self.host + ':' + str(self.port) + ' ' + '\n ' + welcomeString
750
- printText('New Welcome:' + welcomeString)
751
- except Exception as e:
752
- logging.error('tried and failed to get new welcome')
753
- raise e
754
-
755
- readRepeats=readRepeats+1
756
- time.sleep(0.5)
757
-
758
- except Exception as e:
759
- #printText('rxBytes - exceptions + 1')
760
- exceptions=exceptions+1
761
- time.sleep(0.5)
762
- raise e
763
-
764
- #If read repeats has been exceeded we failed to get any data on this read.
765
- # !!! This is likely to break whatever called us !!!
766
- if readRepeats >= maxReadRepeats:
767
- logging.error('Max read repeats exceeded - returning.')
768
- return 'No data received from QIS'
769
- #If number of exceptions exceeded then give up by exiting
770
- if exceptions >= maxExceptions:
771
- logging.error('Max exceptions exceeded - exiting') #exceptions are probably 10035 non-blocking socket could not complete immediatley
772
- exit()
773
-
774
- # Send text to the back end don't read it's response
775
- # The objects connection needs to be opened (connect()) before this is used
776
- def sendText(self, sock, message='$help', device=''):
777
- if device != '':
778
- specialTimeout = '%500000'
779
- message = device + specialTimeout + ' ' + message
780
- #printText('Sending: "' + message + '" ' + self.host + ':' + str(self.port))
781
-
782
- if self.pythonVersion == 2:
783
- sock.sendall(message + '\r\n')
784
- else:
785
- convM = message + '\r\n'
786
- sock.sendall(convM.encode('utf-8'))
787
- return 'Sent:' + message
788
-
789
- # Query the backend for a list of connected modules. A $scan command is sent to refresh the list of devices,
790
- # 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
791
- # The objects connection needs to be opened (connect()) before this is used
792
- def getDeviceList(self, sock=None):
793
-
794
- if sock == None:
795
- sock = self.sock
796
- scanWait = 2
797
- #printText('Scanning for devices and waiting ' + str(scanWait) + ' seconds.')
798
- if self.pythonVersion == '3':
799
- #devString = self.sendAndReceiveText(sock, '$scan').decode
800
- #time.sleep(scanWait)
801
- devString = self.sendAndReceiveText(sock, '$list').decode()
802
- else:
803
- #devString = self.sendAndReceiveText(sock, '$scan')
804
- #time.sleep(scanWait)
805
- devString = self.sendAndReceiveText(sock, '$list')
806
-
807
- devString = devString.replace('>', '')
808
- devString = devString.replace(r'\d+\) ', '')
809
-
810
- #printText('"' + devString + '"')
811
- devString = devString.split('\r\n')
812
- devString = filter(None, devString) #remove empty elements
813
- return devString
814
-
815
- def get_list_details(self, sock=None):
816
- if sock == None:
817
- sock = self.sock
818
- scanWait = 2
819
- # printText('Scanning for devices and waiting ' + str(scanWait) + ' seconds.')
820
- if self.pythonVersion == '3':
821
- # devString = self.sendAndReceiveText(sock, '$scan').decode
822
- # time.sleep(scanWait)
823
- devString = self.sendAndReceiveText(sock, '$list details').decode()
824
- else:
825
- # devString = self.sendAndReceiveText(sock, '$scan')
826
- # time.sleep(scanWait)
827
- devString = self.sendAndReceiveText(sock, '$list details')
828
-
829
- devString = devString.replace('>', '')
830
- devString = devString.replace(r'\d+\) ', '')
831
-
832
- # printText('"' + devString + '"')
833
- devString = devString.split('\r\n')
834
- devString = [x for x in devString if x] # remove empty elements
835
- return devString
836
-
837
- def scanIP(QisConnection, ipAddress):
838
- """
839
- Triggers QIS to look at a specific IP address for a quarch module
840
-
841
- Parameters
842
- ----------
843
- QisConnection : QpsInterface
844
- The interface to the instance of QPS you would like to use for the scan.
845
- ipAddress : str
846
- The IP address of the module you are looking for eg '192.168.123.123'
847
- sleep : int, optional
848
- This optional variable sleeps to allow the network to scan for the module before allowing new commands to be sent to QIS.
849
- """
850
-
851
- logging.debug("Starting QIS IP Address Lookup at " + ipAddress)
852
- if not ipAddress.lower().__contains__("tcp::"):
853
- ipAddress = "TCP::" + ipAddress
854
- response = "No response from QIS Scan"
855
- try:
856
- response = QisConnection.sendCmd(cmd="$scan " + ipAddress, expectedResponse=True)
857
- # valid response is "Located device: 192.168.1.2"
858
- if "located" in response.lower():
859
- logging.debug(response)
860
- # return the valid response
861
- return response
862
- else:
863
- logging.warning("No module found at " + ipAddress)
864
- logging.warning(response)
865
- return response
866
-
867
- except Exception as e:
868
- logging.warning(e)
869
- logging.warning("No module found at " + ipAddress)
870
-
871
-
872
-
873
-
874
- def GetQisModuleSelection(self, favouriteOnly=True , additionalOptions=[], scan=True):
875
- tableHeaders =["Modules"]
876
- if scan :
877
- foundDevices = self.qis_scan_devices(scan=scan, favouriteOnly=favouriteOnly)
878
-
879
- myDeviceID = listSelection(title="Select a module",message="Select a module",selectionList=foundDevices,
880
- additionalOptions= additionalOptions, nice=True, tableHeaders=tableHeaders,
881
- indexReq=True)
882
- return myDeviceID
883
-
884
- def qis_scan_devices(self, scan=True, favouriteOnly=True):
885
- deviceList = []
886
- foundDevices = "1"
887
- foundDevices2 = "2" # this is used to check if new modules are being discovered or if all have been found.
888
- scanWait = 2 # The number of seconds waited between the two scans.
889
- if self.pythonVersion == '3':
890
- if scan:
891
- devString = self.sendAndReceiveText(self.sock, '$scan').decode
892
- time.sleep(scanWait)
893
- while foundDevices not in foundDevices2:
894
- foundDevices = self.sendAndReceiveText(self.sock, '$list').decode()
895
- time.sleep(scanWait)
896
- foundDevices2 = self.sendAndReceiveText(self.sock, '$list').decode()
897
- else:
898
- foundDevices = self.sendAndReceiveText(self.sock, '$list').decode()
899
-
900
- else:
901
- if scan:
902
- devString = self.sendAndReceiveText(self.sock, '$scan')
903
- time.sleep(scanWait)
904
- while foundDevices not in foundDevices2:
905
- foundDevices = self.sendAndReceiveText(self.sock, '$list')
906
- time.sleep(scanWait)
907
- foundDevices2 = self.sendAndReceiveText(self.sock, '$list')
908
- else:
909
- foundDevices = self.sendAndReceiveText(self.sock, '$list')
910
-
911
- if not "no devices found" in foundDevices.lower():
912
- foundDevices = foundDevices.replace('>', '')
913
- #foundDevices = foundDevices.replace(r'\d\) ', '')
914
- # printText('"' + devString + '"')
915
- foundDevices = foundDevices.split('\r\n')
916
- #Can't stream over REST! Removing all REST connections.
917
- tempList= list()
918
- for item in foundDevices:
919
- if item is None or "rest" in item.lower() or item == "":
920
- pass
921
- else:
922
- tempList.append(item.split(")")[1].strip())
923
- foundDevices = tempList
924
-
925
- #If favourite only is True then only show one connection type for each module connected.
926
- #First order the devices in preference type and then pick the first con type found for each module.
927
- if (favouriteOnly):
928
- foundDevices = self.sortFavourite(foundDevices)
929
- else:
930
- foundDevices = ["***No Devices Found***"]
931
-
932
- return foundDevices
933
-
934
- def sortFavourite(self, foundDevices):
935
- index = 0
936
- sortedFoundDevices = []
937
- conPref = ["USB", "TCP", "SERIAL", "REST", "TELNET"]
938
- while len(sortedFoundDevices) != len(foundDevices):
939
- for device in foundDevices:
940
- if conPref[index] in device.upper():
941
- sortedFoundDevices.append(device)
942
- index += 1
943
- foundDevices = sortedFoundDevices
944
- # new dictionary only containing one favourite connection to each device.
945
- favConFoundDevices = []
946
- index = 0
947
- for device in sortedFoundDevices:
948
- if (favConFoundDevices == [] or not device.split("::")[1] in str(favConFoundDevices)):
949
- favConFoundDevices.append(device)
950
- foundDevices = favConFoundDevices
951
- return foundDevices
952
-
953
- # Query stream status for a device attached to backend
954
- # The objects connection needs to be opened (connect()) before this is used
955
- def streamRunningStatus(self, device, sock=None):
956
- if sock == None:
957
- sock = self.sock
958
- index = 0 # index of relevant line in split string
959
- if self.pythonVersion == '3':
960
- streamStatus = self.sendAndReceiveText(sock, 'stream?', device).decode()
961
- else:
962
- streamStatus = self.sendAndReceiveText(sock, 'stream?', device)
963
- streamStatus = streamStatus.split('\r\n')
964
- streamStatus[index] = re.sub(r':', '', streamStatus[index]) #remove :
965
- return streamStatus[index]
966
-
967
- # Query stream buffer status for a device attached to backend
968
- # The objects connection needs to be opened (connect()) before this is used
969
- def streamBufferStatus(self, device, sock=None):
970
- if sock == None:
971
- sock = self.sock
972
- index = 1 # index of relevant line in split string
973
- if self.pythonVersion == '3':
974
- streamStatus = self.sendAndReceiveText(sock, 'stream?', device).decode()
975
- else:
976
- streamStatus = self.sendAndReceiveText(sock, 'stream?', device)
977
- streamStatus = streamStatus.split('\r\n')
978
- streamStatus[index] = re.sub(r'^Stripes Buffered: ', '', streamStatus[index])
979
- return streamStatus[index]
980
-
981
- # TODO: MD - This function should be replaced with a more generic method of accessing the header
982
- # The return of a string with concatenated value and units should be replaced with something easier to parse
983
- #
984
- # Get the averaging used on the last/current stream
985
- # The objects connection needs to be opened (connect()) before this is used
986
- def streamHeaderAverage(self, device, sock=None):
987
- try:
988
- if sock == None:
989
- sock = self.sock
990
- index = 2 # index of relevant line in split string
991
- if self.pythonVersion == '3':
992
- streamStatus = self.sendAndReceiveText(sock, sentText='stream text header', device=device).decode()
993
- else:
994
- streamStatus = self.sendAndReceiveText(sock, sentText='stream text header', device=device)
995
-
996
- self.qps_stream_header = streamStatus
997
-
998
- # Check for the header format. If XML, process here
999
- if (self.isXmlHeader(streamStatus)):
1000
- # Get the basic averaging rate (V3 header)
1001
- xml_root = self.getStreamXmlHeader(device=device, sock=sock)
1002
-
1003
- # For QPS streaming, stream header v3 command has already been issued before this
1004
- self.module_xml_header = xml_root
1005
-
1006
- # Return the time based averaging string
1007
- device_period = xml_root.find('.//devicePeriod')
1008
- if device_period == None:
1009
- device_period = xml_root.find('.//devicePerioduS')
1010
- if device_period == None:
1011
- device_period = xml_root.find('.//mainPeriod')
1012
- averageStr = device_period.text
1013
- return averageStr
1014
- # For legacy text headers, process here
1015
- else:
1016
- streamStatus = streamStatus.split('\r\n')
1017
- if('Header Not Available' in streamStatus[0]):
1018
- dummy = streamStatus[0] + '. Check stream has been run on device.'
1019
- return dummy
1020
- streamStatus[index] = re.sub(r'^Average: ', '', streamStatus[index])
1021
- avg = streamStatus[index]
1022
- avg = 2 ** int(avg)
1023
- return '{}'.format(avg)
1024
- except Exception as e:
1025
- logging.error(device + ' Unable to get stream average.' + self.host + ':' + str(self.port))
1026
- raise e
1027
-
1028
- # Get the version of the stream and convert to string for the specified device
1029
- # The objects connection needs to be opened (connect()) before this is used
1030
- def streamHeaderVersion(self, device, sock=None):
1031
- try:
1032
- if sock == None:
1033
- sock = self.sock
1034
- index = 0 # index of relevant line in split string
1035
- if self.pythonVersion == '3':
1036
- streamStatus = self.sendAndReceiveText(sock,'stream text header', device).decode()
1037
- else:
1038
- streamStatus = self.sendAndReceiveText(sock,'stream text header', device)
1039
- streamStatus = streamStatus.split('\r\n')
1040
- if 'Header Not Available' in streamStatus[0]:
1041
- str = streamStatus[0] + '. Check stream has been ran on device.'
1042
- logging.error(str)
1043
- return str
1044
- version = re.sub(r'^Version: ', '', streamStatus[index])
1045
- if version == '3':
1046
- version = 'Original PPM'
1047
- elif version == '4':
1048
- version = 'XLC PPM'
1049
- elif version == '5':
1050
- version = 'HD PPM'
1051
- else:
1052
- version = 'Unknown stream version'
1053
- return version
1054
- except Exception as e:
1055
- logging.error(device + ' Unable to get stream version.' + self.host + ':' + str(self.port))
1056
- raise e
1057
-
1058
- # Get a header string giving which measurements are returned in the string for the specified device
1059
- # The objects connection needs to be opened (connect()) before this is used
1060
- def streamHeaderFormat(self, device, sock=None):
1061
- try:
1062
- if sock == None:
1063
- sock = self.sock
1064
- index = 1 # index of relevant line in split string STREAM MODE HEADER [?|V1,V2,V3]
1065
- if self.pythonVersion == '3':
1066
- streamStatus = self.sendAndReceiveText(sock,'stream text header', device).decode()
1067
- else:
1068
- streamStatus = self.sendAndReceiveText(sock,'stream text header', device)
1069
- # Check if this is a new XML form header
1070
- if (self.isXmlHeader (streamStatus)):
1071
- # Get the basic averaging rate (V3 header)
1072
- xml_root = self.getStreamXmlHeader (device=device, sock=sock)
1073
- # Return the time based averaging string
1074
- device_period = xml_root.find('.//devicePeriod')
1075
- time_unit = 'uS'
1076
- if device_period == None:
1077
- device_period = xml_root.find('.//devicePerioduS')
1078
- if device_period == None:
1079
- device_period = xml_root.find('.//mainPeriod')
1080
- if ('ns' in device_period.text):
1081
- time_unit = 'nS'
1082
- averageStr = device_period.text
1083
-
1084
- # Time column always first
1085
- formatHeader = 'Time ' + time_unit + ','
1086
- # Find the channels section of each group and iterate through it to add the channel columns
1087
- for group in xml_root.iter():
1088
- if (group.tag == "channels"):
1089
- for chan in group:
1090
- # Avoid children that are not named channels
1091
- if (chan.find('.//name') is not None):
1092
- nameStr = chan.find('.//name').text
1093
- unitStr = chan.find('.//units').text
1094
- formatHeader = formatHeader + nameStr + " " + unitStr + ","
1095
- return formatHeader
1096
- # Handle legacy text headers here
1097
- else:
1098
- streamStatus = streamStatus.split('\r\n')
1099
- if 'Header Not Available' in streamStatus[0]:
1100
- str = streamStatus[0] + '. Check stream has been ran on device.'
1101
- logging.error(str)
1102
- return str
1103
- if self.pythonVersion == '3':
1104
- outputMode = self.sendAndReceiveText(sock,'Config Output Mode?', device).decode()
1105
- powerMode = self.sendAndReceiveText(sock,'stream mode power?', device).decode()
1106
- else:
1107
- outputMode = self.sendAndReceiveText(sock,'Config Output Mode?', device)
1108
- powerMode = self.sendAndReceiveText(sock,'stream mode power?', device)
1109
- format = int(re.sub(r'^Format: ', '', streamStatus[index]))
1110
- b0 = 1 #12V_I
1111
- b1 = 1 << 1 #12V_V
1112
- b2 = 1 << 2 #5V_I
1113
- b3 = 1 << 3 #5V_V
1114
- formatHeader = 'StripeNum, Trig, '
1115
- if format & b3:
1116
- if ('3V3' in outputMode):
1117
- formatHeader = formatHeader + '3V3_V,'
1118
- else:
1119
- formatHeader = formatHeader + '5V_V,'
1120
- if format & b2:
1121
- if ('3V3' in outputMode):
1122
- formatHeader = formatHeader + ' 3V3_I,'
1123
- else:
1124
- formatHeader = formatHeader + ' 5V_I,'
1125
-
1126
- if format & b1:
1127
- formatHeader = formatHeader + ' 12V_V,'
1128
- if format & b0:
1129
- formatHeader = formatHeader + ' 12V_I'
1130
- if 'Enabled' in powerMode:
1131
- if ('3V3' in outputMode):
1132
- formatHeader = formatHeader + ' 3V3_P'
1133
- else:
1134
- formatHeader = formatHeader + ' 5V_P'
1135
- if ((format & b1) or (format & b0)):
1136
- formatHeader = formatHeader + ' 12V_P'
1137
- return formatHeader
1138
- except Exception as e:
1139
- logging.error(device + ' Unable to get stream format.' + self.host + ':' + '{}'.format(self.port))
1140
- raise e
1141
-
1142
- # Get stripes out of the backends stream buffer for the specified device using text commands
1143
- # The objects connection needs to be opened (connect()) before this is used
1144
- def streamGetStripesText(self, sock, device, numStripes=4096, skipStatusCheck=False):
1145
-
1146
- bufferStatus = False
1147
- # Allows the status check to be skipped when emptying the buffer after streaming has stopped (saving time)
1148
- if (skipStatusCheck == False):
1149
- if self.pythonVersion == '3':
1150
- streamStatus = self.sendAndReceiveText(sock, 'stream?', device).decode()
1151
- else:
1152
- streamStatus = self.sendAndReceiveText(sock, 'stream?', device)
1153
- if ('Overrun' in streamStatus) or ('8388608 of 8388608' in streamStatus):
1154
- bufferStatus = True
1155
- stripes = self.sendAndReceiveText(sock, 'stream text all', device, readUntilCursor=True)
1156
- # time.sleep(0.001)
1157
- if stripes[-1:] != self.cursor:
1158
- return "Error no cursor returned."
1159
- else:
1160
- if self.pythonVersion == '3':
1161
- endOfFile = 'eof\r\n>'
1162
- genEndOfFile = endOfFile.encode()
1163
- else:
1164
- genEndOfFile = 'eof\r\n>'
1165
- if stripes[-6:] == genEndOfFile:
1166
- removeChar = -6
1167
- else:
1168
- removeChar = -1
1169
-
1170
- # stripes = stripes.split('\r\n')
1171
- # stripes = filter(None, stripes) #remove empty sting elements
1172
- #printText(stripes)
1173
- return bufferStatus, removeChar, stripes
1174
-
1175
- def avgStringFromPwr(self, avgPwrTwo):
1176
- if(avgPwrTwo==0):
1177
- return '0'
1178
- elif(avgPwrTwo==1):
1179
- return '2'
1180
- elif(avgPwrTwo > 1 and avgPwrTwo < 10 ):
1181
- avg = 2 ** int(avgPwrTwo)
1182
- return '{}'.format(avg)
1183
- elif(avgPwrTwo==10):
1184
- return '1k'
1185
- elif(avgPwrTwo==11):
1186
- return '2k'
1187
- elif(avgPwrTwo==12):
1188
- return '4k'
1189
- elif(avgPwrTwo==13):
1190
- return '8k'
1191
- elif(avgPwrTwo==14):
1192
- return '16k'
1193
- elif(avgPwrTwo==15):
1194
- return '32k'
1195
- else:
1196
- return 'Invalid Average Value'
1197
-
1198
- # TODO: MD Thinks this implements software averaging, is unused and now performed in QIS
1199
- # Works out average values of timescales longer than max device averaging
1200
- def averageStripes(self, leftover, streamAverage, newStripes, f, remainingStripes = []):
1201
- newString = str(newStripes)
1202
- newList = []
1203
- if remainingStripes == []:
1204
- newList = newString.split('\r\n')
1205
- else:
1206
- newList = remainingStripes
1207
- newList.extend(newString.split('\r\n'))
1208
- numElements = newList[0].count(' ') + 1
1209
- streamTotalAverage = leftover + streamAverage
1210
- splitList = [] * numElements
1211
- if len(newList) < streamTotalAverage:
1212
- remainingStripes = newList[:-1]
1213
- return leftover, remainingStripes
1214
- runningAverage = [0] * (len(newList[0].split(' ')) - 2)
1215
- j = 0
1216
- z = 1
1217
- for i in newList[:-1]:
1218
- splitList = i.split(' ')
1219
- splitNumbers = [int(x) for x in splitList[2:]]
1220
- runningAverage = [sum(x) for x in zip(runningAverage, splitNumbers)]
1221
- if z == math.floor(streamTotalAverage):
1222
- finalAverage = splitList[0:2] + [str(round(x / streamAverage)) for x in runningAverage]
1223
- for counter in xrange(len(finalAverage)-1):
1224
- finalAverage[counter] = finalAverage[counter] + ' '
1225
- if self.pythonVersion == '3':
1226
- finalAverage = finalAverage.encode
1227
- for x in finalAverage:
1228
- f.write(x)
1229
- f.write('\r\n')
1230
- streamTotalAverage += streamAverage
1231
- j += 1
1232
- z += 1
1233
- remainingStripes = newList[int(math.floor(j * streamAverage + leftover)):-1]
1234
- leftover = (streamTotalAverage - streamAverage) % 1
1235
- return leftover, remainingStripes
1236
-
1237
- def deviceMulti(self, device):
1238
- if (device in self.deviceList):
1239
- return self.deviceList.index(device)
1240
- else:
1241
- self.listSemaphore.acquire()
1242
- self.deviceList.append(device)
1243
- self.stopFlagList.append(True)
1244
- self.listSemaphore.release()
1245
- return self.deviceList.index(device)
1246
-
1247
- def deviceDictSetup(self, module):
1248
- if module in self.deviceDict.keys():
1249
- return
1250
- elif module == 'QIS':
1251
- self.dictSemaphore.acquire()
1252
- self.deviceDict[module] = [False, 'Disconnected', "No attempt to connect to QIS yet"]
1253
- self.dictSemaphore.release()
1254
- else:
1255
- self.dictSemaphore.acquire()
1256
- self.deviceDict[module] = [False, 'Stopped', "User hasn't started stream"]
1257
- self.dictSemaphore.release()
1258
-
1259
- def streamInterrupt(self):
1260
- for key in self.deviceDict.keys():
1261
- if self.deviceDict[key][0]:
1262
- return True
1263
- return False
1264
-
1265
- def interruptList(self):
1266
- streamIssueList = []
1267
- for key in self.deviceDict.keys():
1268
- if self.deviceDict[key][0]:
1269
- streamIssue = [key]
1270
- streamIssue.append(self.deviceDict[key][1])
1271
- streamIssue.append(self.deviceDict[key][2])
1272
- streamIssueList.append(streamIssue)
1273
- return streamIssueList
1274
-
1275
- def waitStop(self):
1276
- running = 1
1277
- while running != 0:
1278
- threadNameList = []
1279
- for t1 in threading.enumerate():
1280
- threadNameList.append(t1.name)
1281
- running = 0
1282
- for module in self.deviceList:
1283
- if (module in threadNameList):
1284
- running += 1
1285
- time.sleep(0.5)
1286
- time.sleep(1)
1287
-
1288
- def convertStreamAverage (self, streamAveraging):
1289
- returnValue = 32000;
1290
- if ("k" in streamAveraging):
1291
- returnValue = streamAveraging.replace("k", "000")
1292
- else:
1293
- returnValue = streamAveraging
1294
-
1295
- return returnValue
1296
-
1297
- # Pass in a stream header and we check if it is XML or legacy format
1298
- def isXmlHeader (self, headerText):
1299
- if('?xml version=' not in headerText):
1300
- return False;
1301
- else:
1302
- return True
1303
-
1304
- # Internal function. Gets the stream header and parses it into useful information
1305
- def getStreamXmlHeader (self, device, sock=None):
1306
- try:
1307
- if sock == None:
1308
- sock = self.sock
1309
-
1310
- # Get the raw data
1311
- if self.pythonVersion == '3':
1312
- headerData = self.sendAndReceiveText(sock, sentText='stream text header', device=device).decode()
1313
- else:
1314
- headerData = self.sendAndReceiveText(sock, sentText='stream text header', device=device)
1315
-
1316
- # The XML can contain the cursor on the end! Trap and remove it here TODO: Needs fixed in the command layer above
1317
- if ('\r\n>' in headerData):
1318
- headerData = headerData[:-1]
1319
-
1320
- # Check for no header (no stream started)
1321
- if('Header Not Available' in headerData):
1322
- logging.error(device + ' Stream header not available.' + self.host + ':' + str(self.port))
1323
- return None;
1324
-
1325
- # Check for XML format
1326
- if('?xml version=' not in headerData):
1327
- logging.error(device + ' Header not in XML form.' + self.host + ':' + str(self.port))
1328
- return None;
1329
-
1330
- # Parse XML into structured format
1331
- xml_root = ET.fromstring(headerData)
1332
-
1333
- # Check header format is supported by quarchpy
1334
- versionStr = xml_root.find('.//version').text
1335
- if ('V3' not in versionStr):
1336
- logging.error(device + ' Stream header version not compatible: ' + xml_root['version'].text + '.' + self.host + ':' + str(self.port))
1337
- raise Exception ("Stream header version not supported");
1338
-
1339
- # Return the XML structure for the code to use
1340
- return xml_root
1341
-
1342
- except Exception as e:
1343
- logging.error(device + ' Exception while parsing stream header XML.' + self.host + ':' + str(self.port))
1344
- raise e
1345
-
1346
- def create_dir_structure(self, module, directory=None):
1347
- """
1348
- Creates the QPS directory structure and (empty) files to be written to
1349
-
1350
- I've put a bunch of try-except just to be sure the directory is correctly created.
1351
- ( There's probably a better way of doing this than this )
1352
-
1353
- :param: module: String - Module string
1354
- :param: directory: String - Name of directory for QPS stream (defaults to default recording location if invalid)
1355
- :return: success: Boolean - Was the file structure created successfully?
1356
- """
1357
-
1358
- directory = self.create_qps_directory(directory)
1359
-
1360
- digital_count = 0
1361
- non_dig_counter = 0
1362
- self.streamGroups = StreamGroups()
1363
- for index, i in enumerate(self.module_xml_header.findall('.//channels')):
1364
- self.streamGroups.add_group(index)
1365
- for item in i.findall('.//channel'):
1366
- self.streamGroups.groups[index].add_channel(item.find(".//name"), item.find(".//group"), item.find(".//dataPosition"))
1367
- if item.find(".//group").text == "Digital":
1368
- digital_count += 1
1369
- self.has_digitals = True
1370
- else:
1371
- non_dig_counter += 1
1372
-
1373
- # Inner folders for analogue and digital signals streaming
1374
- in_folder_analogue = "data000"
1375
- try:
1376
- inner_path_analogues = os.path.join(directory, in_folder_analogue)
1377
- os.mkdir(inner_path_analogues)
1378
- except:
1379
- logging.warning(f"Failed to make inner directory for analogue signals {inner_path_analogues}")
1380
- return False
1381
-
1382
- in_folder_digitals = "data101"
1383
- if self.has_digitals:
1384
- try:
1385
- inner_path_digitals = os.path.join(directory, in_folder_digitals)
1386
- os.mkdir(inner_path_digitals)
1387
- except:
1388
- logging.warning(f"Failed to make inner directory for digital signals {inner_path_digitals}")
1389
- return False
1390
-
1391
- logging.debug(f"Steaming to : {self.qps_record_dir_path}")
1392
-
1393
- logging.debug("Creating qps data files")
1394
- try:
1395
- for i in range(non_dig_counter):
1396
- file_name = f"data000_00{i}_000000000"
1397
- f = open(os.path.join(inner_path_analogues, file_name), "w")
1398
- f.close()
1399
- for i in range(digital_count):
1400
- x = i
1401
- while len(str(x)) < 3:
1402
- x = "0" + str(x)
1403
- file_name = f"data101_{x}_000000000"
1404
- f = open(os.path.join(inner_path_digitals, file_name), "w")
1405
- f.close()
1406
- except:
1407
- logging.warning(f"failed to create qps data files for analogue signals")
1408
- return False
1409
-
1410
- logging.debug("Finished creating qps data files")
1411
-
1412
- logging.debug("Creating qps upper level files")
1413
- try:
1414
- file_names = ["annotations.xml", "notes.txt", "triggers.txt"]
1415
- for file_nome in file_names:
1416
- f = open(os.path.join(self.qps_record_dir_path, file_nome), "w")
1417
- f.close()
1418
- except Exception as err:
1419
- logging.warning(f"failed to create qps upper level files, {err}")
1420
- return False
1421
-
1422
- try:
1423
- # Adding data000.idx separate as it's written in bytes not normal text
1424
- f = open(os.path.join(self.qps_record_dir_path, "data000.idx"), "wb")
1425
- f.close()
1426
- if digital_count > 0:
1427
- f = open(os.path.join(self.qps_record_dir_path, "data101.idx"), "wb")
1428
- f.close()
1429
- except Exception as err:
1430
- logging.warning(f"failed to create data000.idx file, {err}")
1431
- return False
1432
-
1433
- logging.debug("Finished creating QPS dir structure")
1434
-
1435
- return True
1436
-
1437
- def create_qps_directory(self, directory):
1438
- folder_name = None
1439
- # Checking if there was a directory passed; and if it's a valid directory
1440
- if not directory:
1441
- directory = os.path.join(str(Path.home()), "AppData", "Local", "Quarch", "QPS", "Recordings")
1442
- logging.debug("No directory specified")
1443
- elif not os.path.isdir(directory):
1444
- new_dir = os.path.join(str(Path.home()), "AppData", "Local", "Quarch", "QPS", "Recordings")
1445
- logging.warning(f"{directory} was not a valid directory, streaming to default location: \n{new_dir}")
1446
- directory = new_dir
1447
- else:
1448
- # Split the directory into a path of folders
1449
- folder_name = str(directory).split(os.sep)
1450
- # last folder name is the name we want
1451
- folder_name = folder_name[-1]
1452
- # Make it known to the entire class that the path we're streaming to is the one sent across by the user
1453
- self.qps_record_dir_path = directory
1454
-
1455
- # If no folder name for the stream was passed, then default to 'quarchpy_recording' and a timestamp
1456
- if not folder_name:
1457
- folder_name = "quarchpy_recording"
1458
- folder_name = f"{folder_name}-{time.time()}"
1459
- path = os.path.join(directory, self.qps_stream_folder_name)
1460
- os.mkdir(path)
1461
- self.qps_record_dir_path = path
1462
-
1463
- self.qps_stream_folder_name = folder_name
1464
-
1465
- return directory
1466
-
1467
- def create_index_file(self):
1468
- """
1469
- Create the necessary index file for QPS data000.idx
1470
-
1471
- For future revisions, this should be updated if there are file limits on each data file
1472
- Current implementation assumes only 1 of each data file are made.
1473
-
1474
- No Return./
1475
- """
1476
-
1477
- stream_header_size = -1
1478
-
1479
- my_byte_array = []
1480
-
1481
- # tree = ET.ElementTree(ET.fromstring(self.module_xml_header[:-1]))
1482
- tree = self.module_xml_header
1483
-
1484
- return_b_array = []
1485
- outBuffer = []
1486
- x = 20
1487
- stream_header_size = 20
1488
-
1489
- temp_dict = {"channels": 0}
1490
-
1491
- return_b_array, stream_header_size = self.add_header_to_byte_array(return_b_array, stream_header_size,
1492
- temp_dict, tree, is_digital=False)
1493
-
1494
- self.add_header_to_buffer(outBuffer, return_b_array, stream_header_size, temp_dict)
1495
-
1496
- # Attempting to read the size of the first file in data files
1497
- file = os.path.join(self.qps_record_dir_path, "data000", "data000_000_000000000")
1498
- data = None
1499
- with open(file, "rb") as f:
1500
- data = f.read() # if you only wanted to read 512 bytes, do .read(512)
1501
-
1502
- if not data:
1503
- raise "No data written to file"
1504
-
1505
- num_records = len(data) / 8
1506
- logging.debug(f"num_record = {num_records}")
1507
- return_b_array.append(int(num_records).to_bytes(4, byteorder='big'))
1508
-
1509
- start_number = 0
1510
- logging.debug(f"start_record = {start_number}")
1511
- return_b_array.append(start_number.to_bytes(8, byteorder='big'))
1512
-
1513
- num_records = num_records - 1
1514
- logging.debug(f"last_Record_number = {num_records}")
1515
- return_b_array.append(int(num_records).to_bytes(8, byteorder='big'))
1516
-
1517
- # Add names of every file in data000 dir here.
1518
- files = os.listdir(os.path.join(self.qps_record_dir_path, "data000"))
1519
- for file3 in files:
1520
- # print(file)
1521
- item = strToBb(file3, False)
1522
- # print(item)
1523
- while len(item) < 32:
1524
- item.append("\x00")
1525
- # print(item)
1526
- return_b_array.append(item)
1527
-
1528
- with open(os.path.join(self.qps_record_dir_path, "data000.idx"), "ab") as f:
1529
- for item in outBuffer:
1530
- # print(item)
1531
- # print(type(item))
1532
- f.write(bytes(item))
1533
- # f.write(outBuffer)
1534
-
1535
- with open(os.path.join(self.qps_record_dir_path, "data000.idx"), "ab") as f:
1536
- self.write_b_array_to_idx_file(f, return_b_array)
1537
-
1538
- def create_index_file_digitals(self):
1539
- """
1540
- Create the necessary index file for QPS data101.idx
1541
-
1542
- For future revisions, this should be updated if there are file limits on each data file
1543
- Current implementation assumes only 1 of each data file are made.
1544
-
1545
- No Return.
1546
- """
1547
-
1548
- stream_header_size = -1
1549
- my_byte_array = []
1550
- tree = self.module_xml_header
1551
- return_b_array = []
1552
- outBuffer = []
1553
- temp_dict = {}
1554
-
1555
- return_b_array, stream_header_size = self.add_header_to_byte_array(return_b_array, stream_header_size,
1556
- temp_dict, tree, is_digital=True)
1557
-
1558
- self.add_header_to_buffer(outBuffer, return_b_array, stream_header_size, temp_dict)
1559
-
1560
- # Attempting to read the size of the first file in data files
1561
- file = os.path.join(self.qps_record_dir_path, "data101", "data101_000_000000000")
1562
- data = None
1563
- with open(file, "rb") as f:
1564
- data = f.read() # if you only wanted to read 512 bytes, do .read(512)
1565
-
1566
- if not data:
1567
- raise "No data written to file"
1568
-
1569
- num_records = len(data) / 8
1570
- logging.debug(f"num_record = {num_records}")
1571
- return_b_array.append(int(num_records).to_bytes(4, byteorder='big'))
1572
-
1573
- start_number = 0
1574
- logging.debug(f"start_record = {start_number}")
1575
- return_b_array.append(start_number.to_bytes(8, byteorder='big'))
1576
-
1577
- num_records = num_records - 1
1578
- logging.debug(f"last_Record_number = {num_records}")
1579
- return_b_array.append(int(num_records).to_bytes(8, byteorder='big'))
1580
-
1581
- # Add names of every file in data000 dir here.
1582
- files = os.listdir(os.path.join(self.qps_record_dir_path, "data101"))
1583
- for file3 in files:
1584
- # print(file)
1585
- item = strToBb(file3, False)
1586
- # print(item)
1587
- while len(item) < 32:
1588
- item.append("\x00")
1589
- # print(item)
1590
- return_b_array.append(item)
1591
-
1592
- with open(os.path.join(self.qps_record_dir_path, "data101.idx"), "ab") as f:
1593
- for item in outBuffer:
1594
- f.write(bytes(item))
1595
-
1596
- with open(os.path.join(self.qps_record_dir_path, "data101.idx"), "ab") as f:
1597
- self.write_b_array_to_idx_file(f, return_b_array)
1598
-
1599
- def add_header_to_byte_array(self, return_b_array, stream_header_size, temp_dict, tree, is_digital=False):
1600
- for element in tree:
1601
- if "legacyVersion" in element.tag:
1602
- intItem = element.text
1603
- temp_dict[element.tag] = intItem
1604
- # my_byte_array.append(int.to_bytes(intItem, 'big'))
1605
- if "legacyAverage" in element.tag:
1606
- intItem = element.text
1607
- temp_dict[element.tag] = intItem
1608
- # my_byte_array.append(int.to_bytes(intItem, 'big'))
1609
- if "legacyFormat" in element.tag:
1610
- intItem = element.text
1611
- temp_dict[element.tag] = intItem
1612
- # my_byte_array.append(int.to_bytes(intItem, 'big'))
1613
- if "mainPeriod" in element.tag:
1614
- intItem = element.text
1615
- intItem = intItem[:-2]
1616
- temp_dict[element.tag] = intItem
1617
- if "channels" in element.tag:
1618
- counter = 0
1619
- for child in element:
1620
- for child2 in child:
1621
- if "group" in child2.tag:
1622
- if is_digital:
1623
- if str(child2.text).lower() == "digital":
1624
- counter += 1
1625
- else:
1626
- if str(child2.text).lower() != "digital":
1627
- counter += 1
1628
-
1629
- temp_dict[element.tag] = counter
1630
-
1631
- return_b_array = []
1632
-
1633
- stream_header_size = 20
1634
-
1635
- # Cycle through all the channels.
1636
- for child in element:
1637
-
1638
- if child.tag == "groupId":
1639
- continue
1640
-
1641
- if is_digital:
1642
- # skip channel if we're only looking for digitals
1643
- if not str(child.find(".//group").text).lower() == "digital":
1644
- continue
1645
- else:
1646
- # skip if we're looking for analogues
1647
- if str(child.find(".//group").text).lower() == "digital":
1648
- continue
1649
-
1650
- # my_byte_array.append(int.to_bytes(5, 'big'))
1651
- return_b_array.append(int(5).to_bytes(4, byteorder='big'))
1652
- stream_header_size += 4
1653
- name = None
1654
-
1655
- for child2 in child:
1656
-
1657
- if "group" in child2.tag:
1658
- my_byte_array = strToBb(str(child2.text))
1659
- return_b_array.append(my_byte_array)
1660
- # QPS index file requires name tag come after group tag.
1661
- return_b_array.append(name)
1662
- stream_header_size += len(my_byte_array)
1663
-
1664
- if "name" in child2.tag:
1665
- my_byte_array = strToBb(str(child2.text))
1666
- name = my_byte_array
1667
- stream_header_size += len(my_byte_array)
1668
-
1669
- if "units" in child2.tag:
1670
- my_byte_array = strToBb(str(child2.text))
1671
- return_b_array.append(my_byte_array)
1672
- stream_header_size += len(my_byte_array)
1673
-
1674
- """
1675
- # Unclear if the only thing here is TRUE
1676
- bb = strToBB( Boolean.toString( cdr.isUsePrefixStr() ));
1677
- bbList.add(bb);
1678
- retVal += bb.capacity();
1679
- """
1680
- my_byte_array = strToBb(str("true"))
1681
- return_b_array.append(my_byte_array)
1682
- stream_header_size += len(my_byte_array)
1683
-
1684
- if "maxTValue" in child2.tag:
1685
- my_byte_array = strToBb(str(child2.text))
1686
- return_b_array.append(my_byte_array)
1687
- stream_header_size += len(my_byte_array)
1688
-
1689
- return return_b_array, stream_header_size
1690
-
1691
- def add_header_to_buffer(self, outBuffer, return_b_array, stream_header_size, temp_dict):
1692
- number = 2
1693
- outBuffer.append(number.to_bytes(4, byteorder='big'))
1694
- logging.debug(f"indexVersion : {number}")
1695
-
1696
- number = 1 if self.has_digitals else 0
1697
- outBuffer.append(number.to_bytes(4, byteorder='big'))
1698
- logging.debug(f"value0 : {number}")
1699
- number = stream_header_size
1700
- outBuffer.append(number.to_bytes(4, byteorder='big'))
1701
- logging.debug(f"header_size : {number}")
1702
- logging.debug(f"legacyVersion : {temp_dict['legacyVersion']}")
1703
- outBuffer.append(int(temp_dict["legacyVersion"]).to_bytes(4, byteorder='big'))
1704
- logging.debug(f"legacyAverage : {temp_dict['legacyAverage']}")
1705
- outBuffer.append(int(temp_dict["legacyAverage"]).to_bytes(4, byteorder='big'))
1706
- logging.debug(f"legacyFormat : {temp_dict['legacyFormat']}")
1707
- outBuffer.append(int(temp_dict["legacyFormat"]).to_bytes(4, byteorder='big'))
1708
- logging.debug(f"mainPeriod : {temp_dict['mainPeriod']}")
1709
- outBuffer.append(int(temp_dict["mainPeriod"]).to_bytes(4, byteorder='big'))
1710
- logging.debug(f"channels : {temp_dict['channels']}")
1711
- outBuffer.append(int(temp_dict["channels"]).to_bytes(4, byteorder='big'))
1712
- return_b_array.append(int(self.qps_record_start_time).to_bytes(8, byteorder='big'))
1713
- index_record_state = True
1714
- logging.debug(int(1))
1715
- return_b_array.append(int(1).to_bytes(1, byteorder='big'))
1716
- record_type = 1
1717
- logging.debug(f"record type : {int(index_record_state)}")
1718
- return_b_array.append(int(record_type).to_bytes(1, byteorder='big'))
1719
-
1720
- def write_b_array_to_idx_file(self, f, return_b_array):
1721
- # print(return_b_array)
1722
- for item in return_b_array:
1723
- # print(item)
1724
- if isinstance(item, int):
1725
- # 'f.write(str(item).encode())
1726
- # print(item)
1727
- f.write(bytes([item]))
1728
- continue
1729
- if isinstance(item, bytes):
1730
- # print(item)
1731
- f.write(bytes(item))
1732
- continue
1733
- if isinstance(item, list):
1734
- for character in item:
1735
- if isinstance(character, int):
1736
- f.write(bytes([character]))
1737
- continue
1738
- elif isinstance(item, bytes):
1739
- f.write(item)
1740
- continue
1741
- else:
1742
- f.write(str(character).encode())
1743
- continue
1744
-
1745
- def create_qps_file(self, module):
1746
- """
1747
- Creates the end QPS file that is used to open QPS
1748
-
1749
- :param module: Module QTL number that was used for the stream
1750
- :return:
1751
- """
1752
-
1753
- with open(os.path.join(self.qps_record_dir_path, self.qps_stream_folder_name + ".qps"), "w") as f:
1754
- x = datetime.datetime.fromtimestamp(self.qps_record_start_time / 1000.0)
1755
- x = str(x).split(".")
1756
- x = x[0]
1757
- x = x.replace("-", " ")
1758
- f.write(f"Started: {x}\n")
1759
- f.write(f"Device: {module}\n")
1760
- f.write("Fixture: \n")
1761
-
1762
- x = datetime.datetime.now()
1763
- x = str(x).split(".")
1764
- x = x[0]
1765
- x = x.replace("-", " ")
1766
- f.write(f"Saved: {x}\n")
1767
-
1768
-
1769
- def strToBb(string_in, add_length=True):
1770
- length = len(str(string_in))
1771
- b_array = []
1772
- if add_length:
1773
- b_array.append(length)
1774
- for character in str(string_in):
1775
- b_array.append(character)
1776
-
1777
- return b_array
1
+ import gzip
2
+ import re
3
+ import socket
4
+ import threading
5
+ import xml.etree.ElementTree as ET
6
+ from io import StringIO
7
+ from typing import Tuple
8
+
9
+ import select
10
+ #from .connection_specific.StreamChannels import StreamGroups
11
+ from .StreamChannels import StreamGroups
12
+
13
+ from quarchpy.user_interface import *
14
+
15
+
16
+ # QisInterface provides a way of connecting to a Quarch backend running at the specified ip address and port, defaults to localhost and 9722
17
+ class QisInterface:
18
+ def __init__(self, host='127.0.0.1', port=9722, connectionMessage=True):
19
+ self.host = host
20
+ self.port = port
21
+ self.maxRxBytes = 4096
22
+ self.sock = None
23
+ self.StreamRunSentSemaphore = threading.Semaphore()
24
+ self.sockSemaphore = threading.Semaphore()
25
+ self.stopFlagList = []
26
+ self.listSemaphore = threading.Semaphore()
27
+ self.deviceList = []
28
+ self.deviceDict = {}
29
+ self.dictSemaphore = threading.Semaphore()
30
+ self.connect(connection_message = connectionMessage)
31
+ self.stripesEvent = threading.Event()
32
+
33
+ self.qps_stream_header = None
34
+ self.qps_record_dir_path = None
35
+ self.qps_record_start_time = None
36
+ self.qps_stream_folder_name = None
37
+
38
+ self.module_xml_header = None
39
+ self.streamGroups = None
40
+ self.has_digitals = False
41
+ self.is_multirate = False
42
+
43
+ self.streamSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
44
+ self.streamSock.settimeout(5)
45
+ self.streamSock.connect((self.host, self.port))
46
+ self.pythonVersion = sys.version[0]
47
+ self.cursor = '>'
48
+ #clear packets
49
+ welcome_string = self.streamSock.recv(self.maxRxBytes).rstrip()
50
+
51
+
52
+ def connect(self, connection_message: bool = True) -> str:
53
+ """
54
+ Opens the connection to the QIS backend
55
+
56
+ Args:
57
+ connection_message:
58
+ Defaults to True. If set to False, suppresses the warning message about an
59
+ instance already running on the specified port. This can be useful when
60
+ using `isQisRunning()` from `qisFuncs`.
61
+
62
+ Raises:
63
+ Exception:
64
+ If the connection fails or the welcome string is not received an exception is raised
65
+
66
+ Returns:
67
+ The welcome string received from the backend server upon a successful
68
+ connection. This will confirm the QIS version but is generally not used other than
69
+ for debugging
70
+ """
71
+
72
+ try:
73
+ self.device_dict_setup('QIS')
74
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
75
+ self.sock.settimeout(5)
76
+ self.sock.connect((self.host, self.port))
77
+
78
+ #clear packets
79
+ try:
80
+ welcome_string = self.sock.recv(self.maxRxBytes).rstrip()
81
+ welcome_string = 'Connected@' + str(self.host) + ':' + str(self.port) + ' ' + '\n ' + str(welcome_string)
82
+ self.deviceDict['QIS'][0:3] = [False, 'Connected', welcome_string]
83
+ return welcome_string
84
+ except Exception as e:
85
+ logger.error('No welcome received. Unable to connect to Quarch backend on specified host and port (' + self.host + ':' + str(self.port) + ')')
86
+ logger.error('Is backend running and host accessible?')
87
+ self.deviceDict['QIS'][0:3] = [True, 'Disconnected', 'Unable to connect to QIS']
88
+ raise e
89
+ except Exception as e:
90
+ self.device_dict_setup('QIS')
91
+ if connection_message:
92
+ logger.error('Unable to connect to Quarch backend on specified host and port (' + self.host + ':' + str(self.port) + ').')
93
+ logger.error('Is backend running and host accessible?')
94
+ self.deviceDict['QIS'][0:3] = [True, 'Disconnected', 'Unable to connect to QIS']
95
+ raise e
96
+
97
+
98
+ def disconnect(self) -> str:
99
+ """
100
+ Disconnects the current connection to the QIS backend.
101
+
102
+ This method attempts to gracefully disconnect from the backend server and updates
103
+ the connection state in the device dictionary. If an error occurs during the
104
+ disconnection process, the state is updated to indicate the failure, and the
105
+ exception is re-raised
106
+
107
+ Returns:
108
+ str: A message indicating that the disconnection process has started.
109
+
110
+ Raises:
111
+ Exception: Propagates any exception that occurs during the disconnection process.
112
+ """
113
+ res = 'Disconnecting from backend'
114
+ try:
115
+ self.sock.shutdown(socket.SHUT_RDWR)
116
+ self.sock.close()
117
+ self.deviceDict['QIS'][0:3] = [False, "Disconnected", 'Successfully disconnected from QIS']
118
+ except Exception as e:
119
+ exc_type, exc_obj, exc_tb = sys.exc_info()
120
+ fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
121
+ message = 'Unable to end connection. ' + self.host + ':' + str(self.port) + ' \r\n' + str(exc_type) + ' ' + str(fname) + ' ' + str(exc_tb.tb_lineno)
122
+ self.deviceDict['QIS'][0:3] = [True, "Connected", message]
123
+ raise e
124
+ return res
125
+
126
+ def close_connection(self, sock=None, con_string: str=None) -> str:
127
+ """
128
+ Orders QIS to release a given device (or all devices if no connection string is specified)
129
+ This is more important for TCP connected devices as the socket is held open until
130
+ specifically released.
131
+
132
+ Args:
133
+ sock:
134
+ The socket object to close the connection to. Defaults to the existing socket.
135
+ con_string:
136
+ Specify the device ID to close, otherwise all devices will be closed
137
+
138
+ Raises:
139
+ ConnectionResetError: Raised if the socket connection has already been reset.
140
+
141
+ Returns:
142
+ The response received after sending the close command. On success, this will be: 'OK'
143
+
144
+ """
145
+ if sock is None:
146
+ sock = self.sock
147
+ if con_string is None:
148
+ cmd = "close"
149
+ else:
150
+ cmd = con_string + " close"
151
+ try:
152
+ response = self.sendAndReceiveText(sock, cmd)
153
+ return response
154
+ except ConnectionResetError:
155
+ logger.error('Unable to close connection to device(s), QIS may be already closed')
156
+ return "FAIL: Unable to close connection to device(s), QIS may be already closed"
157
+
158
+ def start_stream(self, module: str, file_name: str, max_file_size: int, release_on_data: bool, separator: str,
159
+ stream_duration: float=None, in_memory_data: StringIO=None, output_file_handle=None, use_gzip: bool=None,
160
+ gzip_compress_level: int=9):
161
+ """
162
+ Begins a stream process which will record data from the module to a CSV file or in memory CSV equivalent
163
+
164
+ Args:
165
+ module:
166
+ The ID of the module for which the stream is being initiated.
167
+ file_name:
168
+ The target file path+name for storing the streamed data in CSV form.
169
+ max_file_size:
170
+ The maximum size in megabytes allowed for the output file.
171
+ release_on_data:
172
+ If set, blocks further streams until this one has started
173
+ separator:
174
+ The value separator used to format the streamed CSV data.
175
+ stream_duration:
176
+ The duration (in seconds) for which the streaming process should run. Unlimited if None.
177
+ in_memory_data:
178
+ An in memory CSV StringIO as an alternate to file output
179
+ output_file_handle:
180
+ A file handle to an output file where the stream data is written as an alternate to a file name
181
+ use_gzip:
182
+ A flag indicating whether the output file should be compressed using gzip to reduce disk use
183
+
184
+ Returns:
185
+ None
186
+ """
187
+ self.StreamRunSentSemaphore.acquire()
188
+ self.device_dict_setup('QIS')
189
+ i = self.device_control_index(module)
190
+ self.stopFlagList[i] = True
191
+ self.stripesEvent.set()
192
+ self.module_xml_header = None
193
+
194
+ # Create the worker thread to handle stream processing
195
+ t1 = threading.Thread(target=self.start_stream_thread, name=module,
196
+ args=(module, file_name, max_file_size, release_on_data, separator, stream_duration,
197
+ in_memory_data, output_file_handle, use_gzip, gzip_compress_level))
198
+ # Start the thread
199
+ t1.start()
200
+
201
+ while self.stripesEvent.is_set():
202
+ pass
203
+
204
+ def stop_stream(self, module, blocking:bool = True):
205
+ """
206
+
207
+ Args:
208
+ module:
209
+ The quarchPPM module instance for which the streaming process is to be stopped.
210
+ blocking:
211
+ If set to True, the function will block and wait until the module has
212
+ completely stopped streaming. Defaults to True.
213
+
214
+ Returns:
215
+ None
216
+
217
+ """
218
+
219
+ module_name=module.ConString
220
+ i = self.device_control_index(module_name)
221
+ self.stopFlagList[i] = False
222
+
223
+ # Wait until the stream thread is finished before returning to the user.
224
+ # This means this function will block until the QIS buffer is emptied by the second while
225
+ # loop in startStreamThread. This may take some time, especially at low averaging,
226
+ # but should guarantee the data won't be lost and QIS buffer is emptied.
227
+ if blocking:
228
+ running = True
229
+ while running:
230
+ thread_name_list = []
231
+ for t1 in threading.enumerate():
232
+ thread_name_list.append(t1.name)
233
+ module_streaming= module.sendCommand("rec stream?").lower() #checking if module thinks its streaming.
234
+ module_streaming2= module.sendCommand("stream?").lower() #checking if the module has told qis it has stopped streaming.
235
+
236
+ if module_name in thread_name_list or "running" in module_streaming or "running" in module_streaming2:
237
+ time.sleep(0.1)
238
+ else:
239
+ running = False
240
+
241
+ def start_stream_thread(self, module: str, file_name: str, max_file_size: float, release_on_data: bool, separator: str,
242
+ stream_duration: int=None, in_memory_data=None, output_file_handle=None, use_gzip: bool=False,
243
+ gzip_compress_level: int=9):
244
+ """
245
+
246
+ Args:
247
+ module:
248
+ The name of the module from which data is to be streamed.
249
+ file_name:
250
+ The path to the file where streamed data will be written. Mandatory if neither an in-memory
251
+ buffer (in_memory_data) nor an external file handle (output_file_handle) is provided.
252
+ max_file_size:
253
+ The maximum permissible file size in MB. After reaching this limit, streaming to the current
254
+ file will stop
255
+ release_on_data:
256
+ True to prevent the stream lock from releasing until data has been received
257
+ separator:
258
+ Custom separator used to CSV data
259
+ stream_duration:
260
+ Duration of streaming in seconds, relative to the sampling period. Defaults to streaming
261
+ indefinitely.
262
+ in_memory_data:
263
+ An in-memory buffer of type StringIO to hold streamed data. If set, data is written here
264
+ instead of a file.
265
+ output_file_handle:
266
+ A pre-opened file handle where data will be written. If set, file_name is ignored.
267
+ use_gzip:
268
+ If True, writes streamed data to a gzip-compressed file.
269
+ gzip_compress_level:
270
+ (Default: 9) The compression level (0-9) to use for gzip.
271
+ 1 is fastest with low compression. 9 is slowest with high compression.
272
+
273
+ Raises:
274
+ TypeError
275
+ If in_memory_data is passed but is not of type StringIO.
276
+ ValueError
277
+ If file_name is not provided and neither in_memory_data nor output_file_handle is given.
278
+ Also raised for invalid or undecodable sampling periods.
279
+
280
+ Returns:
281
+ None
282
+ """
283
+
284
+ f = None
285
+ max_mb_val = 0
286
+ file_opened_by_function = False # True if this function opens the file
287
+ is_in_memory_stream = False # True if using inMemoryData (StringIO)
288
+
289
+ # Output priority: 1. output_file_handle, 2. inMemoryData, 3. A new a file
290
+ if output_file_handle is not None:
291
+ f = output_file_handle
292
+ # Caller is responsible for the handle's mode (e.g., text/binary) and type.
293
+ elif in_memory_data is not None:
294
+ if not isinstance(in_memory_data, StringIO):
295
+ raise TypeError("Error! The parameter 'inMemoryData' must be of type StringIO.")
296
+ f = in_memory_data
297
+ is_in_memory_stream = True
298
+ else:
299
+ # No external handle or in-memory buffer, so open a file.
300
+ if not file_name: # fileName is mandatory if we are to open a file.
301
+ raise ValueError("fie_name must be provided if output_file_handle and in_memory_data are None.")
302
+ file_opened_by_function = True
303
+ if use_gzip:
304
+ # Open in text mode ('wt'). Encoding 'utf-8' is a good default.
305
+ # gzip.open in text mode handles newline conversions.
306
+ f = gzip.open(f'{file_name}.gz', 'wt', encoding='utf-8', compresslevel=gzip_compress_level)
307
+ else:
308
+ # Open in text mode ('w').
309
+ # newline='' ensures that '\n' is written as '\n' on all platforms.
310
+ f = open(file_name, 'w', encoding='utf-8', newline='')
311
+
312
+ # Check for a valid max file size limit
313
+ if max_file_size is not None:
314
+ try:
315
+ max_mb_val = int(max_file_size)
316
+ except (ValueError, TypeError):
317
+ logger.warning(f"Invalid max_file_size parameter: {max_file_size}. No limit will be applied")
318
+ max_file_size = None
319
+
320
+ # Send stream command so the module starts streaming data into the backends buffer
321
+ stream_res = self.send_command('rec stream', device=module, qis_socket=self.streamSock)
322
+ # Check the stream started
323
+ if 'OK' in stream_res:
324
+ if not release_on_data:
325
+ self.StreamRunSentSemaphore.release()
326
+ self.stripesEvent.clear()
327
+ self.deviceDict[module][0:3] = [False, 'Running', 'Stream Running']
328
+ else:
329
+ self.StreamRunSentSemaphore.release()
330
+ self.stripesEvent.clear()
331
+ self.deviceDict[module][0:3] = [True, 'Stopped', module + " couldn't start because " + stream_res]
332
+ if file_opened_by_function and f:
333
+ try:
334
+ f.close()
335
+ except Exception as e_close:
336
+ logger.error(f"Error closing file {file_name} on stream start failure: {e_close}")
337
+ return
338
+
339
+ # Poll for the stream header to become available. This is needed to configure the output file
340
+ base_sample_period = self.stream_header_average(device=module, sock=self.streamSock)
341
+ count = 0
342
+ max_tries = 10
343
+ while 'Header Not Available' in base_sample_period:
344
+ base_sample_period = self.stream_header_average(device=module, sock=self.streamSock)
345
+ time.sleep(0.1)
346
+ count += 1
347
+ if count > max_tries:
348
+ self.deviceDict[module][0:3] = [True, 'Stopped', 'Header not available']
349
+ if file_opened_by_function and f:
350
+ try:
351
+ f.close()
352
+ except Exception as e_close:
353
+ logger.error(f"Error closing file {file_name} on header failure: {e_close}")
354
+ return # Changed from exit() for cleaner thread termination
355
+
356
+ # Format the header and write it to the output file
357
+ format_header = self.stream_header_format(device=module, sock=self.streamSock)
358
+ format_header = format_header.replace(", ", separator)
359
+ f.write(format_header + '\n')
360
+
361
+ # Initialize stream variables
362
+ max_file_exceeded = False
363
+ open_attempts = 0
364
+ leftover = 0
365
+ remaining_stripes = []
366
+ stream_overrun = False
367
+ stream_complete = False
368
+ stream_status_str = ""
369
+
370
+ # Calculate and verify stripe rate information
371
+ if 'ns' in base_sample_period.lower():
372
+ base_sample_unit_exponent = -9
373
+ elif 'us' in base_sample_period.lower():
374
+ base_sample_unit_exponent = -6
375
+ elif 'ms' in base_sample_period.lower():
376
+ base_sample_unit_exponent = -3
377
+ elif 'S' in base_sample_period.lower(): # Original was 'S', assuming it means 's'
378
+ base_sample_unit_exponent = 0
379
+ else:
380
+ # Clean up and raise error if baseSamplePeriod is undecodable
381
+ if file_opened_by_function and f:
382
+ try:
383
+ f.close()
384
+ except Exception as e_close:
385
+ logger.error(f"Error closing file {file_name} due to ValueError: {e_close}")
386
+ raise ValueError(f"couldn't decode samplePeriod: {base_sample_period}")
387
+
388
+ base_sample_period_period_s = int(re.search(r'^\d*\.?\d*', base_sample_period).group()) * (10 ** base_sample_unit_exponent)
389
+
390
+ # Now we loop to process the stripes of data as they are available
391
+ is_run = True
392
+ while is_run:
393
+ try:
394
+ # Check for exit flags. These can be from user request (stopFlagList) or from the stream
395
+ # process ending
396
+ i = self.device_control_index(module)
397
+ while self.stopFlagList[i] and (not stream_overrun) and (not stream_complete):
398
+
399
+ # Read a block of stripes from QIS
400
+ stream_status_str, new_stripes = self.stream_get_stripes_text(self.streamSock, module)
401
+
402
+ # Overrun is a termination event where the stream stopped earlier than desired and must
403
+ # be flagged to the user
404
+ if "overrun" in stream_status_str:
405
+ stream_overrun = True
406
+ self.deviceDict[module][0:3] = [True, 'Stopped', 'Device buffer overrun']
407
+ if "eof" in stream_status_str:
408
+ stream_complete = True
409
+
410
+ # Continue here if there are stripes to process
411
+ if len(new_stripes) > 0:
412
+ # switch in the correct value seperator
413
+ new_stripes = new_stripes.replace(' ', separator)
414
+
415
+ # Track the total size of the file here if needed
416
+ if max_file_size is not None:
417
+ current_file_mb = 0.0
418
+ if is_in_memory_stream:
419
+ current_file_mb = f.tell() / 1048576.0
420
+ elif file_name:
421
+ try:
422
+ # os.stat reflects the size on disk. For buffered writes (incl. gzip),
423
+ # this might not be the exact current unwritten buffer size + disk size
424
+ # without a flush, but it's an decent estimate.
425
+ stat_info = os.stat(file_name)
426
+ current_file_mb = stat_info.st_size / 1048576.0
427
+ except FileNotFoundError:
428
+ current_file_mb = 0.0 # File might not exist yet or fileName is not locatable
429
+ except Exception as e_stat:
430
+ logger.warning(f"Could not get file size for {file_name}: {e_stat}")
431
+ current_file_mb = 0.0 # Default to small size on error
432
+ else:
433
+ # output_file_handle was given, but fileName was None. Cannot check disk size.
434
+ # Assume it's okay or managed by the caller. fileMaxMB check effectively bypassed.
435
+ current_file_mb = 0.0
436
+
437
+ # Flag the limit has been exceeded
438
+ if current_file_mb > max_mb_val:
439
+ max_file_exceeded = True
440
+ max_file_status = self.stream_buffer_status(device=module, sock=self.streamSock)
441
+ f.write('Warning: Max file size exceeded before end of stream.\n')
442
+ f.write('Unrecorded stripes in buffer when file full: ' + max_file_status + '.\n')
443
+ self.deviceDict[module][0:3] = [True, 'Stopped', 'User defined max filesize reached']
444
+ break # Exit stream processing loop
445
+
446
+ # Release the stream semaphore now we have data
447
+ if release_on_data:
448
+ self.StreamRunSentSemaphore.release()
449
+ self.stripesEvent.clear()
450
+ release_on_data = False
451
+
452
+ # If a duration has been set, track it based on the time of the last stripe
453
+ if stream_duration is not None:
454
+ last_line = new_stripes.splitlines()[-1]
455
+ last_time = last_line.split(separator)[0]
456
+
457
+ # Write all the stripes if we can
458
+ if int(last_time) < int(stream_duration / (10 ** base_sample_unit_exponent)):
459
+ f.write(new_stripes)
460
+ # Otherwise only write stripes within the duration limit
461
+ else:
462
+ for this_line in new_stripes.splitlines():
463
+ this_time_str = this_line.split(separator)[0]
464
+ if int(this_time_str) < int(stream_duration / (10 ** base_sample_unit_exponent)):
465
+ f.write(this_line + '\r\n') # Put the CR back on the end
466
+ else:
467
+ stream_complete = True
468
+ break
469
+ # Default to writing all stripes
470
+ else:
471
+ f.write(new_stripes)
472
+ # If we have no data
473
+ else:
474
+ if stream_overrun:
475
+ break # Exit stream processing loop
476
+ elif "stopped" in stream_status_str:
477
+ self.deviceDict[module][0:3] = [True, 'Stopped', 'User halted stream']
478
+ break # Exit stream processing loop
479
+ # End of stream data processing loop
480
+
481
+ # Ensure the stream is fully stopped, though standard exit cases should have ended it already
482
+ self.send_command('rec stop', device=module, qis_socket=self.streamSock)
483
+ stream_state = self.send_command('stream?', device=module, qis_socket=self.streamSock)
484
+ while "stopped" not in stream_state.lower():
485
+ logger.debug("waiting for stream? to return stopped")
486
+ time.sleep(0.1)
487
+ stream_state = self.send_command('stream?', device=module, qis_socket=self.streamSock)
488
+
489
+ if stream_overrun:
490
+ self.deviceDict[module][0:3] = [True, 'Stopped', 'Device buffer overrun - QIS buffer empty']
491
+ elif not max_file_exceeded:
492
+ self.deviceDict[module][0:3] = [False, 'Stopped', 'Stream stopped']
493
+
494
+ is_run = False # Exit main while loop
495
+ except IOError as err:
496
+ logger.error(f"IOError in startStreamThread for module {module}: {err}")
497
+
498
+ # Check if this error might be related to GZIP performance
499
+ if use_gzip:
500
+ logger.warning(f"An IOError occurred while writing GZIP data for {module}.")
501
+ logger.warning("This can happen at high data rates if compression is too slow for the I/O buffer.")
502
+ logger.warning(f"Current compression level is {gzip_compress_level}. "
503
+ f"Consider re-running with a lower 'gzip_compress_level' (e.g., 6 or 1) "
504
+ "if this error persists.")
505
+
506
+ # File might have been closed by the system if it's a pipe and the other end closed or other severe errors.
507
+ # Attempt to close only if this function opened it, and it seems like it might be openable/closable.
508
+ if file_opened_by_function and f is not None:
509
+ try:
510
+ if not f.closed:
511
+ f.close()
512
+ except Exception as e_close:
513
+ logger.error(f"Error closing file {file_name} during IOError handling: {e_close}")
514
+ f = None # Avoid trying to close again in finally if error persists
515
+
516
+ time.sleep(0.5)
517
+ open_attempts += 1
518
+ if open_attempts > 4:
519
+ logger.error(f"Too many IOErrors in QisInterface for module {module}. Raising error.")
520
+ # Set device status before raising, if possible
521
+ self.deviceDict[module][0:3] = [True, 'Stopped', f'IOError limit exceeded: {err}']
522
+ raise # Re-raise the last IOError
523
+ finally:
524
+ if file_opened_by_function and f is not None:
525
+ try:
526
+ if not f.closed: # Check if not already closed (e.g. in IOError block)
527
+ f.close()
528
+ except Exception as e_close:
529
+ logger.error(f"Error closing file {file_name} in finally block: {e_close}")
530
+ # If output_file_handle was passed, the caller is responsible for closing.
531
+ # If inMemoryData was passed, it's managed by the caller.
532
+
533
+ def get_device_list(self, sock=None) -> filter:
534
+ """
535
+ returns a list of device IDs that are available for connection
536
+
537
+ Args:
538
+ sock:
539
+ Optional connection socket
540
+
541
+ Returns:
542
+ A filtered iterable list of devices
543
+
544
+ """
545
+
546
+ if sock is None:
547
+ sock = self.sock
548
+
549
+ dev_string = self.sendAndReceiveText(sock, '$list')
550
+ dev_string = dev_string.replace('>', '')
551
+ dev_string = dev_string.replace(r'\d+\) ', '')
552
+ dev_string = dev_string.split('\r\n')
553
+ dev_string = filter(None, dev_string) #remove empty elements
554
+
555
+ return dev_string
556
+
557
+ def get_list_details(self, sock=None) -> list:
558
+ """
559
+ Extended version of get_device_list which also returns the additional details
560
+ fields for each module
561
+
562
+ Args:
563
+ sock:
564
+ Optional connection socket
565
+ Returns:
566
+ Iterable list of strings containing the details of each device available for connection.
567
+
568
+ """
569
+ if sock is None:
570
+ sock = self.sock
571
+
572
+ dev_string = self.sendAndReceiveText(sock, '$list details')
573
+ dev_string = dev_string.replace('>', '')
574
+ dev_string = dev_string.replace(r'\d+\) ', '')
575
+ dev_string = dev_string.split('\r\n')
576
+ dev_string = [x for x in dev_string if x] # remove empty elements
577
+ return dev_string
578
+
579
+ def scan_ip(self, qis_connection, ip_address) -> bool:
580
+ """
581
+ Triggers QIS to look at a specific IP address for a module
582
+
583
+ Arguments
584
+
585
+ QisConnection : QpsInterface
586
+ The interface to the instance of QIS you would like to use for the scan.
587
+ ipAddress : str
588
+ The IP address of the module you are looking for eg '192.168.123.123'
589
+ """
590
+
591
+ logger.debug("Starting QIS IP Address Lookup at " + ip_address)
592
+ if not ip_address.lower().__contains__("tcp::"):
593
+ ip_address = "TCP::" + ip_address
594
+ response = "No response from QIS Scan"
595
+ try:
596
+ response = qis_connection.send_command("$scan " + ip_address)
597
+ # The valid response is "Located device: 192.168.1.2"
598
+ if "located" in response.lower():
599
+ logger.debug(response)
600
+ # return the valid response
601
+ return True
602
+ else:
603
+ logger.warning("No module found at " + ip_address)
604
+ logger.warning(response)
605
+ return False
606
+
607
+ except Exception as e:
608
+ logger.warning("No module found at " + ip_address)
609
+ logger.warning(e)
610
+ return False
611
+
612
+
613
+ def get_qis_module_selection(self, preferred_connection_only=True, additional_options="DEF_ARGS", scan=True) -> str:
614
+ """
615
+ Scans for available modules and allows the user to select one through an interactive selection process.
616
+ Can also handle additional custom options and some built-in ones such as rescanning
617
+
618
+ Arguments:
619
+ preferred_connection_only : bool
620
+ by default (True), returns only one preferred connection eg: USB for simplicity
621
+ additional_options: list
622
+ Additional operational options provided during module selection, such as rescan,
623
+ all connection types, and IP scan. Defaults to ['rescan', 'all con types', 'ip scan']. These allow the
624
+ additional options to be given to the user and handled in the top level script
625
+ scan : bool
626
+ Indicates whether to initiate a rescanning process for devices before listing. Defaults to True and
627
+ will take longer to return
628
+
629
+ Returns:
630
+ str: The identifier of the selected module, or the action selected from the additional options.
631
+
632
+ Raises:
633
+ KeyError: Raised when unexpected keys are found in the scanned device data.
634
+ ValueError: Raised if no valid selection is made or the provided IP address is invalid.
635
+ """
636
+
637
+ # Avoid mutable warning by adding the argument list in the function rather than the header
638
+ if additional_options == "DEF_ARGS":
639
+ additional_options = ['rescan', 'all con types', 'ip scan']
640
+
641
+ table_headers = ["Modules"]
642
+ ip_address = None
643
+ favourite = preferred_connection_only
644
+ while True:
645
+ printText("Scanning for modules...")
646
+ found_devices = None
647
+ if scan and ip_address is None:
648
+ found_devices = self.qis_scan_devices(scan=scan, preferred_connection_only=favourite)
649
+ elif scan and ip_address is not None:
650
+ found_devices = self.qis_scan_devices(scan=scan, preferred_connection_only=favourite, ip_address=ip_address)
651
+
652
+ my_device_id = listSelection(title="Select a module",message="Select a module",
653
+ selectionList=found_devices, additionalOptions= additional_options,
654
+ nice=True, tableHeaders=table_headers, indexReq=True)
655
+
656
+ if my_device_id.lower() == 'rescan':
657
+ favourite = True
658
+ ip_address = None
659
+ continue
660
+ elif my_device_id.lower() == 'all con types':
661
+ favourite = False
662
+ printText("Displaying all connection types...")
663
+ continue
664
+ elif my_device_id.lower() == 'ip scan':
665
+ ip_address = requestDialog(title="Please input the IP Address you would like to scan")
666
+ favourite = False
667
+ continue
668
+ break
669
+
670
+ return my_device_id
671
+
672
+ def qis_scan_devices(self, scan=True, preferred_connection_only=True, ip_address=None) -> list:
673
+ """
674
+ Begins a scan for devices and returns a simple list of devices
675
+
676
+ Args:
677
+ scan:
678
+ Should a scan be initiated? If False, the function will return immediately with the list
679
+ preferred_connection_only:
680
+ The default (True), returns only one preferred connection eg: USB for simplicity
681
+ ip_address:
682
+ IP address of the module you are looking for eg '192.168.123.123'
683
+
684
+ Returns:
685
+ list: List of module strings found during scan
686
+ """
687
+
688
+ device_list = []
689
+ found_devices = "1"
690
+ found_devices2 = "2" # this is used to check if new modules are being discovered or if all have been found.
691
+ scan_wait = 2 # The number of seconds waited between the scan and the initial list
692
+ list_wait = 1 # The time between checks for new devices in the list
693
+
694
+ if scan:
695
+ # Perform the initial scan attempt
696
+ if ip_address is None:
697
+ dev_string = self.sendAndReceiveText(self.sock, '$scan')
698
+ else:
699
+ dev_string = self.sendAndReceiveText(self.sock, '$scan TCP::' + ip_address)
700
+ # Wait for devices to enumerate
701
+ time.sleep(scan_wait)
702
+ # While new devices are being found, extend the wait time
703
+ while found_devices not in found_devices2:
704
+ found_devices = self.sendAndReceiveText(self.sock, '$list')
705
+ time.sleep(list_wait)
706
+ found_devices2 = self.sendAndReceiveText(self.sock, '$list')
707
+ else:
708
+ found_devices = self.sendAndReceiveText(self.sock, '$list')
709
+
710
+ # If we found devices, process them into a list to return
711
+ if not "no devices found" in found_devices.lower():
712
+ found_devices = found_devices.replace('>', '')
713
+ found_devices = found_devices.split('\r\n')
714
+ # Can't stream over REST. Removing all REST connections.
715
+ temp_list= list()
716
+ for item in found_devices:
717
+ if item is None or "rest" in item.lower() or item == "":
718
+ pass
719
+ else:
720
+ temp_list.append(item.split(")")[1].strip())
721
+ found_devices = temp_list
722
+
723
+ # If the preferred connection only flag is True, then only show one connection type for each module connected.
724
+ # First, order the devices by their preference type and then pick the first con type found for each module.
725
+ if preferred_connection_only:
726
+ found_devices = self.sort_favourite(found_devices)
727
+ else:
728
+ found_devices = ["***No Devices Found***"]
729
+
730
+ return found_devices
731
+
732
+ def sort_favourite(self, found_devices) -> list:
733
+ """
734
+ Reduces the list of located devices by referencing to the preferred type of connection. Only
735
+ one connection type will be returned for each module for easier user selection. ie: A module connected
736
+ on both USB and TCP will now only return with USB
737
+
738
+ Args:
739
+ found_devices:
740
+ List of located devices from a scan operation
741
+
742
+ Returns:
743
+ List of devices filtered/sorted devices
744
+ """
745
+
746
+ index = 0
747
+ sorted_found_devices = []
748
+ con_pref = ["USB", "TCP", "SERIAL", "REST", "TELNET"]
749
+ while len(sorted_found_devices) != len(found_devices):
750
+ for device in found_devices:
751
+ if con_pref[index] in device.upper():
752
+ sorted_found_devices.append(device)
753
+ index += 1
754
+ found_devices = sorted_found_devices
755
+
756
+ # new dictionary only containing one favourite connection to each device.
757
+ fav_con_found_devices = []
758
+ index = 0
759
+ for device in sorted_found_devices:
760
+ if fav_con_found_devices == [] or not device.split("::")[1] in str(fav_con_found_devices):
761
+ fav_con_found_devices.append(device)
762
+ found_devices = fav_con_found_devices
763
+ return found_devices
764
+
765
+ def stream_running_status(self, device, sock=None) -> str:
766
+ """
767
+ returns a single word status string for a given device. Generally this will be running, overrun, or stopped
768
+
769
+ Arguments
770
+
771
+ device : str
772
+ The device ID to target
773
+ sock:
774
+ The socket to communicate over, or None to use the default.
775
+
776
+ Returns:
777
+ str: Single word status string to show the operation of streaming
778
+ """
779
+ if sock is None:
780
+ sock = self.sock
781
+
782
+ index = 0
783
+ stream_status = self.sendAndReceiveText(sock, 'stream?', device)
784
+
785
+ # Split the response, select the first time and trim the colon
786
+ status_parts = stream_status.split('\r\n')
787
+ stream_status = re.sub(r':', '', status_parts[index])
788
+ return stream_status
789
+
790
+ def stream_buffer_status(self, device, sock=None) -> str:
791
+ """
792
+ returns the info on the stripes buffered during the stream
793
+
794
+ Arguments
795
+
796
+ device : str
797
+ The device ID to target
798
+ sock:
799
+ The socket to communicate over, or None to use the default.
800
+
801
+ Returns:
802
+ str: String with the numbers of stripes buffered
803
+ """
804
+ if sock is None:
805
+ sock = self.sock
806
+
807
+ index = 1
808
+ stream_status = self.sendAndReceiveText(sock, 'stream?', device)
809
+
810
+ # Split the response, select the second the info on the stripes buffered
811
+ status_lines = stream_status.split('\r\n')
812
+ stream_status = re.sub(r'^Stripes Buffered: ', '', status_lines[index])
813
+ return stream_status
814
+
815
+ # TODO: MD - This function should be replaced with a more generic method of accessing the header
816
+ # The return of a string with concatenated value and units should be replaced with something easier to parse
817
+ def stream_header_average(self, device, sock=None) -> str:
818
+ """
819
+ Gets the averaging used on the current stream, required for processing the stripe data returned from QIS
820
+
821
+ Arguments
822
+
823
+ device : str
824
+ The device ID to target
825
+ sock:
826
+ The socket to communicate over, or None to use the default.
827
+
828
+ Returns:
829
+ str: String with the rate and unit
830
+ """
831
+ try:
832
+ if sock is None:
833
+ sock = self.sock
834
+
835
+ index = 2 # index of relevant line in split string
836
+ stream_status = self.send_and_receive_text(sock, send_text='stream text header', device=device)
837
+
838
+ self.qps_stream_header = stream_status
839
+
840
+ # Check for the header format. If XML, process here
841
+ if self.is_xml_header(stream_status):
842
+ # Get the basic averaging rate (V3 header)
843
+ xml_root = self.get_stream_xml_header(device=device, sock=sock)
844
+ self.module_xml_header = xml_root
845
+
846
+ # Return the time-based averaging string
847
+ device_period = xml_root.find('.//devicePeriod')
848
+ if device_period is None:
849
+ device_period = xml_root.find('.//devicePerioduS')
850
+ if device_period is None:
851
+ device_period = xml_root.find('.//mainPeriod')
852
+ average_str = device_period.text
853
+ return average_str
854
+ # For legacy text headers, process here
855
+ else:
856
+ status_lines = stream_status.split('\r\n')
857
+ if 'Header Not Available' in status_lines[0]:
858
+ dummy = status_lines[0] + '. Check stream has been run on device.'
859
+ return dummy
860
+ average_str = re.sub(r'^Average: ', '', status_lines[index])
861
+ avg = average_str
862
+ avg = 2 ** int(avg)
863
+ return '{}'.format(avg)
864
+ except Exception as e:
865
+ logger.error(device + ' Unable to get stream average.' + self.host + ':' + str(self.port))
866
+ raise e
867
+
868
+ def stream_header_format(self, device, sock=None) -> str:
869
+ """
870
+ Formats the stream header for use at the top of a CSV file. This adds the appropriate time column and
871
+ each of the channel data columns
872
+
873
+ Arguments
874
+
875
+ device:
876
+ The device ID to target
877
+ sock:
878
+ The socket to communicate over, or None to use the default.
879
+
880
+ Returns:
881
+ str: Get the CSV formatted header string for the current stream
882
+ """
883
+ try:
884
+ if sock is None:
885
+ sock = self.sock
886
+
887
+ index = 1
888
+ stream_status = self.sendAndReceiveText(sock,'stream text header', device)
889
+ # Check if this is an XML form header
890
+ if self.is_xml_header (stream_status):
891
+ # Get the basic averaging rate (V3 header)
892
+ xml_root = self.get_stream_xml_header (device=device, sock=sock)
893
+ # Return the time-based averaging string
894
+ device_period = xml_root.find('.//devicePeriod')
895
+ time_unit = 'uS'
896
+ if device_period is None:
897
+ device_period = xml_root.find('.//devicePerioduS')
898
+ if device_period is None:
899
+ device_period = xml_root.find('.//mainPeriod')
900
+ if 'ns' in device_period.text:
901
+ time_unit = 'nS'
902
+
903
+ # The time column always first
904
+ format_header = 'Time ' + time_unit + ','
905
+ # Find the channels section of each group and iterate through it to add the channel columns
906
+ for group in xml_root.iter():
907
+ if group.tag == "channels":
908
+ for chan in group:
909
+ # Avoid children that are not named channels
910
+ if (chan.find('.//name') is not None):
911
+ name_str = chan.find('.//name').text
912
+ group_str = chan.find('.//group').text
913
+ unit_str = chan.find('.//units').text
914
+ format_header = format_header + name_str + " " + group_str + " " + unit_str + ","
915
+ format_header = format_header.rstrip(",")
916
+ return format_header
917
+ else:
918
+ stream_status = stream_status.split('\r\n')
919
+ if 'Header Not Available' in stream_status[0]:
920
+ err_str = stream_status[0] + '. Check stream has been ran on device.'
921
+ logger.error(err_str)
922
+ return err_str
923
+ output_mode = self.sendAndReceiveText(sock,'Config Output Mode?', device)
924
+ power_mode = self.sendAndReceiveText(sock,'stream mode power?', device)
925
+ data_format = int(re.sub(r'^Format: ', '', stream_status[index]))
926
+ b0 = 1 #12V_I
927
+ b1 = 1 << 1 #12V_V
928
+ b2 = 1 << 2 #5V_I
929
+ b3 = 1 << 3 #5V_V
930
+ format_header = 'StripeNum, Trig, '
931
+ if data_format & b3:
932
+ if '3V3' in output_mode:
933
+ format_header = format_header + '3V3_V,'
934
+ else:
935
+ format_header = format_header + '5V_V,'
936
+ if data_format & b2:
937
+ if '3V3' in output_mode:
938
+ format_header = format_header + ' 3V3_I,'
939
+ else:
940
+ format_header = format_header + ' 5V_I,'
941
+
942
+ if data_format & b1:
943
+ format_header = format_header + ' 12V_V,'
944
+ if data_format & b0:
945
+ format_header = format_header + ' 12V_I'
946
+ if 'Enabled' in power_mode:
947
+ if '3V3' in output_mode:
948
+ format_header = format_header + ' 3V3_P'
949
+ else:
950
+ format_header = format_header + ' 5V_P'
951
+ if (data_format & b1) or (data_format & b0):
952
+ format_header = format_header + ' 12V_P'
953
+ return format_header
954
+ except Exception as e:
955
+ logger.error(device + ' Unable to get stream format.' + self.host + ':' + '{}'.format(self.port))
956
+ raise e
957
+
958
+ def stream_get_stripes_text(self, sock, device: str) -> Tuple[str, str]:
959
+ """
960
+ Retrieve and process text data from a QIS stream.
961
+ We try to ready a block of data and also check for end of data and error cases
962
+
963
+ Args:
964
+ sock:
965
+ The socket instance used for communication with the device.
966
+ device:
967
+ The device ID string
968
+
969
+ Returns:
970
+ A tuple containing:
971
+ - The status of the data stream as a comma seperated list of status items
972
+ - The retrieved text data from the stream.
973
+ """
974
+
975
+ stream_status = "running"
976
+ is_end_of_block = False
977
+
978
+ # Try and read the next blocks of stripes from QIS
979
+ stripes = self.sendAndReceiveText(sock, 'stream text all', device)
980
+
981
+ # The 'eof' marker ONLY indicates that the full number of requested stripes was not available.
982
+ # More may be found later.
983
+ if stripes.endswith("eof\r\n>"):
984
+ is_end_of_block = True
985
+ stripes = stripes.rstrip("eof\r\n>")
986
+ if stripes.endswith("\r\n>"):
987
+ stripes= stripes[:-1] # remove the trailing ">"
988
+ # The current reader seems to lose the final line feeds, so check for this
989
+ if len(stripes) > 0:
990
+ if not stripes.endswith("\r\n"):
991
+ stripes += "\r\n"
992
+
993
+ # If there is an unusually small data set, check the stream status to make sure data is coming
994
+ # 7 is a little arbitrary, but smaller than any possible stripe size. Over calling will not matter anyway
995
+ if len(stripes) < 7 or is_end_of_block:
996
+ current_status = self.sendAndReceiveText(sock, 'stream?', device).lower()
997
+ if "running" in current_status:
998
+ stream_status = "running"
999
+ elif "overrun" in current_status or "out of buffer" in current_status:
1000
+ stream_status = "overrun"
1001
+ elif "stopped" in current_status:
1002
+ stream_status = "stopped"
1003
+ # If the stream is stopped and at end of block, we have read all the data
1004
+ if is_end_of_block:
1005
+ stream_status = stream_status + "eof"
1006
+
1007
+ return stream_status, stripes
1008
+
1009
+ def device_control_index(self, device) -> int:
1010
+ """
1011
+ Returns the index of the device in the control lists. If the device is not
1012
+ registered, then it is added first. This is a key part of allowing us to
1013
+ track the status of multiple streaming devices and manage them from outside
1014
+ their streaming thread
1015
+
1016
+ Args:
1017
+ device:
1018
+ Device ID string
1019
+ Returns:
1020
+ Index of the device in the various control lists
1021
+
1022
+ """
1023
+ if device in self.deviceList:
1024
+ return self.deviceList.index(device)
1025
+ else:
1026
+ self.listSemaphore.acquire()
1027
+ self.deviceList.append(device)
1028
+ self.stopFlagList.append(True)
1029
+ self.listSemaphore.release()
1030
+ return self.deviceList.index(device)
1031
+
1032
+ def device_dict_setup(self, module) -> None:
1033
+ """
1034
+ Adds a dictionary entry for a new module we are connecting to (including the base QIS connection)
1035
+ This is used for tracking the status of modules throughout the streaming process
1036
+
1037
+ Args:
1038
+ module:
1039
+
1040
+ Returns:
1041
+ None
1042
+ """
1043
+ if module in self.deviceDict.keys():
1044
+ return
1045
+ elif module == 'QIS':
1046
+ self.dictSemaphore.acquire()
1047
+ self.deviceDict[module] = [False, 'Disconnected', "No attempt to connect to QIS yet"]
1048
+ self.dictSemaphore.release()
1049
+ else:
1050
+ self.dictSemaphore.acquire()
1051
+ self.deviceDict[module] = [False, 'Stopped', "User hasn't started stream"]
1052
+ self.dictSemaphore.release()
1053
+
1054
+ # Pass in a stream header and we check if it is XML or legacy format
1055
+ def is_xml_header (self, header_text) -> bool:
1056
+ """
1057
+ Checks if the given header string is in XML format (as apposed to legacy text format) or an invalid string
1058
+
1059
+ Args:
1060
+ header_text:
1061
+ The header string to evaluate
1062
+ Returns:
1063
+ True if the header is in XML form
1064
+
1065
+ """
1066
+ if '?xml version=' not in header_text:
1067
+ return False
1068
+ else:
1069
+ return True
1070
+
1071
+ # Internal function. Gets the stream header and parses it into useful information
1072
+ def get_stream_xml_header (self, device, sock=None) -> ET.Element:
1073
+ """
1074
+ Gets the XML format header from an attached device (which must have run or be running a stream)
1075
+ Parses the string into XML and returns the root element
1076
+
1077
+ Args:
1078
+ device:
1079
+ Device ID to return from
1080
+ sock:
1081
+ Optional QIS socket to use for communication.
1082
+
1083
+ Returns:
1084
+
1085
+ """
1086
+ header_data = None
1087
+
1088
+ try:
1089
+ if sock is None:
1090
+ sock = self.sock
1091
+ count = 0
1092
+ while True:
1093
+ if count > 5:
1094
+ break
1095
+ count += 1
1096
+ # Get the raw data
1097
+ header_data = self.send_and_receive_text(sock, send_text='stream text header', device=device)
1098
+
1099
+ # Check for no header (no stream started)
1100
+ if 'Header Not Available' in header_data:
1101
+ logger.error(device + ' Stream header not available.' + self.host + ':' + str(self.port))
1102
+ continue
1103
+
1104
+ # Check for XML format
1105
+ if '?xml version=' not in header_data:
1106
+ logger.error(device + ' Header not in XML form.' + self.host + ':' + str(self.port))
1107
+ continue
1108
+
1109
+ break
1110
+ # Parse XML into a structured format
1111
+ xml_root = ET.fromstring(header_data)
1112
+
1113
+ # Check header format is supported by quarchpy
1114
+ version_str = xml_root.find('.//version').text
1115
+ if 'V3' not in version_str:
1116
+ logger.error(device + ' Stream header version not compatible: ' + xml_root.find('version').text + '.' + self.host + ':' + str(self.port))
1117
+ raise Exception ("Stream header version not supported")
1118
+
1119
+ # Return the XML structure for the code to use
1120
+ return xml_root
1121
+
1122
+ except Exception as e:
1123
+ logger.error(device + ' Exception while parsing stream header XML.' + self.host + ':' + str(self.port))
1124
+ raise e
1125
+
1126
+ def send_command (self, command: str, device: str = '', qis_socket: socket.socket=None, no_cursor_expected: bool=False, no_response_expected: bool=False, command_delay: float=0.0) -> str:
1127
+ """
1128
+ Sends a command and returns the response as a string. Multiple lines are escaped with CRLF.
1129
+ The command is sent to the QIS socket, and depending on the command will be replied by either QIS
1130
+ or the hardware module.
1131
+
1132
+ Args:
1133
+ command:
1134
+ Command string
1135
+ device:
1136
+ Optional Device ID string to send the command to. Use default/blank for QIS direct commands
1137
+ qis_socket:
1138
+ Optional Socket to use for the command, if the default is not wanted
1139
+ no_cursor_expected:
1140
+ Optional Flag true if the command does not return a cursor, so we should not wait for it
1141
+ no_response_expected:
1142
+ Optional Flag true if the command does not return a response, so we should not wait for it.
1143
+ command_delay:
1144
+ Optional delay to prevent commands running in close succession. Timed in seconds.
1145
+ Returns:
1146
+ Command response string or None if no response expected
1147
+ """
1148
+ if qis_socket is None:
1149
+ qis_socket = self.sock
1150
+
1151
+ if no_response_expected:
1152
+ self.send_text(qis_socket, command, device)
1153
+ return ""
1154
+ else:
1155
+ if not (device == ''):
1156
+ self.device_dict_setup(device)
1157
+ res = self.send_and_receive_text(qis_socket, command, device, not no_cursor_expected)
1158
+
1159
+ # This is a poor sleep mechanism! Better would be to track time since the last command
1160
+ if command_delay > 0:
1161
+ time.sleep(command_delay)
1162
+
1163
+ # Trim the expected cursor at the end of the response
1164
+ if res[-3:] == '\r\n>':
1165
+ res = res[:-3] # remove last three chars - '\r\n>'
1166
+ elif res[-2:] == '\n>':
1167
+ res = res[:-2] # remove last 2 chars - '\n>'
1168
+ return res
1169
+
1170
+ def send_and_receive_text(self, sock, send_text='$help', device='', read_until_cursor=True) -> str:
1171
+ """
1172
+ Internal function for command handling. This handles complex cases such as timeouts and XML
1173
+ response formatting, which conflicts with the default cursor
1174
+
1175
+ Args:
1176
+ sock:
1177
+ The socket to communicate over
1178
+ send_text:
1179
+ The command text to send
1180
+ device:
1181
+ Optional device ID to send the command to
1182
+ read_until_cursor:
1183
+ Flag to indicate if we should read until the cursor is returned
1184
+
1185
+ Returns:
1186
+ Response string from the module
1187
+ """
1188
+
1189
+ # Avoid multiple threads trying to send at once. QIS only has a single socket for all devices
1190
+ self.sockSemaphore.acquire()
1191
+ try:
1192
+ # Send the command
1193
+ self.send_text(sock, send_text, device)
1194
+ # Receive Response
1195
+ res = self.receive_text(sock)
1196
+ # If we get no response, log an error and try to flush using a simple stream query command
1197
+ # If that works, we retry our command. In fail cases we raise an exception and abort as
1198
+ # the connection is bad
1199
+ if len(res) == 0:
1200
+ logger.error("Empty response from QIS for cmd: " + send_text + ". To device: " + device)
1201
+ self.send_text(sock, "stream?", device)
1202
+ res = self.receive_text(sock)
1203
+ if len(res) != 0:
1204
+ self.send_text(sock, send_text, device)
1205
+ res = self.receive_text(sock)
1206
+ if len(res) == 0:
1207
+ raise (Exception("Empty response from QIS. Sent: " + send_text))
1208
+ else:
1209
+ raise (Exception("Empty response from QIS. Sent: " + send_text))
1210
+
1211
+ if res[0] == self.cursor:
1212
+ logger.error('Only returned a cursor from QIS. Sent: ' + send_text)
1213
+ raise (Exception("Only returned a cursor from QIS. Sent: " + send_text))
1214
+ if 'Create Socket Fail' == res[0]: # If create socked fail (between QIS and tcp/ip module)
1215
+ logger.error(res[0])
1216
+ raise (Exception("Failed to open QIS to module socked. Sent: " + send_text))
1217
+ if 'Connection Timeout' == res[0]:
1218
+ logger.error(res[0])
1219
+ raise (Exception("Connection timeout from QIS. Sent: " + send_text))
1220
+
1221
+ # If reading until a cursor comes back, then keep reading until a cursor appears or max tries exceeded
1222
+ # Because large XML responses are possible, we need to validate them as complete before looking
1223
+ # for a final cursor
1224
+ if read_until_cursor:
1225
+
1226
+ max_reads = 1000
1227
+ count = 1
1228
+ is_xml = False
1229
+
1230
+ while True:
1231
+
1232
+ # Determine if the response is XML based on its start
1233
+ if count == 1: # Only check this on the first read
1234
+ if res.startswith("<?xml"): # Likely XML if it starts with '<'
1235
+ is_xml = True
1236
+ elif res.startswith("<XmlResponse"):
1237
+ is_xml = True
1238
+
1239
+
1240
+ if is_xml:
1241
+ # Try to parse the XML to check if it's complete
1242
+ try:
1243
+ ET.fromstring(res[:-1]) # If it parses, the response is complete
1244
+ return res[:-1] # Exit the loop, valid XML received
1245
+ except ET.ParseError:
1246
+ pass # Keep reading until XML is complete
1247
+ else:
1248
+ # Handle normal strings
1249
+ if res[-1:] == self.cursor: # If the last character is '>', stop reading
1250
+ break
1251
+
1252
+ # Receive more data
1253
+ res += self.receive_text(sock)
1254
+
1255
+ # Increment count and check for max reads
1256
+ count += 1
1257
+ if count >= max_reads:
1258
+ raise Exception('Count = Error: max reads exceeded before response was complete')
1259
+
1260
+ return res
1261
+
1262
+ except Exception as e:
1263
+ # Something went wrong during send qis cmd
1264
+ logger.error("Error! Unable to retrieve response from QIS. Command: " + send_text)
1265
+ logger.error(e)
1266
+ raise e
1267
+ finally:
1268
+ self.sockSemaphore.release()
1269
+
1270
+ def receive_text(self, sock) -> str:
1271
+ """
1272
+ Received bytes from the socket and converts to a test string
1273
+ Args:
1274
+ sock:
1275
+ Socket to communicate over
1276
+
1277
+ Returns:
1278
+
1279
+ """
1280
+ res = bytearray()
1281
+ res.extend(self.rx_bytes(sock))
1282
+ res = res.decode()
1283
+
1284
+ return res
1285
+
1286
+ def send_text(self, sock, message='$help', device='') -> bool:
1287
+ """
1288
+
1289
+ Args:
1290
+ sock:
1291
+ Socket to communicate over
1292
+ message:
1293
+ text command to send
1294
+ device:
1295
+ Optional device ID to target with the command
1296
+
1297
+ Returns:
1298
+
1299
+ """
1300
+ # Send text to QIS, don't read its response
1301
+ if device != '':
1302
+ message = device + ' ' + message
1303
+
1304
+ conv_mess = message + '\r\n'
1305
+ sock.sendall(conv_mess.encode('utf-8'))
1306
+ return True
1307
+
1308
+ def rx_bytes(self,sock) -> bytes:
1309
+ """
1310
+ Reads an array of bytes from the socket as part of handling a command response
1311
+
1312
+ Args:
1313
+ sock:
1314
+ Socket to communicate over
1315
+ Returns:
1316
+ Bytes read
1317
+
1318
+ """
1319
+
1320
+ max_exceptions=10
1321
+ exceptions=0
1322
+ max_read_repeats=50
1323
+ read_repeats=0
1324
+ timeout_in_seconds = 10
1325
+
1326
+ #Keep trying to read bytes until we get some, unless the number of read repeats or exceptions is exceeded
1327
+ while True:
1328
+ try:
1329
+ # Select.select returns a list of waitable objects which are ready. On Windows, it has to be sockets.
1330
+ # The first argument is a list of objects to wait for reading, second writing, third 'exceptional condition'
1331
+ # We only use the read list and our socket to check if it is readable. if no timeout is specified,
1332
+ # then it blocks until it becomes readable.
1333
+ # TODO: AN: It is very unclear why we try to open a new socket if data is not ready.
1334
+ # This would seem like a hard failure case!
1335
+ ready = select.select([sock], [], [], timeout_in_seconds)
1336
+ if ready[0]:
1337
+ ret = sock.recv(self.maxRxBytes)
1338
+ return ret
1339
+ # If the socket is not ready for read, open a new one
1340
+ else:
1341
+ logger.error("Timeout: No bytes were available to read from QIS")
1342
+ logger.debug("Opening new QIS socket")
1343
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1344
+ sock.connect((self.host, self.port))
1345
+ sock.settimeout(5)
1346
+
1347
+ try:
1348
+ welcome_string = self.sock.recv(self.maxRxBytes).rstrip()
1349
+ welcome_string = 'Connected@' + self.host + ':' + str(self.port) + ' ' + '\n ' + welcome_string
1350
+ logger.debug("Socket opened: " + welcome_string)
1351
+ except Exception as e:
1352
+ logger.error('Timeout: Failed to open new QIS socket and get the welcome message')
1353
+ raise e
1354
+
1355
+ read_repeats=read_repeats+1
1356
+ time.sleep(0.5)
1357
+
1358
+ except Exception as e:
1359
+ raise e
1360
+
1361
+ # If we read no data, its probably an error, but there may be cases where an empty response is valid
1362
+ if read_repeats >= max_read_repeats:
1363
+ logger.error('Max read repeats exceeded')
1364
+ return b''
1365
+
1366
+ def closeConnection(self, sock=None, conString: str=None) -> str:
1367
+ """
1368
+ deprecated:: 2.2.13
1369
+ Use `close_connection` instead.
1370
+ """
1371
+ return self.close_connection (sock=sock, con_string=conString)
1372
+
1373
+ def startStream(self, module: str, fileName: str, fileMaxMB: int, releaseOnData: bool, separator: str,
1374
+ streamDuration: int = None, inMemoryData=None, outputFileHandle=None, useGzip: bool = None,
1375
+ gzipCompressLevel: int = 9):
1376
+ """
1377
+ deprecated:: 2.2.13
1378
+ Use `start_stream` instead.
1379
+ """
1380
+ return self.start_stream(module, fileName, fileMaxMB, releaseOnData, separator, streamDuration, inMemoryData,
1381
+ outputFileHandle, useGzip, gzipCompressLevel)
1382
+
1383
+ def stopStream(self, module, blocking=True):
1384
+ """
1385
+ deprecated:: 2.2.13
1386
+ Use `stop_stream` instead.
1387
+ """
1388
+ return self.stop_stream(module, blocking)
1389
+
1390
+ def getDeviceList(self, sock=None):
1391
+ """
1392
+ deprecated:: 2.2.13
1393
+ Use `start_stream_thread_qps` instead.
1394
+ """
1395
+ return self.get_device_list(sock)
1396
+
1397
+ def scanIP(self, QisConnection, ipAddress):
1398
+ """
1399
+ deprecated:: 2.2.13
1400
+ Use `scan_ip` instead.
1401
+ """
1402
+ return self.scan_ip(QisConnection, ipAddress)
1403
+
1404
+ def GetQisModuleSelection(self, favouriteOnly=True, additionalOptions=['rescan', 'all con types', 'ip scan'],
1405
+ scan=True):
1406
+ """
1407
+ deprecated:: 2.2.13
1408
+ Use `get_qis_module_selection` instead.
1409
+ """
1410
+ return self.get_qis_module_selection(favouriteOnly, additionalOptions, scan)
1411
+
1412
+ def sendCommand(self, cmd, device="", timeout=20,sock=None,readUntilCursor=True, betweenCommandDelay=0.0, expectedResponse=True) -> str:
1413
+ """
1414
+ deprecated:: 2.2.13
1415
+ Use `send_command` instead.
1416
+ """
1417
+ return self.send_command(cmd, device, sock, False, not expectedResponse, betweenCommandDelay)
1418
+
1419
+ def sendCmd(self, device='', cmd='$help', sock=None, readUntilCursor=True, betweenCommandDelay=0.0, expectedResponse = True) -> str:
1420
+ """
1421
+ deprecated:: 2.2.13
1422
+ Use `send_command` instead.
1423
+ """
1424
+ return self.send_command(cmd, device, sock, not readUntilCursor, not expectedResponse, betweenCommandDelay)
1425
+
1426
+ def sendAndReceiveCmd(self, sock=None, cmd='$help', device='', readUntilCursor=True, betweenCommandDelay=0.0) -> str:
1427
+ """
1428
+ deprecated:: 2.2.13
1429
+ Use `send_command` instead.
1430
+ """
1431
+ return self.send_command(cmd, device, sock, not readUntilCursor, no_response_expected=False, command_delay=betweenCommandDelay)
1432
+
1433
+ def streamRunningStatus(self, device: str) -> str:
1434
+ """
1435
+ deprecated:: 2.2.13
1436
+ Use `stream_running_status` instead.
1437
+ """
1438
+ return self.stream_running_status(device)
1439
+
1440
+ def streamBufferStatus(self, device: str) -> str:
1441
+ """
1442
+ deprecated:: 2.2.13
1443
+ Use `stream_buffer_status` instead.
1444
+ """
1445
+ return self.stream_buffer_status(device)
1446
+
1447
+ def streamHeaderFormat(self, device, sock=None) -> str:
1448
+ """
1449
+ deprecated:: 2.2.13
1450
+ Use `stream_header_format` instead.
1451
+ """
1452
+ return self.stream_header_format(device, sock)
1453
+
1454
+ def streamInterrupt(self) -> bool:
1455
+ """
1456
+ deprecated:: 2.2.13
1457
+ No indication this is used anywhere
1458
+ """
1459
+ for key in self.deviceDict.keys():
1460
+ if self.deviceDict[key][0]:
1461
+ return True
1462
+ return False
1463
+
1464
+ def interruptList(self):
1465
+ """
1466
+ deprecated:: 2.2.13
1467
+ No indication this is used anywhere
1468
+ """
1469
+ streamIssueList = []
1470
+ for key in self.deviceDict.keys():
1471
+ if self.deviceDict[key][0]:
1472
+ streamIssue = [key]
1473
+ streamIssue.append(self.deviceDict[key][1])
1474
+ streamIssue.append(self.deviceDict[key][2])
1475
+ streamIssueList.append(streamIssue)
1476
+ return streamIssueList
1477
+
1478
+ def waitStop(self):
1479
+ """
1480
+ deprecated:: 2.2.13
1481
+ No indication this is used anywhere
1482
+ """
1483
+ running = 1
1484
+ while running != 0:
1485
+ threadNameList = []
1486
+ for t1 in threading.enumerate():
1487
+ threadNameList.append(t1.name)
1488
+ running = 0
1489
+ for module in self.deviceList:
1490
+ if (module in threadNameList):
1491
+ running += 1
1492
+ time.sleep(0.5)
1493
+ time.sleep(1)
1494
+
1495
+ def convertStreamAverage (self, streamAveraging):
1496
+ """
1497
+ deprecated:: 2.2.13
1498
+ No indication this is used anywhere
1499
+ """
1500
+ returnValue = 32000
1501
+ if ("k" in streamAveraging):
1502
+ returnValue = streamAveraging.replace("k", "000")
1503
+ else:
1504
+ returnValue = streamAveraging
1505
+
1506
+ return returnValue
1507
+
1508
+ def sendAndReceiveText(self, sock, sendText='$help', device='', readUntilCursor=True) -> str:
1509
+ """
1510
+ deprecated:: 2.2.13
1511
+ Use `send_and_receive_text` instead.
1512
+ """
1513
+ return self.send_and_receive_text(sock, send_text=sendText, device=device, read_until_cursor=readUntilCursor)