cgse 2024.7.0__py3-none-any.whl → 2025.0.1__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 (664) hide show
  1. README.md +27 -0
  2. bump.py +85 -0
  3. cgse-2025.0.1.dist-info/METADATA +38 -0
  4. cgse-2025.0.1.dist-info/RECORD +5 -0
  5. {cgse-2024.7.0.dist-info → cgse-2025.0.1.dist-info}/WHEEL +1 -2
  6. cgse-2024.7.0.dist-info/COPYING +0 -674
  7. cgse-2024.7.0.dist-info/COPYING.LESSER +0 -165
  8. cgse-2024.7.0.dist-info/METADATA +0 -144
  9. cgse-2024.7.0.dist-info/RECORD +0 -660
  10. cgse-2024.7.0.dist-info/entry_points.txt +0 -75
  11. cgse-2024.7.0.dist-info/top_level.txt +0 -2
  12. egse/__init__.py +0 -12
  13. egse/__main__.py +0 -32
  14. egse/aeu/aeu.py +0 -5238
  15. egse/aeu/aeu_awg.yaml +0 -265
  16. egse/aeu/aeu_crio.yaml +0 -273
  17. egse/aeu/aeu_cs.py +0 -627
  18. egse/aeu/aeu_devif.py +0 -321
  19. egse/aeu/aeu_main_ui.py +0 -903
  20. egse/aeu/aeu_metrics.py +0 -131
  21. egse/aeu/aeu_protocol.py +0 -463
  22. egse/aeu/aeu_psu.yaml +0 -204
  23. egse/aeu/aeu_ui.py +0 -873
  24. egse/aeu/arbdata/FccdRead.arb +0 -2
  25. egse/aeu/arbdata/FccdRead_min_points.arb +0 -2
  26. egse/aeu/arbdata/HeaterSync_FccdRead.arb +0 -2
  27. egse/aeu/arbdata/HeaterSync_ccdRead25.arb +0 -2
  28. egse/aeu/arbdata/HeaterSync_ccdRead31_25.arb +0 -2
  29. egse/aeu/arbdata/HeaterSync_ccdRead37_50.arb +0 -2
  30. egse/aeu/arbdata/HeaterSync_ccdRead43_75.arb +0 -2
  31. egse/aeu/arbdata/HeaterSync_ccdRead50.arb +0 -2
  32. egse/aeu/arbdata/Heater_FccdRead_min_points.arb +0 -2
  33. egse/aeu/arbdata/ccdRead25.arb +0 -2
  34. egse/aeu/arbdata/ccdRead25_150ms.arb +0 -2
  35. egse/aeu/arbdata/ccdRead31_25.arb +0 -2
  36. egse/aeu/arbdata/ccdRead31_25_150ms.arb +0 -2
  37. egse/aeu/arbdata/ccdRead37_50.arb +0 -2
  38. egse/aeu/arbdata/ccdRead37_50_150ms.arb +0 -2
  39. egse/aeu/arbdata/ccdRead43_75.arb +0 -2
  40. egse/aeu/arbdata/ccdRead43_75_150ms.arb +0 -2
  41. egse/aeu/arbdata/ccdRead50.arb +0 -2
  42. egse/aeu/arbdata/ccdRead50_150ms.arb +0 -2
  43. egse/alert/__init__.py +0 -1049
  44. egse/alert/alertman.yaml +0 -37
  45. egse/alert/alertman_cs.py +0 -233
  46. egse/alert/alertman_ui.py +0 -600
  47. egse/alert/gsm/beaglebone.py +0 -138
  48. egse/alert/gsm/beaglebone.yaml +0 -51
  49. egse/alert/gsm/beaglebone_cs.py +0 -108
  50. egse/alert/gsm/beaglebone_devif.py +0 -122
  51. egse/alert/gsm/beaglebone_protocol.py +0 -46
  52. egse/bits.py +0 -318
  53. egse/camera.py +0 -44
  54. egse/collimator/__init__.py +0 -0
  55. egse/collimator/fcul/__init__.py +0 -0
  56. egse/collimator/fcul/ogse.py +0 -1077
  57. egse/collimator/fcul/ogse.yaml +0 -14
  58. egse/collimator/fcul/ogse_cs.py +0 -154
  59. egse/collimator/fcul/ogse_devif.py +0 -358
  60. egse/collimator/fcul/ogse_protocol.py +0 -132
  61. egse/collimator/fcul/ogse_sim.py +0 -431
  62. egse/collimator/fcul/ogse_ui.py +0 -1108
  63. egse/command.py +0 -699
  64. egse/config.py +0 -410
  65. egse/confman/__init__.py +0 -1058
  66. egse/confman/confman.yaml +0 -70
  67. egse/confman/confman_cs.py +0 -240
  68. egse/confman/confman_ui.py +0 -381
  69. egse/confman/setup_ui.py +0 -565
  70. egse/control.py +0 -632
  71. egse/coordinates/__init__.py +0 -534
  72. egse/coordinates/avoidance.py +0 -100
  73. egse/coordinates/cslmodel.py +0 -127
  74. egse/coordinates/laser_tracker_to_dict.py +0 -122
  75. egse/coordinates/point.py +0 -707
  76. egse/coordinates/pyplot.py +0 -194
  77. egse/coordinates/referenceFrame.py +0 -1279
  78. egse/coordinates/refmodel.py +0 -737
  79. egse/coordinates/rotationMatrix.py +0 -85
  80. egse/coordinates/transform3d_addon.py +0 -419
  81. egse/csl/__init__.py +0 -50
  82. egse/csl/commanding.py +0 -78
  83. egse/csl/icons/hexapod-connected-selected.svg +0 -30
  84. egse/csl/icons/hexapod-connected.svg +0 -30
  85. egse/csl/icons/hexapod-homing-selected.svg +0 -68
  86. egse/csl/icons/hexapod-homing.svg +0 -68
  87. egse/csl/icons/hexapod-retract-selected.svg +0 -56
  88. egse/csl/icons/hexapod-retract.svg +0 -51
  89. egse/csl/icons/hexapod-zero-selected.svg +0 -56
  90. egse/csl/icons/hexapod-zero.svg +0 -56
  91. egse/csl/icons/logo-puna.svg +0 -92
  92. egse/csl/icons/stop.svg +0 -1
  93. egse/csl/initialisation.py +0 -102
  94. egse/csl/mech_pos_settings.yaml +0 -18
  95. egse/das.py +0 -1240
  96. egse/das.yaml +0 -7
  97. egse/data/conf/SETUP_CSL_00000_170620_150000.yaml +0 -5
  98. egse/data/conf/SETUP_CSL_00001_170620_151010.yaml +0 -69
  99. egse/data/conf/SETUP_CSL_00002_170620_151020.yaml +0 -69
  100. egse/data/conf/SETUP_CSL_00003_170620_151030.yaml +0 -69
  101. egse/data/conf/SETUP_CSL_00004_170620_151040.yaml +0 -69
  102. egse/data/conf/SETUP_CSL_00005_170620_151050.yaml +0 -69
  103. egse/data/conf/SETUP_CSL_00006_170620_151060.yaml +0 -69
  104. egse/data/conf/SETUP_CSL_00007_170620_151070.yaml +0 -69
  105. egse/data/conf/SETUP_CSL_00008_170620_151080.yaml +0 -75
  106. egse/data/conf/SETUP_CSL_00010_210308_083016.yaml +0 -138
  107. egse/data/conf/SETUP_INTA_00000_170620_150000.yaml +0 -4
  108. egse/data/conf/SETUP_SRON_00000_170620_150000.yaml +0 -4
  109. egse/decorators.py +0 -514
  110. egse/device.py +0 -269
  111. egse/dpu/__init__.py +0 -2698
  112. egse/dpu/ccd_ui.py +0 -514
  113. egse/dpu/dpu.py +0 -783
  114. egse/dpu/dpu.yaml +0 -153
  115. egse/dpu/dpu_cs.py +0 -272
  116. egse/dpu/dpu_ui.py +0 -671
  117. egse/dpu/fitsgen.py +0 -2096
  118. egse/dpu/fitsgen_ui.py +0 -399
  119. egse/dpu/hdf5_model.py +0 -332
  120. egse/dpu/hdf5_ui.py +0 -277
  121. egse/dpu/hdf5_viewer.py +0 -506
  122. egse/dpu/hk_ui.py +0 -468
  123. egse/dpu_commands.py +0 -81
  124. egse/dsi/__init__.py +0 -33
  125. egse/dsi/_libesl.py +0 -232
  126. egse/dsi/constants.py +0 -296
  127. egse/dsi/esl.py +0 -630
  128. egse/dsi/rmap.py +0 -444
  129. egse/dsi/rmapci.py +0 -39
  130. egse/dsi/spw.py +0 -335
  131. egse/dsi/spw_state.py +0 -29
  132. egse/dummy.py +0 -318
  133. egse/dyndummy.py +0 -179
  134. egse/env.py +0 -278
  135. egse/exceptions.py +0 -88
  136. egse/fdir/__init__.py +0 -26
  137. egse/fdir/fdir_manager.py +0 -85
  138. egse/fdir/fdir_manager.yaml +0 -37
  139. egse/fdir/fdir_manager_controller.py +0 -136
  140. egse/fdir/fdir_manager_cs.py +0 -164
  141. egse/fdir/fdir_manager_interface.py +0 -15
  142. egse/fdir/fdir_remote.py +0 -73
  143. egse/fdir/fdir_remote.yaml +0 -30
  144. egse/fdir/fdir_remote_controller.py +0 -30
  145. egse/fdir/fdir_remote_cs.py +0 -94
  146. egse/fdir/fdir_remote_interface.py +0 -9
  147. egse/fdir/fdir_remote_popup.py +0 -26
  148. egse/fee/__init__.py +0 -106
  149. egse/fee/f_fee_register.yaml +0 -43
  150. egse/fee/feesim.py +0 -914
  151. egse/fee/n_fee_hk.py +0 -768
  152. egse/fee/nfee.py +0 -188
  153. egse/filterwheel/__init__.py +0 -4
  154. egse/filterwheel/eksma/__init__.py +0 -49
  155. egse/filterwheel/eksma/fw8smc4.py +0 -657
  156. egse/filterwheel/eksma/fw8smc4.yaml +0 -121
  157. egse/filterwheel/eksma/fw8smc4_cs.py +0 -144
  158. egse/filterwheel/eksma/fw8smc4_devif.py +0 -473
  159. egse/filterwheel/eksma/fw8smc4_protocol.py +0 -82
  160. egse/filterwheel/eksma/fw8smc4_ui.py +0 -940
  161. egse/filterwheel/eksma/fw8smc5.py +0 -115
  162. egse/filterwheel/eksma/fw8smc5.yaml +0 -105
  163. egse/filterwheel/eksma/fw8smc5_controller.py +0 -307
  164. egse/filterwheel/eksma/fw8smc5_cs.py +0 -141
  165. egse/filterwheel/eksma/fw8smc5_interface.py +0 -65
  166. egse/filterwheel/eksma/fw8smc5_simulator.py +0 -29
  167. egse/filterwheel/eksma/fw8smc5_ui.py +0 -1065
  168. egse/filterwheel/eksma/testpythonfw.py +0 -215
  169. egse/fov/__init__.py +0 -65
  170. egse/fov/fov_hk.py +0 -710
  171. egse/fov/fov_ui.py +0 -859
  172. egse/fov/fov_ui_controller.py +0 -140
  173. egse/fov/fov_ui_model.py +0 -200
  174. egse/fov/fov_ui_view.py +0 -345
  175. egse/gimbal/__init__.py +0 -32
  176. egse/gimbal/symetrie/__init__.py +0 -26
  177. egse/gimbal/symetrie/alpha.py +0 -586
  178. egse/gimbal/symetrie/generic_gimbal_ui.py +0 -1521
  179. egse/gimbal/symetrie/gimbal.py +0 -877
  180. egse/gimbal/symetrie/gimbal.yaml +0 -168
  181. egse/gimbal/symetrie/gimbal_cs.py +0 -183
  182. egse/gimbal/symetrie/gimbal_protocol.py +0 -138
  183. egse/gimbal/symetrie/gimbal_ui.py +0 -361
  184. egse/gimbal/symetrie/pmac.py +0 -1006
  185. egse/gimbal/symetrie/pmac_regex.py +0 -83
  186. egse/graph.py +0 -132
  187. egse/gui/__init__.py +0 -47
  188. egse/gui/buttons.py +0 -378
  189. egse/gui/focalplane.py +0 -1285
  190. egse/gui/formatter.py +0 -10
  191. egse/gui/led.py +0 -162
  192. egse/gui/limitswitch.py +0 -143
  193. egse/gui/mechanisms.py +0 -587
  194. egse/gui/states.py +0 -148
  195. egse/gui/stripchart.py +0 -729
  196. egse/gui/styles.qss +0 -48
  197. egse/gui/switch.py +0 -112
  198. egse/h5.py +0 -274
  199. egse/help/__init__.py +0 -0
  200. egse/help/help_ui.py +0 -126
  201. egse/hexapod/__init__.py +0 -32
  202. egse/hexapod/symetrie/__init__.py +0 -137
  203. egse/hexapod/symetrie/alpha.py +0 -874
  204. egse/hexapod/symetrie/dynalpha.py +0 -1387
  205. egse/hexapod/symetrie/hexapod_ui.py +0 -1516
  206. egse/hexapod/symetrie/pmac.py +0 -1010
  207. egse/hexapod/symetrie/pmac_regex.py +0 -83
  208. egse/hexapod/symetrie/puna.py +0 -1167
  209. egse/hexapod/symetrie/puna.yaml +0 -193
  210. egse/hexapod/symetrie/puna_cs.py +0 -195
  211. egse/hexapod/symetrie/puna_protocol.py +0 -134
  212. egse/hexapod/symetrie/puna_ui.py +0 -433
  213. egse/hexapod/symetrie/punaplus.py +0 -107
  214. egse/hexapod/symetrie/zonda.py +0 -872
  215. egse/hexapod/symetrie/zonda.yaml +0 -337
  216. egse/hexapod/symetrie/zonda_cs.py +0 -172
  217. egse/hexapod/symetrie/zonda_devif.py +0 -414
  218. egse/hexapod/symetrie/zonda_protocol.py +0 -123
  219. egse/hexapod/symetrie/zonda_ui.py +0 -449
  220. egse/hk.py +0 -791
  221. egse/icons/aeu-cs-start.svg +0 -117
  222. egse/icons/aeu-cs-stop.svg +0 -118
  223. egse/icons/aeu-cs.svg +0 -107
  224. egse/icons/aeu_cs-started.svg +0 -112
  225. egse/icons/aeu_cs-stopped.svg +0 -112
  226. egse/icons/aeu_cs.svg +0 -55
  227. egse/icons/alert.svg +0 -1
  228. egse/icons/arrow-double-left.png +0 -0
  229. egse/icons/arrow-double-right.png +0 -0
  230. egse/icons/arrow-up.svg +0 -11
  231. egse/icons/backward.svg +0 -1
  232. egse/icons/busy.svg +0 -1
  233. egse/icons/cleaning.svg +0 -115
  234. egse/icons/color-scheme.svg +0 -1
  235. egse/icons/cs-connected-alert.svg +0 -91
  236. egse/icons/cs-connected-disabled.svg +0 -43
  237. egse/icons/cs-connected.svg +0 -89
  238. egse/icons/cs-not-connected.svg +0 -44
  239. egse/icons/double-left-arrow.svg +0 -1
  240. egse/icons/double-right-arrow.svg +0 -1
  241. egse/icons/erase-disabled.svg +0 -19
  242. egse/icons/erase.svg +0 -59
  243. egse/icons/fitsgen-start.svg +0 -47
  244. egse/icons/fitsgen-stop.svg +0 -48
  245. egse/icons/fitsgen.svg +0 -1
  246. egse/icons/forward.svg +0 -1
  247. egse/icons/fov-hk-start.svg +0 -33
  248. egse/icons/fov-hk-stop.svg +0 -37
  249. egse/icons/fov-hk.svg +0 -1
  250. egse/icons/front-desk.svg +0 -1
  251. egse/icons/home-actioned.svg +0 -15
  252. egse/icons/home-disabled.svg +0 -15
  253. egse/icons/home.svg +0 -13
  254. egse/icons/info.svg +0 -1
  255. egse/icons/invalid.png +0 -0
  256. egse/icons/led-green.svg +0 -20
  257. egse/icons/led-grey.svg +0 -20
  258. egse/icons/led-orange.svg +0 -20
  259. egse/icons/led-red.svg +0 -20
  260. egse/icons/led-square-green.svg +0 -134
  261. egse/icons/led-square-grey.svg +0 -134
  262. egse/icons/led-square-orange.svg +0 -134
  263. egse/icons/led-square-red.svg +0 -134
  264. egse/icons/limit-switch-all-green.svg +0 -115
  265. egse/icons/limit-switch-all-red.svg +0 -117
  266. egse/icons/limit-switch-el+.svg +0 -116
  267. egse/icons/limit-switch-el-.svg +0 -117
  268. egse/icons/location-marker.svg +0 -1
  269. egse/icons/logo-dpu.svg +0 -48
  270. egse/icons/logo-gimbal.svg +0 -112
  271. egse/icons/logo-huber.svg +0 -23
  272. egse/icons/logo-ogse.svg +0 -31
  273. egse/icons/logo-puna.svg +0 -92
  274. egse/icons/logo-tcs.svg +0 -29
  275. egse/icons/logo-zonda.svg +0 -66
  276. egse/icons/maximize.svg +0 -1
  277. egse/icons/meter.svg +0 -1
  278. egse/icons/more.svg +0 -45
  279. egse/icons/n-fee-hk-start.svg +0 -24
  280. egse/icons/n-fee-hk-stop.svg +0 -25
  281. egse/icons/n-fee-hk.svg +0 -83
  282. egse/icons/observing-off.svg +0 -46
  283. egse/icons/observing-on.svg +0 -46
  284. egse/icons/open-document-hdf5.png +0 -0
  285. egse/icons/open-document-hdf5.svg +0 -21
  286. egse/icons/ops-mode.svg +0 -1
  287. egse/icons/play-green.svg +0 -17
  288. egse/icons/plugged-disabled.svg +0 -27
  289. egse/icons/plugged.svg +0 -21
  290. egse/icons/pm_ui.svg +0 -1
  291. egse/icons/power-button-green.svg +0 -27
  292. egse/icons/power-button-red.svg +0 -27
  293. egse/icons/power-button.svg +0 -27
  294. egse/icons/radar.svg +0 -1
  295. egse/icons/radioactive.svg +0 -2
  296. egse/icons/reload.svg +0 -1
  297. egse/icons/remote-control-off.svg +0 -28
  298. egse/icons/remote-control-on.svg +0 -28
  299. egse/icons/repeat-blue.svg +0 -15
  300. egse/icons/repeat.svg +0 -1
  301. egse/icons/settings.svg +0 -1
  302. egse/icons/shrink.svg +0 -1
  303. egse/icons/shutter.svg +0 -1
  304. egse/icons/sign-off.svg +0 -1
  305. egse/icons/sign-on.svg +0 -1
  306. egse/icons/sim-mode.svg +0 -1
  307. egse/icons/small-buttons-go.svg +0 -20
  308. egse/icons/small-buttons-minus.svg +0 -51
  309. egse/icons/small-buttons-plus.svg +0 -51
  310. egse/icons/sponge.svg +0 -220
  311. egse/icons/start-button-disabled.svg +0 -84
  312. egse/icons/start-button.svg +0 -50
  313. egse/icons/stop-button-disabled.svg +0 -84
  314. egse/icons/stop-button.svg +0 -50
  315. egse/icons/stop-red.svg +0 -17
  316. egse/icons/stop.svg +0 -1
  317. egse/icons/switch-disabled-square.svg +0 -87
  318. egse/icons/switch-disabled.svg +0 -15
  319. egse/icons/switch-off-square.svg +0 -87
  320. egse/icons/switch-off.svg +0 -72
  321. egse/icons/switch-on-square.svg +0 -87
  322. egse/icons/switch-on.svg +0 -61
  323. egse/icons/temperature-control.svg +0 -44
  324. egse/icons/th_ui_logo.svg +0 -1
  325. egse/icons/unplugged.svg +0 -23
  326. egse/icons/unvalid.png +0 -0
  327. egse/icons/user-interface.svg +0 -1
  328. egse/icons/vacuum.svg +0 -1
  329. egse/icons/valid.png +0 -0
  330. egse/icons/zoom-to-pixel-dark.svg +0 -64
  331. egse/icons/zoom-to-pixel-white.svg +0 -36
  332. egse/images/big-rotation-stage.png +0 -0
  333. egse/images/connected-100.png +0 -0
  334. egse/images/cross.svg +0 -6
  335. egse/images/disconnected-100.png +0 -0
  336. egse/images/gui-icon.png +0 -0
  337. egse/images/home.svg +0 -6
  338. egse/images/info-icon.png +0 -0
  339. egse/images/led-black.svg +0 -89
  340. egse/images/led-green.svg +0 -85
  341. egse/images/led-orange.svg +0 -85
  342. egse/images/led-red.svg +0 -85
  343. egse/images/load-icon.png +0 -0
  344. egse/images/load-setup.png +0 -0
  345. egse/images/load.png +0 -0
  346. egse/images/pause.png +0 -0
  347. egse/images/play-button.svg +0 -8
  348. egse/images/play.png +0 -0
  349. egse/images/process-status.png +0 -0
  350. egse/images/restart.png +0 -0
  351. egse/images/search.png +0 -0
  352. egse/images/sma.png +0 -0
  353. egse/images/start.png +0 -0
  354. egse/images/stop-button.svg +0 -8
  355. egse/images/stop.png +0 -0
  356. egse/images/switch-off.svg +0 -48
  357. egse/images/switch-on.svg +0 -48
  358. egse/images/undo.png +0 -0
  359. egse/images/update-button.svg +0 -11
  360. egse/imageviewer/exposureselection.py +0 -475
  361. egse/imageviewer/imageviewer.py +0 -198
  362. egse/imageviewer/matchfocalplane.py +0 -179
  363. egse/imageviewer/subfieldposition.py +0 -133
  364. egse/lampcontrol/__init__.py +0 -4
  365. egse/lampcontrol/beaglebone/beaglebone.py +0 -178
  366. egse/lampcontrol/beaglebone/beaglebone.yaml +0 -62
  367. egse/lampcontrol/beaglebone/beaglebone_cs.py +0 -106
  368. egse/lampcontrol/beaglebone/beaglebone_devif.py +0 -150
  369. egse/lampcontrol/beaglebone/beaglebone_protocol.py +0 -73
  370. egse/lampcontrol/energetiq/__init__.py +0 -22
  371. egse/lampcontrol/energetiq/eq99.yaml +0 -98
  372. egse/lampcontrol/energetiq/lampEQ99.py +0 -283
  373. egse/lampcontrol/energetiq/lampEQ99_cs.py +0 -128
  374. egse/lampcontrol/energetiq/lampEQ99_devif.py +0 -158
  375. egse/lampcontrol/energetiq/lampEQ99_encode_decode_errors.py +0 -73
  376. egse/lampcontrol/energetiq/lampEQ99_protocol.py +0 -71
  377. egse/lampcontrol/energetiq/lampEQ99_ui.py +0 -465
  378. egse/lib/CentOS-7/EtherSpaceLink_v34_86.dylib +0 -0
  379. egse/lib/CentOS-8/ESL-RMAP_v34_86.dylib +0 -0
  380. egse/lib/CentOS-8/EtherSpaceLink_v34_86.dylib +0 -0
  381. egse/lib/Debian/ESL-RMAP_v34_86.dylib +0 -0
  382. egse/lib/Debian/EtherSpaceLink_v34_86.dylib +0 -0
  383. egse/lib/Debian/libetherspacelink_v35_21.dylib +0 -0
  384. egse/lib/Linux/ESL-RMAP_v34_86.dylib +0 -0
  385. egse/lib/Linux/EtherSpaceLink_v34_86.dylib +0 -0
  386. egse/lib/Ubuntu-20/ESL-RMAP_v34_86.dylib +0 -0
  387. egse/lib/Ubuntu-20/EtherSpaceLink_v34_86.dylib +0 -0
  388. egse/lib/gssw/python3-gssw_2.2.3+31f63c9f-1_all.deb +0 -0
  389. egse/lib/ximc/__pycache__/pyximc.cpython-38 2.pyc +0 -0
  390. egse/lib/ximc/__pycache__/pyximc.cpython-38.pyc +0 -0
  391. egse/lib/ximc/libximc.framework/Frameworks/libbindy.dylib +0 -0
  392. egse/lib/ximc/libximc.framework/Frameworks/libxiwrapper.dylib +0 -0
  393. egse/lib/ximc/libximc.framework/Headers/ximc.h +0 -5510
  394. egse/lib/ximc/libximc.framework/Resources/Info.plist +0 -42
  395. egse/lib/ximc/libximc.framework/Resources/keyfile.sqlite +0 -0
  396. egse/lib/ximc/libximc.framework/libbindy.so +0 -0
  397. egse/lib/ximc/libximc.framework/libximc +0 -0
  398. egse/lib/ximc/libximc.framework/libximc.so +0 -0
  399. egse/lib/ximc/libximc.framework/libximc.so.7.0.0 +0 -0
  400. egse/lib/ximc/libximc.framework/libxiwrapper.so +0 -0
  401. egse/lib/ximc/pyximc.py +0 -922
  402. egse/listener.py +0 -179
  403. egse/logger/__init__.py +0 -243
  404. egse/logger/log_cs.py +0 -321
  405. egse/metrics.py +0 -102
  406. egse/mixin.py +0 -464
  407. egse/monitoring.py +0 -95
  408. egse/ni/alarms/__init__.py +0 -26
  409. egse/ni/alarms/cdaq9375.py +0 -300
  410. egse/ni/alarms/cdaq9375.yaml +0 -89
  411. egse/ni/alarms/cdaq9375_cs.py +0 -130
  412. egse/ni/alarms/cdaq9375_devif.py +0 -183
  413. egse/ni/alarms/cdaq9375_protocol.py +0 -48
  414. egse/obs_inspection.py +0 -165
  415. egse/observer.py +0 -41
  416. egse/obsid.py +0 -163
  417. egse/powermeter/__init__.py +0 -0
  418. egse/powermeter/ni/__init__.py +0 -38
  419. egse/powermeter/ni/cdaq9184.py +0 -224
  420. egse/powermeter/ni/cdaq9184.yaml +0 -73
  421. egse/powermeter/ni/cdaq9184_cs.py +0 -130
  422. egse/powermeter/ni/cdaq9184_devif.py +0 -201
  423. egse/powermeter/ni/cdaq9184_protocol.py +0 -48
  424. egse/powermeter/ni/cdaq9184_ui.py +0 -544
  425. egse/powermeter/thorlabs/__init__.py +0 -25
  426. egse/powermeter/thorlabs/pm100a.py +0 -380
  427. egse/powermeter/thorlabs/pm100a.yaml +0 -132
  428. egse/powermeter/thorlabs/pm100a_cs.py +0 -136
  429. egse/powermeter/thorlabs/pm100a_devif.py +0 -127
  430. egse/powermeter/thorlabs/pm100a_protocol.py +0 -80
  431. egse/powermeter/thorlabs/pm100a_ui.py +0 -725
  432. egse/process.py +0 -451
  433. egse/procman/__init__.py +0 -834
  434. egse/procman/cannot_start_process_popup.py +0 -43
  435. egse/procman/procman.yaml +0 -49
  436. egse/procman/procman_cs.py +0 -201
  437. egse/procman/procman_ui.py +0 -2081
  438. egse/protocol.py +0 -605
  439. egse/proxy.py +0 -531
  440. egse/randomwalk.py +0 -140
  441. egse/reg.py +0 -585
  442. egse/reload.py +0 -122
  443. egse/reprocess.py +0 -693
  444. egse/resource.py +0 -333
  445. egse/rmap.py +0 -406
  446. egse/rst.py +0 -135
  447. egse/search.py +0 -182
  448. egse/serialdevice.py +0 -190
  449. egse/services.py +0 -247
  450. egse/services.yaml +0 -68
  451. egse/settings.py +0 -379
  452. egse/settings.yaml +0 -980
  453. egse/setup.py +0 -1181
  454. egse/shutter/__init__.py +0 -0
  455. egse/shutter/thorlabs/__init__.py +0 -19
  456. egse/shutter/thorlabs/ksc101.py +0 -205
  457. egse/shutter/thorlabs/ksc101.yaml +0 -105
  458. egse/shutter/thorlabs/ksc101_cs.py +0 -136
  459. egse/shutter/thorlabs/ksc101_devif.py +0 -201
  460. egse/shutter/thorlabs/ksc101_protocol.py +0 -71
  461. egse/shutter/thorlabs/ksc101_ui.py +0 -548
  462. egse/shutter/thorlabs/sc10.py +0 -82
  463. egse/shutter/thorlabs/sc10.yaml +0 -52
  464. egse/shutter/thorlabs/sc10_controller.py +0 -81
  465. egse/shutter/thorlabs/sc10_cs.py +0 -108
  466. egse/shutter/thorlabs/sc10_interface.py +0 -25
  467. egse/shutter/thorlabs/sc10_simulator.py +0 -30
  468. egse/simulator.py +0 -41
  469. egse/slack.py +0 -61
  470. egse/socketdevice.py +0 -218
  471. egse/sockets.py +0 -218
  472. egse/spw.py +0 -1401
  473. egse/stages/__init__.py +0 -12
  474. egse/stages/aerotech/ensemble.py +0 -245
  475. egse/stages/aerotech/ensemble.yaml +0 -205
  476. egse/stages/aerotech/ensemble_controller.py +0 -275
  477. egse/stages/aerotech/ensemble_cs.py +0 -110
  478. egse/stages/aerotech/ensemble_interface.py +0 -132
  479. egse/stages/aerotech/ensemble_parameters.py +0 -433
  480. egse/stages/aerotech/ensemble_simulator.py +0 -27
  481. egse/stages/aerotech/mgse_sim.py +0 -188
  482. egse/stages/arun/smd3.py +0 -110
  483. egse/stages/arun/smd3.yaml +0 -68
  484. egse/stages/arun/smd3_controller.py +0 -470
  485. egse/stages/arun/smd3_cs.py +0 -112
  486. egse/stages/arun/smd3_interface.py +0 -53
  487. egse/stages/arun/smd3_simulator.py +0 -27
  488. egse/stages/arun/smd3_stop.py +0 -16
  489. egse/stages/huber/__init__.py +0 -49
  490. egse/stages/huber/smc9300.py +0 -920
  491. egse/stages/huber/smc9300.yaml +0 -63
  492. egse/stages/huber/smc9300_cs.py +0 -178
  493. egse/stages/huber/smc9300_devif.py +0 -345
  494. egse/stages/huber/smc9300_protocol.py +0 -113
  495. egse/stages/huber/smc9300_sim.py +0 -547
  496. egse/stages/huber/smc9300_ui.py +0 -973
  497. egse/state.py +0 -173
  498. egse/statemachine.py +0 -274
  499. egse/storage/__init__.py +0 -1067
  500. egse/storage/persistence.py +0 -2295
  501. egse/storage/storage.yaml +0 -79
  502. egse/storage/storage_cs.py +0 -231
  503. egse/styles/dark.qss +0 -343
  504. egse/styles/default.qss +0 -48
  505. egse/synoptics/__init__.py +0 -417
  506. egse/synoptics/syn.yaml +0 -9
  507. egse/synoptics/syn_cs.py +0 -195
  508. egse/system.py +0 -1611
  509. egse/tcs/__init__.py +0 -14
  510. egse/tcs/tcs.py +0 -879
  511. egse/tcs/tcs.yaml +0 -14
  512. egse/tcs/tcs_cs.py +0 -202
  513. egse/tcs/tcs_devif.py +0 -292
  514. egse/tcs/tcs_protocol.py +0 -180
  515. egse/tcs/tcs_sim.py +0 -177
  516. egse/tcs/tcs_ui.py +0 -543
  517. egse/tdms.py +0 -171
  518. egse/tempcontrol/__init__.py +0 -23
  519. egse/tempcontrol/agilent/agilent34970.py +0 -109
  520. egse/tempcontrol/agilent/agilent34970.yaml +0 -44
  521. egse/tempcontrol/agilent/agilent34970_cs.py +0 -114
  522. egse/tempcontrol/agilent/agilent34970_devif.py +0 -182
  523. egse/tempcontrol/agilent/agilent34970_protocol.py +0 -96
  524. egse/tempcontrol/agilent/agilent34972.py +0 -111
  525. egse/tempcontrol/agilent/agilent34972.yaml +0 -44
  526. egse/tempcontrol/agilent/agilent34972_cs.py +0 -115
  527. egse/tempcontrol/agilent/agilent34972_devif.py +0 -189
  528. egse/tempcontrol/agilent/agilent34972_protocol.py +0 -98
  529. egse/tempcontrol/beaglebone/beaglebone.py +0 -341
  530. egse/tempcontrol/beaglebone/beaglebone.yaml +0 -110
  531. egse/tempcontrol/beaglebone/beaglebone_cs.py +0 -117
  532. egse/tempcontrol/beaglebone/beaglebone_protocol.py +0 -134
  533. egse/tempcontrol/beaglebone/beaglebone_ui.py +0 -674
  534. egse/tempcontrol/digalox/digalox.py +0 -115
  535. egse/tempcontrol/digalox/digalox.yaml +0 -36
  536. egse/tempcontrol/digalox/digalox_cs.py +0 -108
  537. egse/tempcontrol/digalox/digalox_protocol.py +0 -56
  538. egse/tempcontrol/keithley/__init__.py +0 -33
  539. egse/tempcontrol/keithley/daq6510.py +0 -662
  540. egse/tempcontrol/keithley/daq6510.yaml +0 -105
  541. egse/tempcontrol/keithley/daq6510_cs.py +0 -163
  542. egse/tempcontrol/keithley/daq6510_devif.py +0 -343
  543. egse/tempcontrol/keithley/daq6510_protocol.py +0 -79
  544. egse/tempcontrol/keithley/daq6510_sim.py +0 -186
  545. egse/tempcontrol/lakeshore/__init__.py +0 -33
  546. egse/tempcontrol/lakeshore/lsci.py +0 -361
  547. egse/tempcontrol/lakeshore/lsci.yaml +0 -162
  548. egse/tempcontrol/lakeshore/lsci_cs.py +0 -174
  549. egse/tempcontrol/lakeshore/lsci_devif.py +0 -292
  550. egse/tempcontrol/lakeshore/lsci_protocol.py +0 -76
  551. egse/tempcontrol/lakeshore/lsci_ui.py +0 -387
  552. egse/tempcontrol/ni/__init__.py +0 -0
  553. egse/tempcontrol/spid/spid.py +0 -109
  554. egse/tempcontrol/spid/spid.yaml +0 -81
  555. egse/tempcontrol/spid/spid_controller.py +0 -279
  556. egse/tempcontrol/spid/spid_cs.py +0 -136
  557. egse/tempcontrol/spid/spid_protocol.py +0 -107
  558. egse/tempcontrol/spid/spid_ui.py +0 -723
  559. egse/tempcontrol/srs/__init__.py +0 -22
  560. egse/tempcontrol/srs/ptc10.py +0 -867
  561. egse/tempcontrol/srs/ptc10.yaml +0 -227
  562. egse/tempcontrol/srs/ptc10_cs.py +0 -128
  563. egse/tempcontrol/srs/ptc10_devif.py +0 -116
  564. egse/tempcontrol/srs/ptc10_protocol.py +0 -39
  565. egse/tempcontrol/srs/ptc10_ui.py +0 -906
  566. egse/ups/apc/apc.py +0 -236
  567. egse/ups/apc/apc.yaml +0 -45
  568. egse/ups/apc/apc_cs.py +0 -101
  569. egse/ups/apc/apc_protocol.py +0 -125
  570. egse/user.yaml +0 -7
  571. egse/vacuum/beaglebone/beaglebone.py +0 -149
  572. egse/vacuum/beaglebone/beaglebone.yaml +0 -44
  573. egse/vacuum/beaglebone/beaglebone_cs.py +0 -108
  574. egse/vacuum/beaglebone/beaglebone_devif.py +0 -159
  575. egse/vacuum/beaglebone/beaglebone_protocol.py +0 -192
  576. egse/vacuum/beaglebone/beaglebone_ui.py +0 -638
  577. egse/vacuum/instrutech/igm402.py +0 -91
  578. egse/vacuum/instrutech/igm402.yaml +0 -90
  579. egse/vacuum/instrutech/igm402_controller.py +0 -124
  580. egse/vacuum/instrutech/igm402_cs.py +0 -108
  581. egse/vacuum/instrutech/igm402_interface.py +0 -49
  582. egse/vacuum/instrutech/igm402_simulator.py +0 -36
  583. egse/vacuum/keller/kellerBus.py +0 -256
  584. egse/vacuum/keller/leo3.py +0 -100
  585. egse/vacuum/keller/leo3.yaml +0 -38
  586. egse/vacuum/keller/leo3_controller.py +0 -81
  587. egse/vacuum/keller/leo3_cs.py +0 -101
  588. egse/vacuum/keller/leo3_interface.py +0 -33
  589. egse/vacuum/mks/evision.py +0 -86
  590. egse/vacuum/mks/evision.yaml +0 -75
  591. egse/vacuum/mks/evision_cs.py +0 -101
  592. egse/vacuum/mks/evision_devif.py +0 -313
  593. egse/vacuum/mks/evision_interface.py +0 -60
  594. egse/vacuum/mks/evision_simulator.py +0 -24
  595. egse/vacuum/mks/evision_ui.py +0 -701
  596. egse/vacuum/pfeiffer/acp40.py +0 -87
  597. egse/vacuum/pfeiffer/acp40.yaml +0 -60
  598. egse/vacuum/pfeiffer/acp40_controller.py +0 -117
  599. egse/vacuum/pfeiffer/acp40_cs.py +0 -109
  600. egse/vacuum/pfeiffer/acp40_interface.py +0 -40
  601. egse/vacuum/pfeiffer/acp40_simulator.py +0 -37
  602. egse/vacuum/pfeiffer/tc400.py +0 -87
  603. egse/vacuum/pfeiffer/tc400.yaml +0 -83
  604. egse/vacuum/pfeiffer/tc400_controller.py +0 -136
  605. egse/vacuum/pfeiffer/tc400_cs.py +0 -109
  606. egse/vacuum/pfeiffer/tc400_interface.py +0 -70
  607. egse/vacuum/pfeiffer/tc400_simulator.py +0 -35
  608. egse/vacuum/pfeiffer/tpg261.py +0 -80
  609. egse/vacuum/pfeiffer/tpg261.yaml +0 -66
  610. egse/vacuum/pfeiffer/tpg261_controller.py +0 -150
  611. egse/vacuum/pfeiffer/tpg261_cs.py +0 -109
  612. egse/vacuum/pfeiffer/tpg261_interface.py +0 -59
  613. egse/vacuum/pfeiffer/tpg261_simulator.py +0 -23
  614. egse/version.py +0 -174
  615. egse/visitedpositions.py +0 -398
  616. egse/windowing.py +0 -213
  617. egse/zmq/__init__.py +0 -28
  618. egse/zmq/spw.py +0 -160
  619. egse/zmq_ser.py +0 -41
  620. scripts/alerts/cold.yaml +0 -278
  621. scripts/alerts/example_alerts.yaml +0 -54
  622. scripts/alerts/transition.yaml +0 -14
  623. scripts/alerts/warm.yaml +0 -49
  624. scripts/analyse_n_fee_hk_data.py +0 -52
  625. scripts/check_hdf5_files.py +0 -192
  626. scripts/check_register_sync.py +0 -47
  627. scripts/check_tcs_calib_coef.py +0 -90
  628. scripts/correct_ccd_cold_temperature_cal.py +0 -157
  629. scripts/create_hdf5_report.py +0 -293
  630. scripts/csl_model.py +0 -420
  631. scripts/csl_restore_setup.py +0 -229
  632. scripts/export-grafana-dashboards.py +0 -49
  633. scripts/fdir/cs_recovery/fdir_cs_recovery.py +0 -54
  634. scripts/fdir/fdir_table.yaml +0 -70
  635. scripts/fdir/fdir_test_recovery.py +0 -10
  636. scripts/fdir/hw_recovery/fdir_agilent_hw_recovery.py +0 -73
  637. scripts/fdir/limit_recovery/fdir_agilent_limit.py +0 -61
  638. scripts/fdir/limit_recovery/fdir_bb_heater_limit.py +0 -59
  639. scripts/fdir/limit_recovery/fdir_ensemble_limit.py +0 -33
  640. scripts/fdir/limit_recovery/fdir_pressure_limit_recovery.py +0 -71
  641. scripts/fix_csv.py +0 -80
  642. scripts/ias/correct_ccd_temp_cal_elfique.py +0 -43
  643. scripts/ias/correct_ccd_temp_cal_floreffe.py +0 -43
  644. scripts/ias/correct_trp_swap_achel.py +0 -199
  645. scripts/inta/correct_ccd_temp_cal_duvel.py +0 -43
  646. scripts/inta/correct_ccd_temp_cal_gueuze.py +0 -43
  647. scripts/n_fee_supply_voltage_calculation.py +0 -92
  648. scripts/playground.py +0 -30
  649. scripts/print_hdf5_hk_data.py +0 -68
  650. scripts/print_register_map.py +0 -43
  651. scripts/remove_lines_between_matches.py +0 -188
  652. scripts/sron/commanding/control_heaters.py +0 -44
  653. scripts/sron/commanding/pumpdown.py +0 -46
  654. scripts/sron/commanding/set_pid_setpoint.py +0 -19
  655. scripts/sron/commanding/shutdown_bbb_heaters.py +0 -10
  656. scripts/sron/commanding/shutdown_pumps.py +0 -33
  657. scripts/sron/correct_mgse_coordinates_brigand_chimay.py +0 -272
  658. scripts/sron/correct_trp_swap_brigand.py +0 -204
  659. scripts/sron/gimbal_conversions.py +0 -75
  660. scripts/sron/tm_gen/tm_gen_agilent.py +0 -37
  661. scripts/sron/tm_gen/tm_gen_heaters.py +0 -4
  662. scripts/sron/tm_gen/tm_gen_spid.py +0 -13
  663. scripts/update_operational_cgse.py +0 -268
  664. scripts/update_operational_cgse_old.py +0 -273
egse/dpu/__init__.py DELETED
@@ -1,2698 +0,0 @@
1
- """
2
- This module defines the commanding interfaces for the DPU—N-FEE interaction.
3
-
4
-
5
- On the *client/user side*, the `DPUProxy` class shall be used for user interactions and commanding with both
6
- the DPU simulator and the N-FEE. This class connects to the DPU control server which must be running before
7
- any commands can be processed.
8
-
9
- On the *server side*, the `DPUControlServer` class is located in the module `dpu_cs`.
10
-
11
- The top-level classes that are of interest to the developer when inspecting this module are:
12
-
13
- * `DPUController` which puts the requested commands on the command queue for the `DPUProcessor`
14
- * `DPUProcessor` is the work horse of the DPU simulator and runs in a separate process. This process communicates
15
- directly with the N-FEE through the SpaceWire interface.
16
- * `DPUInternals` keeps critical information about the DPU and provides information on the status of the
17
- readout progress, i.e. where we are in the readout cycle.
18
- * `DPUMonitoring` provides methods to run a function at a certain time, e.g. right after a long pulse, or to wait
19
- for an event, e.g. when an HDF5 file is ready for processing.
20
-
21
- The actual commanding is done in `dpu` module. That module also defines the `NFEEState` which acts as a
22
- mirror of the FPGA status.
23
-
24
- This module also defines a number of functions, but they all are for internal use and are not of any interest
25
- unless you are maintaining this module.
26
-
27
- """
28
-
29
- import logging
30
- import multiprocessing
31
- import pickle
32
- import queue
33
- import time
34
- import traceback
35
- from dataclasses import dataclass
36
- from enum import Enum
37
- from pathlib import Path
38
- from typing import Any
39
- from typing import Callable
40
- from typing import Dict
41
- from typing import List
42
- from typing import Mapping
43
- from typing import Tuple
44
- from typing import Type
45
-
46
- import zmq
47
-
48
- import egse.spw
49
- from egse.command import ClientServerCommand
50
- from egse.confman import ConfigurationManagerProxy
51
- from egse.control import ControlServer
52
- from egse.decorators import dynamic_interface
53
- from egse.dpu.dpu import NFEEState
54
- from egse.dpu.dpu import command_external_clock
55
- from egse.dpu.dpu import command_get_hk_information
56
- from egse.dpu.dpu import command_get_mode
57
- from egse.dpu.dpu import command_internal_clock
58
- from egse.dpu.dpu import command_reset
59
- from egse.dpu.dpu import command_set_charge_injection
60
- from egse.dpu.dpu import command_set_clear_error_flags
61
- from egse.dpu.dpu import command_set_dump_mode
62
- from egse.dpu.dpu import command_set_dump_mode_int_sync
63
- from egse.dpu.dpu import command_set_full_image_mode
64
- from egse.dpu.dpu import command_set_full_image_mode_int_sync
65
- from egse.dpu.dpu import command_set_full_image_pattern_mode
66
- from egse.dpu.dpu import command_set_high_precision_hk_mode
67
- from egse.dpu.dpu import command_set_immediate_on_mode
68
- from egse.dpu.dpu import command_set_nfee_fpga_defaults
69
- from egse.dpu.dpu import command_set_on_mode
70
- from egse.dpu.dpu import command_set_readout_order
71
- from egse.dpu.dpu import command_set_register_value
72
- from egse.dpu.dpu import command_set_reverse_clocking
73
- from egse.dpu.dpu import command_set_standby_mode
74
- from egse.dpu.dpu import command_set_vgd
75
- from egse.dpu.dpu import command_sync_register_map
76
- from egse.dpu.dpu import prio_command_get_mode
77
- from egse.dpu.dpu import prio_command_get_register_map
78
- from egse.dpu.dpu import prio_command_get_slicing
79
- from egse.dpu.dpu import prio_command_get_sync_mode
80
- from egse.dpu.dpu import prio_command_is_dump_mode
81
- from egse.dpu.dpu import prio_command_set_slicing
82
- from egse.dsi.esl import is_timecode
83
- from egse.exceptions import Abort
84
- from egse.fee import n_fee_mode
85
- from egse.fee.nfee import HousekeepingData
86
- from egse.obsid import ObservationIdentifier
87
- from egse.protocol import CommandProtocol
88
- from egse.proxy import Proxy
89
- from egse.reg import RegisterMap
90
- from egse.settings import Settings
91
- from egse.setup import SetupError
92
- from egse.setup import load_setup
93
- from egse.spw import DataDataPacket
94
- from egse.spw import DataPacket
95
- from egse.spw import DataPacketType
96
- from egse.spw import HousekeepingPacket
97
- from egse.spw import OverscanDataPacket
98
- from egse.spw import SpaceWireInterface
99
- from egse.spw import SpaceWirePacket
100
- from egse.spw import TimecodePacket
101
- from egse.spw import to_string
102
- from egse.storage import StorageProxy
103
- from egse.storage.persistence import FITS
104
- from egse.storage.persistence import HDF5
105
- from egse.storage.persistence import PersistenceLayer
106
- from egse.system import SignalCatcher
107
- from egse.system import Timer
108
- from egse.system import format_datetime
109
- from egse.system import wait_until
110
- from egse.zmq import MessageIdentifier
111
- from egse.zmq_ser import bind_address
112
- from egse.zmq_ser import connect_address
113
-
114
- LOGGER = logging.getLogger(__name__)
115
-
116
- CTRL_SETTINGS = Settings.load("DPU Control Server")
117
- N_FEE_SETTINGS = Settings.load("N-FEE")
118
- COMMAND_SETTINGS = Settings.load(filename="dpu.yaml")
119
- DEVICE_SETTINGS = Settings.load(filename="dpu.yaml")
120
-
121
- CRUCIAL_REGISTER_PARAMETERS = (
122
- "ccd_readout_order", "sensor_sel", "v_start", "v_end", "h_end", "ccd_mode_config"
123
- )
124
-
125
- DATA_TYPE: Dict[str, Type[PersistenceLayer]] = {
126
- "HDF5": HDF5,
127
- "FITS": FITS,
128
- }
129
-
130
- CCD_NUMBERS = [1, 2, 3, 4]
131
-
132
-
133
- def rotate_list(seq, n):
134
- if (size := len(seq)) < 2:
135
- return seq
136
- n = n % len(seq)
137
- return seq[n:] + seq[:n]
138
-
139
-
140
- def _get_ccd_readout_order(order_list: list, ccd_id_mapping: list):
141
- return sum(ccd_id_mapping[ccd] << idx * 2 for idx, ccd in enumerate(order_list))
142
-
143
-
144
- class NoDataPacketError(Exception):
145
- """Raised when the expected data packet turns out to be something else."""
146
- pass
147
-
148
-
149
- class NoHousekeepingPacketError(Exception):
150
- """Raised when the expected Housekeeping packet turns out to be something else."""
151
- pass
152
-
153
-
154
- class NoTimeCodeError(Exception):
155
- """Raised when the expected Timecode packet turns out to be something else."""
156
- pass
157
-
158
-
159
- class NoBytesReceivedError(Exception):
160
- """Raised when the zero or one bytes were received."""
161
- pass
162
-
163
-
164
- class TimecodeTimeoutError(Exception):
165
- """Raised when the read_packet times out while waiting for a timecode."""
166
- pass
167
-
168
-
169
- class TimeExceededError(Exception):
170
- """Raised when retrieving the data packets from the N-FEE takes too long."""
171
- pass
172
-
173
-
174
- class NFEECommandError(Exception):
175
- """Raised when sending a command to the N-FEE failed."""
176
- pass
177
-
178
-
179
- class DPUInterface:
180
- """
181
- This interface is for sending commands to the DPU Control Server. The commands are user
182
- oriented and will be translated by the DPU Controller in proper FEE commands.
183
-
184
- The interface should be implemented by the `DPUController` and the `DPUProxy` (and possibly
185
- a `DPUSimulator` should we need that e.g. for testing purposes).
186
-
187
- The command shall also be added to the `dpu.yaml` command definitions file.
188
- """
189
-
190
- @dynamic_interface
191
- def marker(self, mark: str):
192
- raise NotImplementedError
193
-
194
- @dynamic_interface
195
- def get_slicing(self) -> int:
196
- raise NotImplementedError
197
-
198
- @dynamic_interface
199
- def set_slicing(self, num_cycles: int):
200
- raise NotImplementedError
201
-
202
- @dynamic_interface
203
- def is_simulator(self):
204
- raise NotImplementedError
205
-
206
- @dynamic_interface
207
- def get_register_map(self) -> RegisterMap:
208
- """
209
- Returns the RegisterMap
210
- """
211
- raise NotImplementedError
212
-
213
- @dynamic_interface
214
- def n_fee_sync_register_map(self):
215
- """
216
- Read the complete register map from the N-FEE.
217
- """
218
- raise NotImplementedError
219
-
220
- @dynamic_interface
221
- def n_fee_get_mode(self):
222
- """
223
- Returns the N-FEE mode.
224
- """
225
- raise NotImplementedError
226
-
227
- @dynamic_interface
228
- def n_fee_get_sync_mode(self):
229
- """
230
- Returns the N-FEE mode.
231
- """
232
- raise NotImplementedError
233
-
234
- @dynamic_interface
235
- def n_fee_set_on_mode(self):
236
- """Command the N-FEE to go into ON mode."""
237
- raise NotImplementedError
238
-
239
- @dynamic_interface
240
- def n_fee_is_dump_mode(self):
241
- """
242
- Returns True if the N-FEE is configured for DUMP mode.
243
-
244
- DUMP mode is not really an N-FEE mode, but more a set of register settings that allow to
245
- readout/dump all CCDs without transmitting any data. This mode is used in ambient to make
246
- sure the detectors do not get saturated between tests.
247
- """
248
- raise NotImplementedError
249
-
250
- @dynamic_interface
251
- def n_fee_set_immediate_on_mode(self):
252
- """Command the N-FEE to go into ON mode."""
253
- raise NotImplementedError
254
-
255
- @dynamic_interface
256
- def n_fee_set_standby_mode(self):
257
- """Command the N-FEE to go into STANDBY mode."""
258
- raise NotImplementedError
259
-
260
- @dynamic_interface
261
- def n_fee_set_dump_mode(self, n_fee_parameters: dict):
262
- """ Command the N-FEE to go into DUMP mode.
263
-
264
- n_fee_parameters:
265
-
266
- The n_fee_parameters argument is a dictionary with additional/specific parameters to
267
- set in the register when moving to full image pattern mode.
268
-
269
- * num_cycles (int): the number of readout cycles
270
- * v_start (int): the first line to readout
271
- * v_end (int): the last line to readout
272
- * sensor_sel (int): the CCD side to select for transfer
273
-
274
- Args:
275
- n_fee_parameters (dict): dictionary with N-FEE parameters to be set_
276
- """
277
- raise NotImplementedError
278
-
279
- @dynamic_interface
280
- def n_fee_set_dump_mode_int_sync(self, n_fee_parameters: dict):
281
- """ Command the N-FEE to go into DUMP mode and internal sync.
282
-
283
- The n_fee_parameters argument is a dictionary with additional/specific parameters to
284
- set in the register when moving to full image pattern mode.
285
-
286
- * num_cycles (int): the number of readout cycles
287
- * v_start (int): the first line to readout
288
- * v_end (int): the last line to readout
289
- * sensor_sel (int): the CCD side to select for transfer
290
- * int_sync_period (int): the period of the internal sync in milliseconds
291
-
292
- Args:
293
- n_fee_parameters (dict): dictionary with N-FEE parameters to be set_
294
- """
295
- raise NotImplementedError
296
-
297
- @dynamic_interface
298
- def n_fee_set_full_image_mode(self, n_fee_parameters):
299
- """
300
- Command the N-FEE to go into FULL_IMAGE mode.
301
-
302
- n_fee_parameters:
303
-
304
- The n_fee_parameters argument is a dictionary with additional/specific parameters to
305
- set in the register when moving to full image pattern mode.
306
-
307
- * num_cycles (int): the number of readout cycles
308
- * v_start (int): the first line to readout
309
- * v_end (int): the last line to readout
310
- * sensor_sel (int): the CCD side to select for transfer
311
-
312
- Args:
313
- n_fee_parameters (dict): dictionary with N-FEE parameters to be set
314
- """
315
- raise NotImplementedError
316
-
317
- @dynamic_interface
318
- def n_fee_set_full_image_mode_int_sync(self, n_fee_parameters):
319
- """
320
- Command the N-FEE to go into FULL_IMAGE mode and internal sync.
321
-
322
- n_fee_parameters:
323
-
324
- The n_fee_parameters argument is a dictionary with additional/specific parameters to
325
- set in the register when moving to full image pattern mode.
326
-
327
- * num_cycles (int): the number of readout cycles
328
- * v_start (int): the first line to readout
329
- * v_end (int): the last line to readout
330
- * sensor_sel (int): the CCD side to select for transfer
331
-
332
- Args:
333
- n_fee_parameters (dict): dictionary with N-FEE parameters to be set
334
- """
335
- raise NotImplementedError
336
-
337
- @dynamic_interface
338
- def n_fee_set_full_image_pattern_mode(self, n_fee_parameters):
339
- """
340
- Command the N-FEE to go into FULL_IMAGE_PATTERN mode.
341
-
342
- n_fee_parameters:
343
-
344
- The n_fee_parameters argument is a dictionary with additional/specific parameters to
345
- set in the register when moving to full image pattern mode.
346
-
347
- * num_cycles (int): the number of readout cycles
348
- * v_start (int): the first line to readout
349
- * v_end (int): the last line to readout
350
- * sensor_sel (int): the CCD side to select for transfer
351
-
352
- Args:
353
- n_fee_parameters (dict): dictionary with N-FEE parameters to be set
354
- """
355
- raise NotImplementedError
356
-
357
- @dynamic_interface
358
- def n_fee_high_precision_hk_mode(self, n_fee_parameters: dict):
359
- """Command the N-FEE to go into high precision housekeeping mode."""
360
- raise NotImplementedError
361
-
362
- @dynamic_interface
363
- def n_fee_set_internal_sync(self, n_fee_parameters: dict):
364
- """
365
- Command the N-FEE to go into internal sync mode.
366
-
367
- The method expects the following keys in n_fee_parameters:
368
-
369
- * int_sync_period: the internal sync period in milliseconds [default=6250]
370
-
371
- Args:
372
- n_fee_parameters (dict): N-FEE parameter dictionary
373
- """
374
- raise NotImplementedError
375
-
376
- @dynamic_interface
377
- def n_fee_set_external_sync(self, n_fee_parameters: dict):
378
- """
379
- Command the N-FEE to go into external sync mode.
380
- No keys are expected in n_fee_parameters, pass an empty dict.
381
- """
382
- raise NotImplementedError
383
-
384
- @dynamic_interface
385
- def n_fee_set_register_value(self, reg_name: str, field_name: str, field_value: int):
386
- """Command the N-FEE to set a register value."""
387
- raise NotImplementedError
388
-
389
- @dynamic_interface
390
- def n_fee_reset(self):
391
- """Command the N-FEE to reset to its default settings."""
392
- raise NotImplementedError
393
-
394
- @dynamic_interface
395
- def n_fee_set_clear_error_flags(self):
396
- """
397
- Command the N-FEE to clear all error flags for non RMAP/SpW related functions immediately.
398
-
399
- The `clear_error_flag` bit in the register map is set to 1, meaning that all error flags
400
- that are generated by the N-FEE FPGA for non RMAP-SpW related functions are cleared
401
- immediately. This bit is cleared automatically, so that any future error flags can be
402
- latched again. If the error conditions persist and no corrective measures are taken,
403
- then error flags would be set again.
404
- """
405
-
406
- raise NotImplementedError
407
-
408
- @dynamic_interface
409
- def n_fee_set_reverse_clocking(self, n_fee_parameters: dict):
410
-
411
- raise NotImplementedError
412
-
413
- @dynamic_interface
414
- def n_fee_set_charge_injection(self, n_fee_parameters: dict):
415
- raise NotImplementedError
416
-
417
- @dynamic_interface
418
- def n_fee_set_vgd(self, n_fee_parameters: dict):
419
- raise NotImplementedError
420
-
421
- @dynamic_interface
422
- def n_fee_set_fpga_defaults(self):
423
- raise NotImplementedError
424
-
425
-
426
- class DPUSimulator(DPUInterface):
427
- # The DPUSimulator will stand by itself, which means it will not send commands to an FEE
428
- # nor will it request data or HK from the FEE. The methods in this implementation will return
429
- # a fake set of data.
430
-
431
- def n_fee_get_mode(self):
432
- return n_fee_mode.STAND_BY_MODE
433
-
434
- def n_fee_get_sync_mode(self):
435
- return NotImplemented
436
-
437
- def n_fee_set_on_mode(self):
438
- pass
439
-
440
- def n_fee_set_standby_mode(self):
441
- pass
442
-
443
- def n_fee_set_dump_mode(self, n_fee_parameters: dict):
444
- pass
445
-
446
- def n_fee_set_full_image_mode(self, n_fee_parameters):
447
- import pprint
448
-
449
- LOGGER.debug(f"called: n_fee_set_full_image_mode({pprint.pformat(n_fee_parameters)})")
450
-
451
- def n_fee_set_full_image_mode_int_sync(self, n_fee_parameters):
452
- import pprint
453
-
454
- LOGGER.debug(f"called: n_fee_set_full_image_mode_int_sync({pprint.pformat(n_fee_parameters)})")
455
-
456
- def n_fee_set_full_image_pattern_mode(self):
457
- pass
458
-
459
- def n_fee_high_precision_hk_mode(self, n_fee_parameters: dict):
460
- pass
461
-
462
- def n_fee_set_internal_sync(self, n_fee_parameters: dict):
463
- pass
464
-
465
- def n_fee_set_external_sync(self):
466
- pass
467
-
468
- def n_fee_set_clear_error_flags(self):
469
- pass
470
-
471
- def n_fee_set_fpga_defaults(self):
472
- pass
473
-
474
-
475
- class DPUController(DPUInterface):
476
- """
477
- The DPU Controller puts commands on the command queue for processing by the DPU Processor.
478
- Any response from the DPU Processor will be available on the response queue as soon as the
479
- command has been executed. The DPU Processor is a separate process that is started by the
480
- DPU Command Protocol.
481
- """
482
-
483
- def __init__(self,
484
- priority_queue: multiprocessing.Queue,
485
- command_queue: multiprocessing.Queue,
486
- response_queue: multiprocessing.Queue):
487
- self._priority_q = priority_queue
488
- self._command_q = command_queue
489
- self._response_q = response_queue
490
-
491
- self._setup = load_setup()
492
- if self._setup is None:
493
- raise SetupError("The current Setup couldn't be loaded from the configuration manager.")
494
-
495
- try:
496
- self.default_ccd_readout_order = self._setup.camera.fee.ccd_numbering.DEFAULT_CCD_READOUT_ORDER
497
- self.sensor_sel_both_sides = self._setup.camera.fee.sensor_sel.enum.BOTH_SIDES.value
498
- except AttributeError as exc:
499
- raise SetupError("Missing entry in the setup for camera.fee group") from exc
500
-
501
- def marker(self, mark: str):
502
- LOGGER.info(f"{mark = }")
503
-
504
- def get_slicing(self) -> int:
505
- self._priority_q.put((prio_command_get_slicing, []))
506
- LOGGER.debug("Controller.get_slicing: Put prio_command_get_slicing on the Queue.")
507
- (cmd, response) = self._response_q.get()
508
- LOGGER.debug(f"Controller.get_slicing returned: ({cmd.__name__}, {response}).")
509
- return response
510
-
511
- def set_slicing(self, num_cycles: int):
512
- self._priority_q.put((prio_command_set_slicing, [num_cycles]))
513
- LOGGER.debug(
514
- "Controller.set_slicing: Put prio_command_set_slicing on the Queue."
515
- )
516
- (cmd, response) = self._response_q.get()
517
- LOGGER.debug(f"Controller.set_slicing returned: ({cmd.__name__}, {response}).")
518
- return response
519
-
520
- def is_simulator(self):
521
- return True
522
-
523
- def n_fee_sync_register_map(self) -> RegisterMap:
524
- self._command_q.put((command_sync_register_map, [], {}))
525
- LOGGER.debug(
526
- "Controller.n_fee_sync_register_map: Put command_sync_register_map on the Queue."
527
- )
528
- (cmd, response) = self._response_q.get()
529
- LOGGER.debug(f"Controller.n_fee_sync_register_map returned: ({cmd.__name__}, {response}).")
530
- return response
531
-
532
- def get_register_map(self) -> RegisterMap:
533
- self._priority_q.put((prio_command_get_register_map, []))
534
- LOGGER.debug("Controller.get_register_map: Put prio_command_get_register_map on the Queue.")
535
- (cmd, response) = self._response_q.get()
536
- LOGGER.debug(f"Controller.get_register_map returned: ({cmd.__name__}, {response}).")
537
- return response
538
-
539
- def n_fee_get_mode(self):
540
- self._priority_q.put((prio_command_get_mode, []))
541
- LOGGER.debug("Controller.n_fee_get_mode: Put prio_command_get_mode on the Queue.")
542
- (cmd, response) = self._response_q.get()
543
- LOGGER.debug(f"Controller.n_fee_get_mode returned: ({cmd.__name__}, {response}).")
544
- return response
545
-
546
- def n_fee_get_sync_mode(self):
547
- self._priority_q.put((prio_command_get_sync_mode, []))
548
- LOGGER.debug("Controller.n_fee_get_sync_mode: Put prio_command_get_sync_mode on the Queue.")
549
- (cmd, response) = self._response_q.get()
550
- LOGGER.debug(f"Controller.n_fee_get_sync_mode returned: ({cmd.__name__}, {response}).")
551
- return response
552
-
553
- def n_fee_is_dump_mode(self):
554
- self._priority_q.put((prio_command_is_dump_mode, []))
555
- LOGGER.debug("Controller.n_fee_is_dump_mode: Put prio_command_is_dump_mode on the Queue.")
556
- (cmd, response) = self._response_q.get()
557
- LOGGER.debug(f"Controller.n_fee_is_dump_mode returned: ({cmd.__name__}, {response}).")
558
- return response
559
-
560
- def n_fee_set_immediate_on_mode(self):
561
- self._command_q.put((command_set_immediate_on_mode, [], {}))
562
- LOGGER.debug(
563
- "Controller.n_fee_set_immediate_on_mode: Put command_set_immediate_on_mode "
564
- "on the Queue."
565
- )
566
- (cmd, response) = self._response_q.get()
567
- LOGGER.debug(
568
- f"Controller.n_fee_set_immediate_on_mode returned: ({cmd.__name__}, {response})"
569
- )
570
- return response
571
-
572
- def n_fee_set_on_mode(self):
573
- self._command_q.put((command_set_on_mode, [], {}))
574
- LOGGER.debug("Controller.n_fee_set_on_mode: Put command_set_on_mode on the Queue.")
575
- (cmd, response) = self._response_q.get()
576
- LOGGER.debug(f"Controller.n_fee_set_on_mode returned: ({cmd.__name__}, {response})")
577
- return response
578
-
579
- def n_fee_set_standby_mode(self):
580
- self._command_q.put((command_set_standby_mode, [], {}))
581
- LOGGER.debug(
582
- "Controller.n_fee_set_standby_mode: Put command_set_standby_mode on the Queue."
583
- )
584
- (cmd, response) = self._response_q.get()
585
- LOGGER.debug(f"Controller.n_fee_set_standby_mode returned: ({cmd.__name__}, {response})")
586
- return response
587
-
588
- def n_fee_set_dump_mode(self, n_fee_parameters: dict):
589
- v_start = n_fee_parameters.get("v_start", 0)
590
- v_end = n_fee_parameters.get("v_end", 0)
591
- sensor_sel_ = n_fee_parameters.get("sensor_sel", self.sensor_sel_both_sides)
592
- ccd_readout_order = n_fee_parameters.get("ccd_readout_order", self.default_ccd_readout_order)
593
- n_final_dump = n_fee_parameters.get("n_final_dump", 4510)
594
- sync_sel = n_fee_parameters.get("sync_sel", 0)
595
- num_cycles = n_fee_parameters.get("num_cycles", 0)
596
- self._command_q.put((command_set_dump_mode,
597
- [v_start, v_end, sensor_sel_, ccd_readout_order, n_final_dump, sync_sel],
598
- {'num_cycles': num_cycles}))
599
- LOGGER.debug(
600
- "Controller.n_fee_set_dump_mode: Put command_set_dump_mode on the Queue."
601
- )
602
- (cmd, response) = self._response_q.get()
603
- LOGGER.debug(f"Controller.n_fee_set_dump_mode returned: ({cmd.__name__}, {response})")
604
- return response
605
-
606
- def n_fee_set_dump_mode_int_sync(self, n_fee_parameters: dict):
607
- v_start = n_fee_parameters.get("v_start", 0)
608
- v_end = n_fee_parameters.get("v_end", 0)
609
- sensor_sel_ = n_fee_parameters.get("sensor_sel", self.sensor_sel_both_sides)
610
- n_final_dump = n_fee_parameters.get("n_final_dump", 4510)
611
- ccd_readout_order = n_fee_parameters.get("ccd_readout_order", self.default_ccd_readout_order)
612
- sync_sel = n_fee_parameters.get("sync_sel", 1)
613
- int_sync_period = n_fee_parameters.get("int_sync_period", 600)
614
- num_cycles = n_fee_parameters.get("num_cycles", 0)
615
- self._command_q.put(
616
- (
617
- command_set_dump_mode_int_sync,
618
- [v_start, v_end, sensor_sel_, ccd_readout_order, n_final_dump, int_sync_period, sync_sel],
619
- {'num_cycles': num_cycles}
620
- )
621
- )
622
- LOGGER.debug("Controller.n_fee_set_dump_mode_int_sync: "
623
- "Put command_set_dump_mode_int_sync on the Queue.")
624
- (cmd, response) = self._response_q.get()
625
- LOGGER.debug(f"Controller.n_fee_set_dump_mode_int_sync returned: ({cmd.__name__}, {response})")
626
- return response
627
-
628
- def n_fee_set_full_image_mode(self, n_fee_parameters: dict):
629
- v_start = n_fee_parameters.get("v_start", 0)
630
- v_end = n_fee_parameters.get("v_end", 1)
631
- sensor_sel_ = n_fee_parameters.get("sensor_sel", self.sensor_sel_both_sides)
632
- ccd_readout_order = n_fee_parameters.get("ccd_readout_order", self.default_ccd_readout_order)
633
- n_final_dump = n_fee_parameters.get("n_final_dump", 0)
634
- num_cycles = n_fee_parameters.get("num_cycles", 0)
635
- dump_mode_int = n_fee_parameters.get("dump_mode_int", False)
636
- self._command_q.put((command_set_full_image_mode,
637
- [v_start, v_end, sensor_sel_, ccd_readout_order, n_final_dump],
638
- {'num_cycles': num_cycles, 'dump_mode_int': dump_mode_int}))
639
- LOGGER.debug(
640
- "Controller.n_fee_set_full_image_mode: Put command_set_full_image_mode on the Queue."
641
- )
642
- (cmd, response) = self._response_q.get()
643
- LOGGER.debug(f"Controller.n_fee_set_full_image_mode returned: ({cmd.__name__}, {response})")
644
- return response
645
-
646
- def n_fee_set_full_image_mode_int_sync(self, n_fee_parameters: dict):
647
- v_start = n_fee_parameters.get("v_start", 0)
648
- v_end = n_fee_parameters.get("v_end", 1)
649
- sensor_sel_ = n_fee_parameters.get("sensor_sel", self.sensor_sel_both_sides)
650
- ccd_readout_order = n_fee_parameters.get("ccd_readout_order", self.default_ccd_readout_order)
651
- n_final_dump = n_fee_parameters.get("n_final_dump", 0)
652
- int_sync_period = n_fee_parameters.get("int_sync_period", 6250)
653
- num_cycles = n_fee_parameters.get("num_cycles", 0)
654
- dump_mode_int = n_fee_parameters.get("dump_mode_int", True)
655
- self._command_q.put((command_set_full_image_mode_int_sync,
656
- [v_start, v_end, sensor_sel_, ccd_readout_order, n_final_dump, int_sync_period],
657
- {'num_cycles': num_cycles, 'dump_mode_int': dump_mode_int}))
658
- LOGGER.debug(
659
- "Controller.n_fee_set_full_image_mode_int_sync: Put command_set_full_image_mode_int_sync on the Queue."
660
- )
661
- (cmd, response) = self._response_q.get()
662
- LOGGER.debug(f"Controller.n_fee_set_full_image_mode_int_sync returned: ({cmd.__name__}, {response})")
663
- return response
664
-
665
- def n_fee_set_full_image_pattern_mode(self, n_fee_parameters: dict):
666
- v_start = n_fee_parameters.get("v_start", 0)
667
- v_end = n_fee_parameters.get("v_end", 1)
668
- sensor_sel_ = n_fee_parameters.get("sensor_sel", self.sensor_sel_both_sides)
669
- num_cycles = n_fee_parameters.get("num_cycles", 0)
670
- self._command_q.put((command_set_full_image_pattern_mode,
671
- [v_start, v_end, sensor_sel_],
672
- {'num_cycles': num_cycles}))
673
- LOGGER.debug(
674
- "Controller.n_fee_set_full_image_pattern_mode: Put command_set_full_image_pattern_mode "
675
- "on the Queue."
676
- )
677
- (cmd, response) = self._response_q.get()
678
- LOGGER.debug(
679
- f"Controller.n_fee_set_full_image_pattern_mode returned: ({cmd.__name__}, {response})"
680
- )
681
- return response
682
-
683
- def n_fee_high_precision_hk_mode(self, n_fee_parameters:dict):
684
- high_hk = n_fee_parameters.get("high_precision_hk", False)
685
- self._command_q.put((command_set_high_precision_hk_mode, [high_hk], {}))
686
- LOGGER.debug(
687
- "Controller.n_fee_set_high_precision_hk_mode: Put command_set_high_precision_hk_mode "
688
- "on the Queue."
689
- )
690
- (cmd, response) = self._response_q.get()
691
- LOGGER.debug(
692
- f"Controller.n_fee_set_high_precision_hk_mode returned: ({cmd.__name__}, {response})"
693
- )
694
- return response
695
-
696
- def n_fee_set_internal_sync(self, n_fee_parameters: dict):
697
- int_sync_period = n_fee_parameters.get("int_sync_period", 6250)
698
- self._command_q.put((command_internal_clock, [int_sync_period], {}))
699
- LOGGER.debug(
700
- "Controller.n_fee_set_internal_sync: Put command_internal_clock on the Queue."
701
- )
702
- (cmd, response) = self._response_q.get()
703
- LOGGER.debug(
704
- f"Controller.n_fee_set_internal_sync returned: ({cmd.__name__}, {response})"
705
- )
706
- return response
707
-
708
- def n_fee_set_external_sync(self, n_fee_parameters: dict):
709
- self._command_q.put((command_external_clock, [], {}))
710
- LOGGER.debug(
711
- "Controller.n_fee_set_internal_sync: Put command_internal_clock on the Queue."
712
- )
713
- (cmd, response) = self._response_q.get()
714
- LOGGER.debug(
715
- f"Controller.n_fee_set_internal_sync returned: ({cmd.__name__}, {response})"
716
- )
717
- return response
718
-
719
- def n_fee_set_register_value(self, reg_name: str, field_name: str, field_value: int):
720
- self._command_q.put((command_set_register_value, [reg_name, field_name, field_value], {}))
721
- LOGGER.debug(
722
- "Controller.n_fee_set_register_value: Put command_set_register_value on the Queue."
723
- )
724
- (cmd, response) = self._response_q.get()
725
- LOGGER.debug(
726
- f"Controller.n_fee_set_register_value returned: ({cmd.__name__}, {response})"
727
- )
728
- return response
729
-
730
- def n_fee_reset(self):
731
- self._command_q.put((command_reset, [], {}))
732
- LOGGER.debug(
733
- "Controller.n_fee_reset: Put command_reset on the Queue."
734
- )
735
- (cmd, response) = self._response_q.get()
736
- LOGGER.debug(f"Controller.n_fee_reset returned: ({cmd.__name__}, {response})")
737
- return response
738
-
739
- def n_fee_set_clear_error_flags(self):
740
-
741
- self._command_q.put((command_set_clear_error_flags, [], {}))
742
-
743
- LOGGER.debug("Controller.n_fee_set_clear_error_flags: "
744
- "Put command_set_clear_error_flags on the Queue.")
745
-
746
- (cmd, response) = self._response_q.get()
747
-
748
- LOGGER.debug(f"Controller.n_fee_set_clear_error_flags returned: ({cmd.__name__}, {response})")
749
- return response
750
-
751
- def n_fee_set_reverse_clocking(self, n_fee_parameters: dict):
752
- v_start = n_fee_parameters.get("v_start", 0)
753
- v_end = n_fee_parameters.get("v_end", 4509)
754
- sensor_sel_ = n_fee_parameters.get("sensor_sel", self.sensor_sel_both_sides)
755
- ccd_readout_order = n_fee_parameters.get("ccd_readout_order", self.default_ccd_readout_order)
756
- n_final_dump = n_fee_parameters.get("n_final_dump", 0)
757
- num_cycles = n_fee_parameters.get("num_cycles", 0)
758
- img_clk_dir = n_fee_parameters.get("img_clk_dir", 1)
759
- reg_clk_dir = n_fee_parameters.get("reg_clk_dir", 0)
760
- dump_mode_int = n_fee_parameters.get("dump_mode_int", False)
761
-
762
- self._command_q.put((command_set_reverse_clocking,
763
- [v_start, v_end, sensor_sel_, ccd_readout_order, n_final_dump, img_clk_dir, reg_clk_dir],
764
- {'num_cycles': num_cycles, 'dump_mode_int': dump_mode_int}))
765
- LOGGER.debug(
766
- "Controller.n_fee_set_reverse_clocking: Put command_set_reverse_clocking on the Queue."
767
- )
768
- (cmd, response) = self._response_q.get()
769
- LOGGER.debug(f"Controller.n_fee_set_reverse_clocking returned: ({cmd.__name__}, {response})")
770
- return response
771
-
772
- def n_fee_set_charge_injection(self, n_fee_parameters: dict):
773
- num_cycles = n_fee_parameters.get("num_cycles", 0)
774
- v_start = n_fee_parameters.get("v_start", 0)
775
- v_end = n_fee_parameters.get("v_end", 4509)
776
- n_final_dump = n_fee_parameters.get("n_final_dump", 0)
777
- sensor_sel_ = n_fee_parameters.get("sensor_sel", self.sensor_sel_both_sides)
778
- ccd_readout_order = n_fee_parameters.get("ccd_readout_order", self.default_ccd_readout_order)
779
- charge_injection_width = n_fee_parameters.get("charge_injection_width", 0)
780
- charge_injection_gap = n_fee_parameters.get("charge_injection_gap", 0)
781
-
782
- self._command_q.put(
783
- (
784
- command_set_charge_injection,
785
- [
786
- v_start, v_end, n_final_dump, sensor_sel_, ccd_readout_order,
787
- charge_injection_width, charge_injection_gap
788
- ],
789
- {'num_cycles': num_cycles}
790
- ))
791
- LOGGER.debug(
792
- "Controller.n_fee_set_dump_mode: Put command_set_charge_injection on the Queue."
793
- )
794
- (cmd, response) = self._response_q.get()
795
- LOGGER.debug(f"Controller.n_fee_set_charge_injection returned: ({cmd.__name__}, {response})")
796
- return response
797
-
798
- def n_fee_set_vgd(self, n_fee_parameters: dict):
799
-
800
- # The default value for ccd_vgd_config is 0xCFE = hex(int(19.90/5.983*1000))
801
- # This value is taken from: PLATO-MSSL-PL-FI-0001_9.0_N-FEE_Register_Map Draft A
802
-
803
- ccd_vgd_config = n_fee_parameters.get("ccd_vgd_config", 19.90)
804
-
805
- self._command_q.put((command_set_vgd, [ccd_vgd_config], {}))
806
-
807
- LOGGER.debug("Controller.n_fee_set_vgd: Put command_set_vgd on the Queue.")
808
-
809
- (cmd, response) = self._response_q.get()
810
-
811
- LOGGER.debug(f"Controller.n_fee_set_vgd returned: ({cmd.__name__}, {response})")
812
- return response
813
-
814
- def n_fee_set_fpga_defaults(self):
815
- """
816
- Loads the FPGA defaults from the Setup and commands the DPUProcessor to pass these defaults to the N-FEE.
817
-
818
- Returns:
819
- The response from the DPU Processor after executing the command.
820
- """
821
- fpga_defaults = self._setup.camera.fee.fpga_defaults
822
-
823
- self._command_q.put((command_set_nfee_fpga_defaults, [fpga_defaults], {}))
824
-
825
- LOGGER.debug("Controller.n_fee_set_fpga_defaults: Put command_set_nfee_fpga_defaults on the Queue.")
826
-
827
- (cmd, response) = self._response_q.get()
828
-
829
- LOGGER.debug(f"Controller.n_fee_set_fpga_defaults returned: ({cmd.__name__}, {response})")
830
- return response
831
-
832
-
833
- class DPUProxy(Proxy, DPUInterface):
834
- """
835
- The DPUProxy class is used to connect to the DPU Control Server and send commands to the FEE.
836
- """
837
-
838
- def __init__(
839
- self,
840
- protocol=CTRL_SETTINGS.PROTOCOL,
841
- hostname=CTRL_SETTINGS.HOSTNAME,
842
- port=CTRL_SETTINGS.COMMANDING_PORT,
843
- ):
844
- """
845
- Args:
846
- protocol: the transport protocol [default is taken from settings file]
847
- hostname: location of the control server (IP address)
848
- [default is taken from settings file]
849
- port: TCP port on which the control server is listening for commands
850
- [default is taken from settings file]
851
- """
852
- super().__init__(connect_address(protocol, hostname, port), timeout=10_000)
853
-
854
-
855
- class DPUCommand(ClientServerCommand):
856
- pass
857
-
858
-
859
- class DPUProtocol(CommandProtocol):
860
- def __init__(self, control_server: ControlServer, transport: SpaceWireInterface):
861
-
862
- super().__init__()
863
-
864
- self.control_server = control_server
865
-
866
- # Set up two queue's to communicate with the DPU Processor Process.
867
- # The command queue is joinable because the Controller needs to wait for a response in
868
- # the response queue.
869
-
870
- self.command_queue = multiprocessing.Queue()
871
- self.response_queue = multiprocessing.Queue()
872
- self.priority_queue = multiprocessing.Queue()
873
-
874
- # Start a separate Process to handle FEE communication
875
-
876
- self.processor = DPUProcessor(
877
- transport, self.priority_queue, self.command_queue, self.response_queue)
878
- self.processor.name = "dpu.processor"
879
- self.processor.start()
880
-
881
- self.controller = DPUController(
882
- self.priority_queue, self.command_queue, self.response_queue)
883
-
884
- self.load_commands(COMMAND_SETTINGS.Commands, DPUCommand, DPUController)
885
-
886
- self.build_device_method_lookup_table(self.controller)
887
-
888
- def get_bind_address(self):
889
- return bind_address(
890
- self.control_server.get_communication_protocol(),
891
- self.control_server.get_commanding_port(),
892
- )
893
-
894
- def get_status(self) -> dict:
895
- status = super().get_status()
896
- status["DPU Processor"] = "alive" if self.processor.is_alive() else "--"
897
- return status
898
-
899
- def get_housekeeping(self) -> dict:
900
- return {
901
- "timestamp": format_datetime(),
902
- }
903
-
904
- def quit(self):
905
- self.processor.quit()
906
-
907
- def not_alive():
908
- return not self.processor.is_alive()
909
-
910
- if wait_until(not_alive, timeout=6.5) is False:
911
- self.processor.join()
912
- return
913
-
914
- LOGGER.warning("Terminating DPU Processor")
915
- self.processor.terminate()
916
-
917
- # Wait at least 6.25s which is the 'normal' readout cycle time
918
-
919
- if wait_until(not_alive, timeout=6.5) is False:
920
- self.processor.join()
921
- return
922
-
923
- LOGGER.warning("Killing DPU Processor")
924
- self.processor.kill()
925
- self.processor.join()
926
-
927
- def is_alive(self) -> bool:
928
- is_alive = self.processor.is_alive()
929
-
930
- if not is_alive:
931
- LOGGER.warning(
932
- f"Process '{self.processor.name}' died for some reason, check for "
933
- f"an exception in the logging output."
934
- )
935
-
936
- return is_alive
937
-
938
-
939
- DPU_PROCESSOR_SETTINGS = Settings.load("DPU Processor")
940
-
941
-
942
- class DPUMonitoring:
943
- """
944
- The DPUMonitoring class allows you to execute a function synchronised to the reception of a
945
- timecode or a housekeeping packet from the N-FEE.
946
-
947
- Args:
948
- timeout: time to wait for a message before a timeout [default=30s]
949
- """
950
- def __init__(self, timeout: float = 30):
951
- self._context = zmq.Context.instance()
952
- self._endpoint = connect_address('tcp', DPU_PROCESSOR_SETTINGS.HOSTNAME, DPU_PROCESSOR_SETTINGS.MONITORING_PORT)
953
- self._multipart = True
954
- self._timeout = timeout # seconds
955
- self._retries = 3
956
- self._socket = None
957
- self._subscriptions = set()
958
-
959
- def __enter__(self):
960
- self.connect()
961
- return self
962
-
963
- def __exit__(self, exc_type, exc_val, exc_tb):
964
- if not self._socket.closed:
965
- self.disconnect()
966
-
967
- def connect(self):
968
- self._socket = self._context.socket(zmq.SUB)
969
- self._socket.connect(self._endpoint)
970
-
971
- # subscribe_string = b''
972
- # self._socket.subscribe(subscribe_string)
973
- # self._subscriptions.add(subscribe_string)
974
-
975
- def disconnect(self):
976
- self._socket.close(linger=0)
977
- self._subscriptions.clear()
978
-
979
- def unsubscribe_all(self):
980
- for sub in self._subscriptions:
981
- self._socket.unsubscribe(sub)
982
- self._subscriptions.clear()
983
-
984
- def unsubscribe(self, sync_id: int):
985
- subscribe_string = sync_id.to_bytes(1, byteorder='big') if sync_id else b''
986
- try:
987
- self._subscriptions.remove(subscribe_string)
988
- self._socket.unsubscribe(subscribe_string)
989
- except KeyError:
990
- LOGGER.warning(
991
- f"Trying to unsubscribe a key that was not previously subscribed: {subscribe_string}"
992
- )
993
-
994
- def subscribe(self, sync_id: int = None):
995
- subscribe_string = sync_id.to_bytes(1, byteorder='big') if sync_id else b''
996
-
997
- if subscribe_string in self._subscriptions:
998
- return
999
-
1000
- self._socket.subscribe(subscribe_string)
1001
- self._subscriptions.add(subscribe_string)
1002
-
1003
- def wait_for_timecode(self) -> Tuple[int, str]:
1004
- """
1005
- Connects to the monitoring socket of the DPU Processor and returns when a TIMECODE
1006
- synchronisation message is received.
1007
-
1008
- Returns:
1009
- A tuple of the timecode (int) and the corresponding timestamp (str).
1010
- """
1011
- self.unsubscribe_all()
1012
- self.subscribe(MessageIdentifier.SYNC_TIMECODE)
1013
-
1014
- retries = self._retries
1015
-
1016
- while True:
1017
- rlist, _, _ = zmq.select([self._socket], [], [], timeout=self._timeout)
1018
- if self._socket in rlist:
1019
- if self._multipart:
1020
- sync_id, pickle_string = self._socket.recv_multipart()
1021
- sync_id = int.from_bytes(sync_id, byteorder='big')
1022
- else:
1023
- sync_id = MessageIdentifier.ALL
1024
- pickle_string = self._socket.recv()
1025
- timecode, timestamp = pickle.loads(pickle_string)
1026
-
1027
- LOGGER.debug(f"{MessageIdentifier(sync_id).name}, {timecode}, {timestamp}")
1028
-
1029
- return timecode, timestamp
1030
- else:
1031
- retries -= 1
1032
- LOGGER.warning(f"Monitoring timeout, {retries} retries to go")
1033
- if retries <= 0:
1034
- raise TimeoutError(f"DPUMonitoring timed out after {self._retries * self._timeout} seconds.")
1035
-
1036
- def wait_for_hdf5_filename(self, retries: int = None, timeout: float = None) -> List[Path]:
1037
- """
1038
- Connects to the monitoring socket of the DPU Processor and returns a list of path names that
1039
- were part of the current registration in the Storage, right before a new registration was
1040
- initiated.
1041
-
1042
- This method is mainly intended to be used by processes that need to work with the generated
1043
- HDF5 files after they have been closed by the DPU Processor. One of these processes is the
1044
- FITS generation.
1045
-
1046
- Notes:
1047
- The path names that are returned are absolute filenames that are specific for the
1048
- egse-server on which the DPU Processor is running. These files might not be accessible
1049
- from the machine you are running this monitoring request.
1050
-
1051
- Returns:
1052
- A list of path names.
1053
- Raises:
1054
- A TimeoutError when no sync data was received from the monitoring socket after 30s.
1055
- """
1056
- self.unsubscribe_all()
1057
- self.subscribe(MessageIdentifier.HDF5_FILENAMES)
1058
-
1059
- retries = retries if retries is not None else self._retries
1060
- timeout = timeout or self._timeout
1061
-
1062
- while True:
1063
- rlist, _, _ = zmq.select([self._socket], [], [], timeout=timeout)
1064
- if self._socket in rlist:
1065
- if self._multipart:
1066
- sync_id, pickle_string = self._socket.recv_multipart()
1067
- sync_id = int.from_bytes(sync_id, byteorder='big')
1068
- else:
1069
- sync_id = MessageIdentifier.ALL
1070
- pickle_string = self._socket.recv()
1071
- filenames = pickle.loads(pickle_string)
1072
-
1073
- LOGGER.debug(f"{MessageIdentifier(sync_id).name}, {filenames}")
1074
-
1075
- return filenames
1076
- else:
1077
- retries -= 1
1078
- # LOGGER.warning(f"Monitoring timeout, {retries} retries to go")
1079
- if retries <= 0:
1080
- raise TimeoutError(f"DPUMonitoring timed out after {self._retries * self._timeout} seconds.")
1081
-
1082
- def wait_number_of_pulses(self, num_pulses: int) -> None:
1083
- """
1084
- Wait for a number of pulses (long and short), then return.
1085
-
1086
- When the number of pulses has been received, the function returns right after the timecode synchronisation
1087
- message. Any command that is sent to the N-FEE immediately after this function returns will be processes within
1088
- that same readout frame, i.e. before the next sync pulse.
1089
-
1090
- Args:
1091
- num_pulses: the number of sync pulses to wait before returning.
1092
-
1093
- Raises:
1094
- A TimeoutError when no sync data was received from the monitoring socket after 30s.
1095
- """
1096
- self.unsubscribe_all()
1097
- self.subscribe(MessageIdentifier.SYNC_TIMECODE)
1098
-
1099
- retries = self._retries
1100
-
1101
- LOGGER.debug(f"Waiting for {num_pulses} pulses...")
1102
-
1103
- while True:
1104
- rlist, _, _ = zmq.select([self._socket], [], [], timeout=self._timeout)
1105
- if self._socket in rlist:
1106
- if self._multipart:
1107
- sync_id, pickle_string = self._socket.recv_multipart()
1108
- sync_id = int.from_bytes(sync_id, byteorder='big')
1109
- else:
1110
- sync_id = MessageIdentifier.ALL
1111
- pickle_string = self._socket.recv()
1112
- timecode, timestamp = pickle.loads(pickle_string)
1113
-
1114
- num_pulses -= 1
1115
- LOGGER.debug(f"{MessageIdentifier(sync_id).name}, {timecode=}, {timestamp=}, {num_pulses=}")
1116
- if num_pulses <= 0:
1117
- return
1118
-
1119
- retries = self._retries # reset the number of retries
1120
- else:
1121
- retries -= 1
1122
- LOGGER.warning(f"Monitoring timeout, {retries} retries to go")
1123
- if retries <= 0:
1124
- raise TimeoutError(f"DPUMonitoring timed out after {self._retries * self._timeout} seconds.")
1125
-
1126
- def wait_num_cycles(self, num_cycles: int, return_on_frame: int = 3) -> int:
1127
- """
1128
- Wait for a number of long pulses (cycles), then return.
1129
-
1130
- This method will wait for full cycles, i.e. 4 readouts in external sync mode, 1 readout in internal sync mode,
1131
- and will return immediately after receiving the HK sync pulse for the last frame in the requested cycle, i.e.
1132
- frame number == 3 for external sync and frame number == 0 for internal sync. If an RMAP command is then sent
1133
- when the function returns, it will still be executed within that frame and the changed register settings will
1134
- become active on the next pulse, which is a long pulse, the start of the next cycle.
1135
- That way we do not lose a cycle.
1136
-
1137
- Args:
1138
- num_cycles: the number of full cycles to wait before returning
1139
- return_on_frame: choose the readout frame on which to return [default = 3]
1140
-
1141
- Returns:
1142
- Zero (0) when no cycles were waited because num_cycles <= 0, otherwise return value > 0.
1143
-
1144
- Raises:
1145
- A TimeoutError when no sync data was received from the monitoring socket after 30s.
1146
- """
1147
- self.unsubscribe_all()
1148
- self.subscribe(MessageIdentifier.SYNC_HK_PACKET)
1149
-
1150
- retries = self._retries
1151
- count = 0
1152
-
1153
- if num_cycles <= 0:
1154
- LOGGER.debug(f"{num_cycles=}, no cycles waited, returned immediately")
1155
- return count
1156
-
1157
- LOGGER.debug(f"Waiting for {num_cycles} cycles...")
1158
-
1159
- with Timer("Loop cycles") as timer, DPUProxy() as dpu_proxy:
1160
-
1161
- # When we are in external sync mode, we need to skip the current cycle,
1162
- # because the requested changes in the register -> FPGA will ony occur on
1163
- # the next long pulse. No need for this when in internal sync mode.
1164
-
1165
- sync_mode = dpu_proxy.n_fee_get_sync_mode()
1166
- if sync_mode == 0:
1167
- num_cycles += 1
1168
-
1169
- while True:
1170
- count += 1
1171
- rlist, _, _ = zmq.select([self._socket], [], [], timeout=self._timeout)
1172
- if self._socket in rlist:
1173
- if self._multipart:
1174
- sync_id, pickle_string = self._socket.recv_multipart()
1175
- sync_id = int.from_bytes(sync_id, byteorder='big')
1176
- else:
1177
- sync_id = MessageIdentifier.ALL
1178
- pickle_string = self._socket.recv()
1179
- status = pickle.loads(pickle_string)
1180
-
1181
- LOGGER.debug(f"{MessageIdentifier(sync_id).name}, {to_string(status[0])}")
1182
-
1183
- sync_mode = dpu_proxy.n_fee_get_sync_mode()
1184
-
1185
- if (sync_mode == 1) or (packet := status[0]) and packet.frame_number == return_on_frame:
1186
- num_cycles -= 1
1187
-
1188
- LOGGER.debug(f"NUM_CYCLES={num_cycles}, {sync_mode=}")
1189
-
1190
- if num_cycles <= 0:
1191
- return count
1192
-
1193
- retries = self._retries # reset the number of retries
1194
- else:
1195
- retries -= 1
1196
- LOGGER.warning(f"Monitoring timeout, {retries} retries to go")
1197
- if retries <= 0:
1198
- raise TimeoutError(f"DPUMonitoring timed out after {self._retries * self._timeout} seconds.")
1199
-
1200
- timer.log_elapsed()
1201
-
1202
- def monitor_all(self):
1203
- self.subscribe()
1204
-
1205
- retries = self._retries
1206
-
1207
- while True:
1208
- rlist, _, _ = zmq.select([self._socket], [], [], timeout=self._timeout)
1209
- if self._socket in rlist:
1210
- if self._multipart:
1211
- sync_id, pickle_string = self._socket.recv_multipart()
1212
- sync_id = int.from_bytes(sync_id, byteorder='big')
1213
- else:
1214
- sync_id = MessageIdentifier.ALL
1215
- pickle_string = self._socket.recv()
1216
-
1217
- msg = pickle.loads(pickle_string)
1218
- if sync_id == MessageIdentifier.SYNC_TIMECODE:
1219
- msg = f"timestamp={msg[1]}, timecode={msg[0]}"
1220
- LOGGER.info(f"{MessageIdentifier(sync_id).name}, {msg}")
1221
- elif sync_id == MessageIdentifier.SYNC_HK_PACKET:
1222
- msg = f"timestamp={msg[1]}, packet type={to_string(msg[0])}"
1223
- LOGGER.info(f"{MessageIdentifier(sync_id).name}, {msg}")
1224
- elif sync_id == MessageIdentifier.NUM_CYCLES:
1225
- LOGGER.info(f"{MessageIdentifier(sync_id).name}, num_cycles={msg}")
1226
-
1227
- retries = self._retries # reset the number of retries
1228
- else:
1229
- retries -= 1
1230
- LOGGER.warning(f"Monitoring timeout, {retries} retries to go")
1231
- if retries <= 0:
1232
- raise TimeoutError(f"DPUMonitoring timed out after {self._retries * self._timeout} seconds.")
1233
-
1234
- def wait_until_synced_num_cycles_is_zero(self):
1235
- """
1236
- Wait until the synced num_cycles turns zero, then return. The synced num_cycles is
1237
- the num_cycles that is maintained by the DPU Processor and which is distributed by the
1238
- DPU Processor on every 400ms pulse.
1239
-
1240
- Raises:
1241
- A TimeoutError when no sync data was received from the monitoring socket after 30s.
1242
- """
1243
- self.unsubscribe_all()
1244
- self.subscribe(MessageIdentifier.NUM_CYCLES)
1245
-
1246
- retries = self._retries
1247
-
1248
- while True:
1249
- rlist, _, _ = zmq.select([self._socket], [], [], timeout=self._timeout)
1250
- if self._socket in rlist:
1251
- if self._multipart:
1252
- sync_id, pickle_string = self._socket.recv_multipart()
1253
- sync_id = int.from_bytes(sync_id, byteorder='big')
1254
- else:
1255
- sync_id = MessageIdentifier.ALL
1256
- pickle_string = self._socket.recv()
1257
-
1258
- synced_num_cycles = pickle.loads(pickle_string)
1259
-
1260
- LOGGER.info(f"{MessageIdentifier(sync_id).name} = {synced_num_cycles}")
1261
-
1262
- if synced_num_cycles <= 0:
1263
- return
1264
-
1265
- retries = self._retries # reset the number of retries
1266
- else:
1267
- retries -= 1
1268
- LOGGER.warning(f"Monitoring timeout, {retries} retries to go")
1269
- if retries <= 0:
1270
- raise TimeoutError(f"DPUMonitoring timed out after {self._retries * self._timeout} seconds.")
1271
-
1272
- def do(self, func: Callable, *args, **kwargs):
1273
- return func(*args, **kwargs)
1274
-
1275
- def on_long_pulse_do(self, func: Callable, *args, **kwargs):
1276
- """
1277
- Connects to the monitoring socket of the DPU Processor and executes the given function
1278
- when the frame_number == 0, i.e. right after a long pulse.
1279
-
1280
- Args:
1281
- func (Callable): the function to synchronise
1282
- *args: any arguments to pass to the function
1283
- **kwargs: any keyword arguments to pass to the function
1284
-
1285
- Returns:
1286
- The return value of the called function.
1287
-
1288
- Raises:
1289
- A TimeoutError when no sync data was received from the monitoring socket after 30s.
1290
- """
1291
- return self.on_frame_number_do(0, func, *args, **kwargs)
1292
-
1293
- def on_frame_number_do(self, frame_number: int, func: Callable, *args, **kwargs):
1294
- """
1295
- Connects to the monitoring socket of the DPU Processor and executes the given function
1296
- when the given frame_number is reached. This allows to send N-FEE commands right before the long pulse.
1297
-
1298
- Args:
1299
- frame_number: the frame number on which to execute the function
1300
- func (Callable): the function to synchronise
1301
- *args: any arguments to pass to the function
1302
- **kwargs: any keyword arguments to pass to the function
1303
-
1304
- Returns:
1305
- The return value of the called function.
1306
-
1307
- Raises:
1308
- A TimeoutError when no sync data was received from the monitoring socket after 30s.
1309
- """
1310
- self.unsubscribe_all()
1311
- self.subscribe(MessageIdentifier.SYNC_HK_PACKET)
1312
-
1313
- retries = self._retries
1314
-
1315
- while True:
1316
- rlist, _, _ = zmq.select([self._socket], [], [], timeout=self._timeout)
1317
- if self._socket in rlist:
1318
- if self._multipart:
1319
- sync_id, pickle_string = self._socket.recv_multipart()
1320
- sync_id = int.from_bytes(sync_id, byteorder='big')
1321
- else:
1322
- sync_id = MessageIdentifier.ALL
1323
- pickle_string = self._socket.recv()
1324
- status = pickle.loads(pickle_string)
1325
-
1326
- LOGGER.debug(f"{MessageIdentifier(sync_id).name}, {status[0]}")
1327
-
1328
- packet: DataPacketType = status[0]
1329
- if packet and packet.frame_number == frame_number:
1330
- return func(*args, **kwargs)
1331
- else:
1332
- retries -= 1
1333
- LOGGER.warning(f"Monitoring timeout, {retries} retries to go")
1334
- if retries <= 0:
1335
- raise TimeoutError(f"DPUMonitoring timed out after {self._retries * self._timeout} seconds.")
1336
-
1337
-
1338
- @dataclass
1339
- class DPUInternals:
1340
-
1341
- # The number of readout cycles requested by the user. A cycle is the period between two long
1342
- # pulses (400ms). When num_cycle == 0, the N-FEE will be instructed to go to dump mode, when
1343
- # num_cycle < 0 nothing will be done.
1344
- num_cycles: int
1345
-
1346
- # The expected last packet flags tell you if for a certain ccd side and packet a last packet
1347
- # flag is expected. This is similar to saying if such a packet is to be expected from the N-FEE.
1348
- expected_last_packet_flags: List[int]
1349
-
1350
- # DUMP mode is not a real N-FEE mode, but is defined in the DPU Processor to make sure the CCDs
1351
- # will not saturate when we are not reading out image data. The conditions for a dump mode are
1352
- # the register map flags 'digitise_en' being False and 'DG_high' being True.
1353
- dump_mode: bool = False
1354
-
1355
- # The internal sync flag is set to True whenever the register map parameter 'sync_sel' is True.
1356
- internal_sync: bool = False
1357
-
1358
- # This flag is set to True when the N-FEE shall be put into dump mode internal sync after
1359
- # num_cycles becomes zero.
1360
- dump_mode_int: bool = False
1361
-
1362
- # The current frame number. This value needs to be updated as soon as the housekeeping packet
1363
- # is received.
1364
- frame_number: int = -1
1365
-
1366
- # Enumeration with the information about E and F, based on the setup (camera-dependent)
1367
- ccd_sides_enum: Enum = None
1368
-
1369
- # Enumeration with the sensor_sel
1370
- sensor_sel_enum: Enum = None
1371
-
1372
- # Mapping of the CCD identifier to the binary representation (loaded from the Setup)
1373
- ccd_id_to_bin: List[int] = None
1374
-
1375
- # The clear_error_flags shall be executed on every readout, i.e. every 200ms and 400ms pulse.
1376
- clear_error_flags = False
1377
-
1378
- # The number of cycles that will be used for slicing the FITS files. This parameter is
1379
- # saved in the HDF5 file upon reception.
1380
- slicing_num_cycles = 0
1381
-
1382
- # When in internal sync the ccd_readout_order is not used like in external sync mode.
1383
- # Each readout is done on the same CCD, i.e. the first CCD number in the ccd_readout_order list.
1384
- # Therefore, we will rotate this list on each readout in internal sync dump mode to guarantee
1385
- # each CCD is cleared out.
1386
-
1387
- # Initialise cycling of CCDs in internal sync dump mode to the default CCD numbering.
1388
- current_ccd_readout_order = CCD_NUMBERS
1389
-
1390
- # The cycle_count goes from [0 -> 3] to make sure that, during internal sync dump mode, we have cleared out
1391
- # all four CCDs. A clear-out cycle can only be interrupted when cycle_count == 0, at that time all commands
1392
- # on the queue will be executed.
1393
- cycle_count = 0
1394
-
1395
- def reset_int_sync_dump_mode(self, ccd_numbers: list = None):
1396
- """
1397
- Resets the cycle_count to zero (0) and the current ccd_readout_order to the given ccd_numbers.
1398
- When ccd_numbers is None the default CCD readout order will be used, i.e. CCD_NUMBERS, [1,2,3,4].
1399
-
1400
- Args:
1401
- ccd_numbers: a list of four CCD numbers going from 1 to 4.
1402
-
1403
- """
1404
- self.current_ccd_readout_order = ccd_numbers or CCD_NUMBERS
1405
- self.cycle_count = 0
1406
-
1407
- def int_sync_cycle_dump_mode(self):
1408
- """Returns True if we are in internal sync dump mode."""
1409
- return self.internal_sync and self.dump_mode and self.num_cycles < 0
1410
-
1411
- def is_start_of_cycle(self):
1412
- """
1413
- Returns True if in the first readout in this cycle, i.e. frame number is 0.
1414
- """
1415
-
1416
- return self.frame_number == 0
1417
-
1418
- def is_end_of_cycle(self):
1419
- """
1420
- Returns True if in the last readout in this cycle.
1421
- Note that, when in internal sync mode, this method always returns True.
1422
- """
1423
- return True if self.internal_sync else self.frame_number == 3
1424
-
1425
- def is_400ms_pulse(self):
1426
- return self.frame_number == 0
1427
-
1428
- def is_200ms_pulse(self):
1429
- return self.frame_number in [1, 2, 3]
1430
-
1431
- def update(self, n_fee_state: NFEEState.StateTuple):
1432
- self.dump_mode = n_fee_state.ccd_mode_config == n_fee_mode.FULL_IMAGE_MODE and not bool(n_fee_state.digitise_en)
1433
- self.internal_sync = bool(n_fee_state.sync_sel)
1434
- self.expected_last_packet_flags = create_expected_last_packet_flags(n_fee_state, self.sensor_sel_enum)
1435
-
1436
-
1437
- class DPUProcessor(multiprocessing.Process):
1438
- """
1439
- The DPU Processor handles all interactions with the FEE. It reads the packets from the FEE
1440
- within the readout time frame, and sends commands to the FEE through the RMAP protocol.
1441
-
1442
- The commands are read from a commanding queue which is shared between the DPU Processor and
1443
- the DPU Controller. Any response from the FEE is put on the response queue which is also
1444
- shared between the processor and the controller.
1445
-
1446
- The transport mechanism that is used to read and write SpaceWire packets is abstracted into a
1447
- SpaceWireInterface. That allows us to interact with the FEE through different hardware
1448
- channels, e.g. a SpaceWire interface (DSI) or a ZeroMQ DEALER-DEALER protocol for
1449
- communication with the FEE Simulator.
1450
- """
1451
-
1452
- def __init__(
1453
- self,
1454
- transport: SpaceWireInterface,
1455
- priority_queue: multiprocessing.Queue,
1456
- command_queue: multiprocessing.Queue,
1457
- response_queue: multiprocessing.Queue,
1458
- ):
1459
-
1460
- super().__init__()
1461
-
1462
- self._transport = transport
1463
- self._priority_q = priority_queue
1464
- self._command_q = command_queue
1465
- self._response_q = response_queue
1466
- self.register_map = RegisterMap("N-FEE")
1467
- self._quit_event = multiprocessing.Event()
1468
-
1469
- # The following variables will be initialised in the run() method.
1470
-
1471
- self._setup = None
1472
- self._dpu_internals = None
1473
-
1474
- # These will be properly initialized when the register map is read from the N-FEE.
1475
-
1476
- self._n_fee_state = NFEEState()
1477
-
1478
- def run(self):
1479
-
1480
- self._setup = load_setup()
1481
- if self._setup is None:
1482
- raise SetupError("Couldn't load the current Setup from the configuration manager.")
1483
-
1484
- self._dpu_internals = DPUInternals(
1485
- num_cycles=-1,
1486
- expected_last_packet_flags=[False, False, False, False],
1487
- dump_mode=False,
1488
- internal_sync=False,
1489
- frame_number=-1,
1490
- ccd_sides_enum=self._setup.camera.fee.ccd_sides.enum,
1491
- sensor_sel_enum=self._setup.camera.fee.sensor_sel.enum,
1492
- ccd_id_to_bin=self._setup.camera.fee.ccd_numbering.CCD_ID_TO_BIN,
1493
- )
1494
-
1495
- # The DPU Processor runs in a different process and since ZeroMQ Sockets are not
1496
- # thread/process safe, we have to recreate the ZeroMQHandler attached to the egse.logger
1497
- # in this process.
1498
- import egse.logger
1499
- egse.logger.replace_zmq_handler()
1500
-
1501
- LOGGER.info("DPU Processor started.")
1502
-
1503
- self._killer = SignalCatcher()
1504
-
1505
- # Setup a SpaceWire connection with the FEE (Simulator) and
1506
- # open a Storage proxy to save all the data packets.
1507
-
1508
- origin_spw_data = N_FEE_SETTINGS.ORIGIN_SPW_DATA
1509
- origin_spw_data_type = DATA_TYPE[N_FEE_SETTINGS.ORIGIN_SPW_DATA_TYPE]
1510
-
1511
- ctx: zmq.Context = zmq.Context().instance()
1512
-
1513
- # Setup monitoring socket
1514
-
1515
- mon_sock: zmq.Socket = ctx.socket(zmq.PUB)
1516
- endpoint = bind_address("tcp", DPU_PROCESSOR_SETTINGS.MONITORING_PORT)
1517
- mon_sock.bind(endpoint)
1518
- LOGGER.info(f"DPU Processor sending monitoring sync signals to {endpoint}.")
1519
-
1520
- # Setup data distribution socket
1521
-
1522
- dist_sock: zmq.Socket = ctx.socket(zmq.PUB)
1523
- endpoint = bind_address("tcp", DPU_PROCESSOR_SETTINGS.DATA_DISTRIBUTION_PORT)
1524
- dist_sock.setsockopt(zmq.SNDHWM, 0) # never block on sending msg
1525
- dist_sock.bind(endpoint)
1526
-
1527
- LOGGER.info(f"DPU Processor sending SpW data to {endpoint}.")
1528
-
1529
- with self._transport, StorageProxy() as storage, ConfigurationManagerProxy() as cm:
1530
- LOGGER.info("SpaceWire Transport has been connected.")
1531
- self._transport.configure()
1532
- LOGGER.info("SpaceWire Transport has been configured.")
1533
-
1534
- LOGGER.info(f"Register {origin_spw_data} to Storage")
1535
- register_to_storage_manager(storage, origin_spw_data)
1536
-
1537
- # Before going into the wile-loop, read the full register from the N-FEE and initialise
1538
- # the register map.
1539
-
1540
- try:
1541
- self.initialise_register_map()
1542
- save_register_map(self.register_map, storage, origin_spw_data, dist_sock)
1543
- save_format_version(storage, origin_spw_data)
1544
- save_obsid(storage, origin_spw_data, cm.get_obsid().return_code)
1545
- except Abort:
1546
- LOGGER.warning("The DPU Processor is aborting....")
1547
- unregister_from_storage_manager(storage, origin_spw_data)
1548
- LOGGER.info(f"The DPU Processor unregistered {origin_spw_data} from the Storage.")
1549
- return
1550
- except Exception as exc:
1551
- LOGGER.error(exc, exc_info=exc)
1552
-
1553
- # Initialise the N-FEE state from the register map
1554
-
1555
- self._n_fee_state.update_at_400ms(self.register_map)
1556
-
1557
- # Initialise the DPU internals from the N-FEE State
1558
-
1559
- self._dpu_internals.update(self._n_fee_state.get_state())
1560
-
1561
- LOGGER.debug(f"{self._dpu_internals.dump_mode=}")
1562
- LOGGER.debug(f"{self._dpu_internals.internal_sync=}")
1563
- LOGGER.debug(f"{self._dpu_internals.expected_last_packet_flags=}")
1564
-
1565
- # Initialise the data attributes, they will be added as attributes to the data group
1566
- # in the HDF5 file.
1567
-
1568
- data_attr = self._n_fee_state.get_state()._asdict()
1569
-
1570
- # Initialise the start_time. This is needed, because when a NoTimeCodeError occurs
1571
- # the variable will not be initialised resulting in a critical error.
1572
-
1573
- start_time = time.perf_counter()
1574
-
1575
- try:
1576
- LOGGER.info("Going into the while True loop...")
1577
- while True:
1578
-
1579
- try:
1580
- # First two packets are a Timecode and a HK packet ------------------------
1581
-
1582
- tc_packet, timestamp, start_time = read_timecode(self._transport)
1583
-
1584
- hk_packet, timestamp = read_hk_packet(self._transport)
1585
-
1586
- self._dpu_internals.frame_number = hk_packet.type.frame_number
1587
- self._dpu_internals.clear_error_flags = True
1588
-
1589
- # Create a new HDF5 file for each readout cycle ----------------------------
1590
-
1591
- if self._dpu_internals.is_start_of_cycle():
1592
- with Timer("Creating a new data file"):
1593
- new_spw_data_file(storage, self.register_map, origin_spw_data,
1594
- origin_spw_data_type, mon_sock, dist_sock)
1595
- save_obsid(storage, origin_spw_data, cm.get_obsid().return_code)
1596
- save_num_cycles(storage, origin_spw_data, self._dpu_internals.num_cycles)
1597
-
1598
- # Update the N-FEE state (FPGA) --------------------------------------------
1599
-
1600
- if self._dpu_internals.is_400ms_pulse():
1601
- self._n_fee_state.update_at_400ms(self.register_map)
1602
- elif self._dpu_internals.is_200ms_pulse():
1603
- self._n_fee_state.update_at_200ms(self.register_map)
1604
- else:
1605
- pass # we are entering the loop for the first time
1606
-
1607
- # Update the DPU internals from the N-FEE state
1608
-
1609
- self._dpu_internals.update(self._n_fee_state.get_state())
1610
-
1611
- # Process and save the timecode and HK packet ------------------------------
1612
-
1613
- process_timecode(tc_packet, timestamp, storage, origin_spw_data,
1614
- self._dpu_internals.frame_number, mon_sock, dist_sock)
1615
-
1616
- process_hk_packet(hk_packet, timestamp, storage, origin_spw_data,
1617
- self._dpu_internals.frame_number, mon_sock, dist_sock)
1618
-
1619
- process_high_priority_commands(self._priority_q, self._response_q,
1620
- self._n_fee_state.get_state(),
1621
- self._dpu_internals, self.register_map)
1622
-
1623
- # On any new readout cycle (400ms pulse), update the state and the internals
1624
-
1625
- # FIXME: Why is this test done here and not at the end of the while loop
1626
- # when all data has been read?
1627
-
1628
- if self._dpu_internals.is_400ms_pulse():
1629
-
1630
- pickle_string = pickle.dumps(self._dpu_internals.num_cycles)
1631
- msg_id = MessageIdentifier.NUM_CYCLES.to_bytes(1, 'big')
1632
- num_cycles_msg = [msg_id, pickle_string]
1633
- dist_sock.send_multipart(num_cycles_msg)
1634
- mon_sock.send_multipart(num_cycles_msg)
1635
-
1636
- # decrement num_cycles, this can go negative which is interpreted as
1637
- # not doing anything...
1638
-
1639
- self._dpu_internals.num_cycles -= 1 # check issue #917 before changing this line
1640
-
1641
- LOGGER.debug(
1642
- f"HK: frame number={hk_packet.type.frame_number}, dump mode={self._dpu_internals.dump_mode}, num_cycles={self._dpu_internals.num_cycles}"
1643
- )
1644
-
1645
- LOGGER.debug(
1646
- f"FEE mode in register map: {n_fee_mode(self.register_map['ccd_mode_config']).name}"
1647
- )
1648
-
1649
- save_slicing_parameter(storage, origin_spw_data, self._dpu_internals.slicing_num_cycles)
1650
-
1651
- if self._dpu_internals.is_end_of_cycle():
1652
-
1653
- # When we are at the end of our requested num_cycles, go to DUMP mode
1654
-
1655
- # FIXME: review if this is the right place and if the dump command will
1656
- # be executed at the right moment, e.g. are there no commands on
1657
- # the queue anymore?
1658
-
1659
- if self._dpu_internals.num_cycles == 0:
1660
- if self._dpu_internals.dump_mode_int:
1661
- self._dpu_internals.reset_int_sync_dump_mode()
1662
- dump_mode_command = command_set_dump_mode_int_sync
1663
- else:
1664
- dump_mode_command = command_set_dump_mode
1665
- self._command_q.put((dump_mode_command, [], {'response': False}))
1666
-
1667
- # Then we might get data packets depending on the FEE mode -----------------
1668
-
1669
- mode = hk_packet.type.mode
1670
- LOGGER.debug(f"FEE mode in HK packet: {n_fee_mode(mode).name}")
1671
-
1672
- data_attr = update_data_attributes(data_attr, self._n_fee_state.get_state())
1673
-
1674
- with Timer("Read and process data packets"):
1675
- read_and_process_data_packets(
1676
- self._transport, storage, origin_spw_data, start_time, mode,
1677
- self.register_map, data_attr, self._dpu_internals, dist_sock)
1678
-
1679
- # Read HK packet from N-FEE memory map
1680
- # see #2478 [https://github.com/IvS-KULeuven/plato-common-egse/issues/2478]
1681
-
1682
- time.sleep(0.012) # add 12ms to make sure HK data has been updated on the N-FEE
1683
-
1684
- with Timer("Read and process updated HK data"):
1685
- hk_data, timestamp = read_updated_hk_data(self._transport)
1686
- process_updated_hk_data(hk_data, timestamp, storage, origin_spw_data,
1687
- self._dpu_internals.frame_number, mon_sock, dist_sock)
1688
-
1689
- if self._dpu_internals.int_sync_cycle_dump_mode():
1690
- LOGGER.warning("Cycling CCD readout in internal sync")
1691
-
1692
- # When we are in internal sync and dump mode, we need to cycle through the four CCDs, and
1693
- # we need an atomic block of four clear outs.
1694
-
1695
- internals = self._dpu_internals
1696
- internals.current_ccd_readout_order = rotate_list(internals.current_ccd_readout_order, 1)
1697
- internals.cycle_count += 1
1698
- ccd_readout_order = _get_ccd_readout_order(
1699
- internals.current_ccd_readout_order, internals.ccd_id_to_bin
1700
- )
1701
- # LOGGER.info(f"{internals.current_ccd_readout_order = }, {ccd_readout_order = }, "
1702
- # f"{internals.cycle_count = }")
1703
- _ = command_set_readout_order(self._transport, self.register_map, ccd_readout_order)
1704
-
1705
- # We agreed to have atomic blocks of 4 clear-outs such that all four CCDs would always
1706
- # be dumped. So, whenever we are within one such atomic block, don't execute any DPU
1707
- # commands.
1708
-
1709
- if internals.cycle_count < 4:
1710
- LOGGER.debug(
1711
- f"[1] {internals.current_ccd_readout_order = }, {ccd_readout_order = }, "
1712
- f"{internals.cycle_count = }")
1713
- else:
1714
- internals.cycle_count = 0
1715
- LOGGER.debug(
1716
- f"[2] {internals.current_ccd_readout_order = }, {ccd_readout_order = }, "
1717
- f"{internals.cycle_count = }")
1718
-
1719
- except NoBytesReceivedError as exc:
1720
- # LOGGER.debug(f"No bytes received: {exc}")
1721
- pass
1722
- except NoTimeCodeError as exc:
1723
- LOGGER.warning("Reading the next timecode packet failed.")
1724
- LOGGER.debug("Traceback for NoTimecodeError:", exc_info=exc)
1725
- except NoHousekeepingPacketError as exc:
1726
- LOGGER.warning("Reading the next housekeeping packet failed.")
1727
- LOGGER.debug("Traceback for NoHousekeepingPacketError:", exc_info=exc)
1728
- except NoDataPacketError as exc:
1729
- LOGGER.warning("Reading the next data packet failed.")
1730
- LOGGER.debug("Traceback for NoDataPacketError:", exc_info=exc)
1731
- except TimecodeTimeoutError as exc:
1732
- # LOGGER.debug("Waiting for the next timecode.")
1733
- pass
1734
- except TimeExceededError as exc:
1735
- LOGGER.warning(
1736
- "Time to retrieve data packets in this readout cycle exceeded "
1737
- "4.0 seconds."
1738
- )
1739
- LOGGER.debug("Traceback for TimeExceededError:", exc_info=exc)
1740
- # FIXME:
1741
- # same here as above, make sure the DPU Processor doesn't crash. This last
1742
- # catching also means that Commands on the Queue will still be executed if
1743
- # there is an error. What needs to be checked here is that the Command should
1744
- # probably be send in the 'save zone' between 4.0s and 6.25s.
1745
- except Exception as exc:
1746
- LOGGER.error(exc, exc_info=True)
1747
- traceback.print_exc()
1748
-
1749
- # LOGGER.info(
1750
- # f"Time past after reading all packets from FEE:"
1751
- # f" {time.perf_counter() - start_time:.3f}s"
1752
- # )
1753
-
1754
- # Process high priority commands
1755
-
1756
- process_high_priority_commands(
1757
- self._priority_q, self._response_q,
1758
- self._n_fee_state.get_state(), self._dpu_internals, self.register_map)
1759
-
1760
- # When we are in internal sync dump mode, we need atomic blocks of 4 readouts such that all four
1761
- # CCDs will be cleared out. The cycle count goes from [0 -> 3] so, we only send commands when
1762
- # cycle count == 0.
1763
- # LOGGER.debug(f"{self._dpu_internals.cycle_count = }")
1764
- if self._dpu_internals.int_sync_cycle_dump_mode() and self._dpu_internals.cycle_count != 0:
1765
- continue
1766
-
1767
- # Then, we might want to send some RMAP commands -------------------------------
1768
-
1769
- # When we are in the 2s RMAP window, send the commands.
1770
- # Waiting till 4s have passed is apparently not needed, commands can be sent as
1771
- # soon as no packets will be received anymore, even if the time elapsed is
1772
- # less than 4s.
1773
- # But the following two lines might be uncommented for testing purposes.
1774
-
1775
- # while time.perf_counter() < start_time + 4.0:
1776
- # time.sleep(0.1)
1777
-
1778
- try:
1779
- send_commands_to_n_fee(
1780
- self._transport, storage, origin_spw_data,
1781
- self.register_map, self._command_q, self._response_q,
1782
- self._dpu_internals
1783
- )
1784
- except NFEECommandError as exc:
1785
- # Error is already logged in the send_commands_to_n_fee() function
1786
- pass
1787
-
1788
- # LOGGER.debug(
1789
- # f"Time past after sending commands to FEE:"
1790
- # f" {time.perf_counter() - start_time:.3f}s"
1791
- # )
1792
-
1793
- # Terminate the DPU Processor when the quit event flag has been set by the
1794
- # commanding protocol.
1795
-
1796
- if self._quit_event.is_set() or self._killer.term_signal_received:
1797
- LOGGER.info("Quit event is set, terminating..")
1798
- break
1799
-
1800
- except (Exception,) as exc:
1801
- LOGGER.critical(
1802
- "A fatal error occurred in the DPU Processor, needs to be restarted!",
1803
- exc_info=exc
1804
- )
1805
- # re-raise the exception such that it will bubble up at a higher level.
1806
- raise
1807
- finally:
1808
- LOGGER.debug("Unregistering from Storage Manager.")
1809
- unregister_from_storage_manager(storage, origin_spw_data)
1810
-
1811
- mon_sock.close(linger=0)
1812
- dist_sock.close(linger=0)
1813
- # ctx.destroy()
1814
-
1815
- def quit(self):
1816
- LOGGER.warning("Sending a Quit event to the DPU Processor.")
1817
- self._quit_event.set()
1818
-
1819
- def initialise_register_map(self):
1820
-
1821
- # FIXME:
1822
- # The DPU Processor shall not crash, therefore we shall catch all Exceptions thrown.
1823
- # Log the exceptions as an error and continue here. It must be tested what the exact
1824
- # harm is when doing this and if we need some further action before proceeding.
1825
-
1826
- # The DPU Processor is only initialised properly after reading the full register from
1827
- # the N-FEE. This can only be done within the time window we have for sending RMAP
1828
- # commands. Therefore we need to make sure we are in a safe time window for sending
1829
- # RMAP commands.
1830
-
1831
- LOGGER.info('Initialise Register Map from N-FEE')
1832
-
1833
- # First wait until a timecode is received
1834
-
1835
- while True:
1836
- terminator, packet = self._transport.read_packet(timeout=200)
1837
- if self._killer.term_signal_received:
1838
- raise Abort("A SIGTERM signal was received for this process")
1839
- if packet is None or len(packet) in (0, 1):
1840
- continue
1841
- if is_timecode(packet):
1842
- break
1843
-
1844
- start_time = time.perf_counter()
1845
-
1846
- LOGGER.debug(f"Timecode received {packet=}")
1847
-
1848
- while time.perf_counter() < start_time + 4.2: # FIXME: assuming N-FEE in external sync when starting...
1849
- terminator, packet = self._transport.read_packet(timeout=200)
1850
- if packet is None:
1851
- msg = f"time passed {time.perf_counter() - start_time:0.3f}"
1852
- else:
1853
- msg = packet[:10]
1854
- LOGGER.debug(f"Discarding packet: {msg}")
1855
-
1856
- LOGGER.info(f"Time passed since last timecode {time.perf_counter() - start_time:0.3f}s")
1857
- LOGGER.info(
1858
- 'In safe time window for sending RMAP command, getting full register..'
1859
- )
1860
-
1861
- command_sync_register_map(self._transport, self.register_map)
1862
-
1863
- LOGGER.debug(self.register_map)
1864
-
1865
-
1866
- def save_register_map(
1867
- reg_map: RegisterMap, storage: StorageProxy, origin: str, dist_socket: zmq.Socket):
1868
-
1869
- reg_memory_map = reg_map.get_memory_map_as_ndarray()
1870
-
1871
- LOGGER.debug("Saving register map")
1872
-
1873
- response = storage.save(
1874
- {
1875
- "origin": origin,
1876
- "data": {
1877
- "/register/": reg_memory_map
1878
- }
1879
- }
1880
- )
1881
-
1882
- LOGGER.debug(f"Response from saving Register Map: {response}")
1883
-
1884
- pickle_string = pickle.dumps(reg_memory_map)
1885
- msg_id = MessageIdentifier.N_FEE_REGISTER_MAP.to_bytes(1, 'big')
1886
- dist_socket.send_multipart([msg_id, pickle_string])
1887
-
1888
-
1889
- def register_to_storage_manager(proxy: StorageProxy, origin: str):
1890
- rc = proxy.new_registration(
1891
- item={
1892
- "origin": origin,
1893
- "persistence_class": HDF5,
1894
- "prep": {
1895
- "mode": "w-",
1896
- },
1897
- },
1898
- use_counter=True
1899
- )
1900
- LOGGER.info(f"{rc=!s}")
1901
- if rc and not rc.successful:
1902
- LOGGER.warning(f"Couldn't register to the Storage manager: {rc}")
1903
-
1904
-
1905
- def unregister_from_storage_manager(proxy: StorageProxy, origin: str):
1906
-
1907
- try:
1908
- rc = proxy.unregister({"origin": origin})
1909
- if not rc.successful:
1910
- LOGGER.warning(f"Couldn't unregister from the Storage manager: {rc}")
1911
-
1912
- except ConnectionError as exc:
1913
- LOGGER.warning(f"Couldn't connect to the Storage manager for de-registration: {exc}")
1914
-
1915
-
1916
- def new_spw_data_file(
1917
- proxy: StorageProxy, reg_map: RegisterMap, origin: str, data_type: Type[PersistenceLayer],
1918
- mon_socket: zmq.Socket, dist_socket: zmq.Socket
1919
- ):
1920
- """
1921
- Open a new data file to store CCD data.
1922
-
1923
- Args:
1924
- - proxy: Storage manager.
1925
- - origin: the origin for which to create a new file
1926
- - reg_map: Register map.
1927
- """
1928
-
1929
- LOGGER.debug(f"Create a new data file for {origin} in the Storage")
1930
-
1931
- # prep = {
1932
- # "expected_last_packet_flags": get_expected_last_packet_flags(reg_map),
1933
- # }
1934
- #
1935
- # for name in CRUCIAL_REGISTER_PARAMETERS:
1936
- # prep[name] = reg_map[name]
1937
-
1938
- item = {
1939
- "origin": origin,
1940
- "persistence_class": data_type,
1941
- "prep": {},
1942
- }
1943
-
1944
- # Retrieve the current filenames that will be available for processing as soon as the new
1945
- # HDF5 file is registered and created by the storage manager. This should be done, of course,
1946
- # before the new-registration call!
1947
-
1948
- hdf5_filenames = proxy.get_filenames(item={"origin": origin})
1949
-
1950
- response = proxy.new_registration(item=item, use_counter=True)
1951
-
1952
- LOGGER.debug(f"Response from new_registration: {response}")
1953
-
1954
- save_format_version(proxy, origin)
1955
-
1956
- # Save the Register Map that is used for the current readout cycle
1957
-
1958
- save_register_map(reg_map, proxy, origin, dist_socket)
1959
-
1960
- LOGGER.info(f"HDF5 files ready for processing: {hdf5_filenames=}")
1961
-
1962
- pickle_string = pickle.dumps(hdf5_filenames)
1963
- msg_id = MessageIdentifier.HDF5_FILENAMES.to_bytes(1, 'big')
1964
- mon_socket.send_multipart([msg_id, pickle_string])
1965
-
1966
-
1967
- def save_format_version(proxy: StorageProxy, origin: str):
1968
-
1969
- # 2.0 - introduced the format_version
1970
- # 2.1 - Added obsid as a dataset to the HDF5 file
1971
- # 2.2 - Multiple commands can now be saved under the same frame number
1972
- # 2.3 - introduced /dpu/num_cycles attribute
1973
- # 2.4 - introduced /dpu/slicing_num_cycles attribute
1974
- # 2.5 - introduced /{frame number}/hk_data dataset
1975
-
1976
- major_version = 2
1977
- minor_version = 5
1978
-
1979
- item_data = {
1980
- "/versions/format_version/": "format version of HDF5 file",
1981
- "/versions/format_version:ATTRS": [
1982
- ("major_version", major_version),
1983
- ("minor_version", minor_version)
1984
- ]
1985
- }
1986
- item = {
1987
- "origin": origin,
1988
- "data": item_data,
1989
- }
1990
- response = proxy.save(item)
1991
- LOGGER.debug(f"Response from saving format_version: {response}")
1992
-
1993
-
1994
- def save_obsid(proxy: StorageProxy, origin: str, obsid: ObservationIdentifier):
1995
-
1996
- item_data = {
1997
- "/obsid": str(obsid),
1998
- }
1999
- item = {
2000
- "origin": origin,
2001
- "data": item_data,
2002
- }
2003
- response = proxy.save(item)
2004
- LOGGER.debug(f"Response from saving OBSID: {response}")
2005
-
2006
-
2007
- def save_num_cycles(proxy: StorageProxy, origin: str, num_cycles: int):
2008
- """Save the number of cycles to the storage. This will only save if num_cycles >= 0."""
2009
-
2010
- # Only save num_cycles >= 0, the DPU Processor understands when num_cycles is negative,
2011
- # but for the HDF5 file we want to keep it clean and always have num_cycles >= 0.
2012
-
2013
- num_cycles = max(num_cycles, 0)
2014
-
2015
- item_data = {
2016
- "/dpu/": "DPU specific parameters",
2017
- "/dpu/:ATTRS": [
2018
- ("num_cycles", num_cycles),
2019
- ]
2020
- }
2021
- item = {
2022
- "origin": origin,
2023
- "data": item_data,
2024
- }
2025
- response = proxy.save(item)
2026
- LOGGER.debug(f"Response from saving NUM_CYCLES: {response}")
2027
-
2028
-
2029
- def save_slicing_parameter(proxy: StorageProxy, origin: str, slicing_num_cycles: int):
2030
- """Save the number of cycles to use for slicing to the storage."""
2031
-
2032
- item_data = {
2033
- "/dpu/:ATTRS": [
2034
- ("slicing_num_cycles", slicing_num_cycles),
2035
- ]
2036
- }
2037
- item = {
2038
- "origin": origin,
2039
- "data": item_data,
2040
- }
2041
- response = proxy.save(item)
2042
- LOGGER.debug(f"Response from saving SLICING_NUM_CYCLES: {response}")
2043
-
2044
-
2045
- def update_data_attributes(attr: dict, n_fee_state: NFEEState.StateTuple) -> Dict[str, Any]:
2046
- """
2047
- Collect parameter/value pairs that will be added to the data group as attributes.
2048
-
2049
- Args:
2050
- attr (dict): the current attributes that need to be updated
2051
- n_fee_state: the current state of the N-FEE
2052
-
2053
- Returns:
2054
- Updated data attributes.
2055
- """
2056
-
2057
- attr.update(n_fee_state._asdict())
2058
- return attr
2059
-
2060
-
2061
- def read_timecode(transport: SpaceWireInterface) -> (TimecodePacket, str, float):
2062
- """
2063
- Reads the next Timecode packet from the N-FEE.
2064
-
2065
- Args:
2066
- transport: the SpaceWire interfaces that is used for communication to the N-FEE
2067
-
2068
- Returns:
2069
- The timecode and associated timestamp, and the approximate start time for this readout cycle.
2070
- Raises:
2071
- NoTimecodeError when the timecode could not be read.
2072
- """
2073
- terminator, packet = transport.read_packet(timeout=100)
2074
- timestamp = format_datetime()
2075
-
2076
- if terminator is None and packet is None:
2077
- raise TimecodeTimeoutError()
2078
-
2079
- # Start time taken as closely as possible to timecode reception, this start_time is
2080
- # returned to be used in further functions called in the outer loop.
2081
-
2082
- start_time = time.perf_counter()
2083
- # LOGGER.debug(f"Time set: {start_time}")
2084
-
2085
- bytes_received = len(packet)
2086
-
2087
- # The following check is to cope with loss of connection when either the
2088
- # FEE simulator crashes or the connection dropped for some other reason.
2089
- # We will receive one packet with 0 or 1 bytes.
2090
-
2091
- if bytes_received in {0, 1}:
2092
- raise NoBytesReceivedError(f"{bytes_received} bytes received, lost connection to FEE?")
2093
-
2094
- if not is_timecode(packet):
2095
- packet = SpaceWirePacket.create_packet(packet)
2096
- raise NoTimeCodeError(f"Expected Timecode Packet, but got {packet.__class__.__name__}")
2097
-
2098
- tc_packet: TimecodePacket = SpaceWirePacket.create_packet(packet)
2099
-
2100
- LOGGER.info(f"Timecode received: 0x{tc_packet.timecode:0X} ({tc_packet.timecode})")
2101
-
2102
- return tc_packet, timestamp, start_time
2103
-
2104
-
2105
- def process_timecode(tc_packet: TimecodePacket, timestamp: str,
2106
- storage: StorageProxy, origin_spw_data: str, frame_number: int,
2107
- mon_socket: zmq.Socket, dist_socket: zmq.Socket):
2108
- """
2109
- Saves the timecode and associated timestamp for this frame. The timecode and timestamp
2110
- are also published on the monitoring and data distribution message queue.
2111
-
2112
- Args:
2113
- tc_packet: the timecode packet
2114
- timestamp: a timestamp associated with the reception of the timecode
2115
- frame_number: the current frame number
2116
- storage: the proxy that is used to communicate with the Storage manager
2117
- origin_spw_data: the registration identifier for the Storage manager, for the SpW data
2118
- mon_socket: the ZeroMQ socket to which monitoring sync signals are sent
2119
- dist_socket: the ZeroMQ socket to which SpW data is sent (for real-time view)
2120
-
2121
- Returns:
2122
- Nothing.
2123
- """
2124
- LOGGER.debug(f"Saving timecode packet: {tc_packet.timecode=}, {frame_number=}")
2125
-
2126
- response = storage.save(
2127
- {
2128
- "origin": origin_spw_data,
2129
- "data":
2130
- {
2131
- f"/{frame_number}/timecode": tc_packet,
2132
- f"/{frame_number}/timecode:ATTRS": [("timestamp", timestamp)],
2133
- }
2134
- }
2135
- )
2136
-
2137
- LOGGER.debug(f"Response from saving Timecode: {response}")
2138
-
2139
- pickle_string = pickle.dumps((tc_packet.timecode, timestamp))
2140
- mon_socket.send_multipart([MessageIdentifier.SYNC_TIMECODE.to_bytes(1, "big"), pickle_string])
2141
- dist_socket.send_multipart([MessageIdentifier.SYNC_TIMECODE.to_bytes(1, "big"), pickle_string])
2142
-
2143
-
2144
- def read_updated_hk_data(transport: SpaceWireInterface) -> (HousekeepingData, str):
2145
- """
2146
- Reads the memory map that contains the housekeeping information from the N-FEE.
2147
- The memory map is returned as a HousekeepingData object.
2148
-
2149
- This is not the same as a housekeeping packet that is read from the N-FEE. For
2150
- that refer to the function `read_hk_packet()`.
2151
-
2152
- Args:
2153
- transport: the SpaceWire interfaces that is used for communication to the N-FEE
2154
-
2155
- Returns:
2156
- The HK data packet and its associated timestamp as a string.
2157
- """
2158
- timestamp = format_datetime()
2159
-
2160
- data = command_get_hk_information(transport, None, 0x000_0700, 0x90)
2161
- hk_data = HousekeepingData(data)
2162
-
2163
- msg = f"Updated housekeeping retrieved... {hk_data.frame_counter = }, {hk_data.timecode = }"
2164
- if hk_data.error_flags:
2165
- msg += f", error_flags = 0b{hk_data.error_flags:032b}"
2166
- LOGGER.warning(msg)
2167
- else:
2168
- LOGGER.info(msg)
2169
-
2170
- return hk_data, timestamp
2171
-
2172
- def process_updated_hk_data(hk_data: HousekeepingData, timestamp: str, storage: StorageProxy,
2173
- origin: str, frame_number: int,
2174
- mon_socket: zmq.Socket, dist_socket: zmq.Socket):
2175
- """
2176
- Saves the housekeeping data and associated timestamp for this frame. The data and timestamp
2177
- are also published on the monitoring message queue.
2178
-
2179
- Args:
2180
- hk_data: the HousekeepingData object
2181
- timestamp: the timestamp associated with the reception of this data
2182
- frame_number: the current frame number
2183
- storage: the proxy that is used to communicate with the Storage manager
2184
- origin: the registration identifier for the Storage manager
2185
- mon_socket: the ZeroMQ socket to which monitoring sync signals are sent
2186
- dist_socket: the ZeroMQ socket to which SpW data is sent (for real-time view)
2187
-
2188
- Returns:
2189
- Nothing.
2190
- """
2191
- LOGGER.debug(f"Saving updated Housekeeping data: {hk_data.frame_counter = }, {hk_data.timecode = }, {hk_data.frame_number = }")
2192
-
2193
- response = storage.save(
2194
- {
2195
- "origin": origin,
2196
- "data":
2197
- {
2198
- f"/{frame_number}/hk_data": hk_data,
2199
- f"/{frame_number}/hk_data:ATTRS": [("timestamp", timestamp)],
2200
- }
2201
- }
2202
- )
2203
-
2204
- LOGGER.debug(f"Response from saving updated Housekeeping data: {response}")
2205
-
2206
- pickle_string = pickle.dumps((hk_data.error_flags, hk_data.frame_counter, timestamp))
2207
- mon_socket.send_multipart([MessageIdentifier.SYNC_ERROR_FLAGS.to_bytes(1, "big"), pickle_string])
2208
-
2209
- msg_id = MessageIdentifier.SYNC_HK_DATA.to_bytes(1, 'big')
2210
-
2211
- pickle_string = pickle.dumps((hk_data, timestamp))
2212
- dist_socket.send_multipart([msg_id, pickle_string])
2213
-
2214
-
2215
- def read_hk_packet(transport: SpaceWireInterface) -> (HousekeepingPacket, str):
2216
- """
2217
- Read the next Housekeeping Packet from the N-FEE.
2218
-
2219
- Args:
2220
- transport: the SpaceWire interfaces that is used for communication to the N-FEE
2221
- Raises:
2222
- NoHousekeepingPacketError when the next packet is not a `HousekeepingPacket`.
2223
- Returns:
2224
- the received housekeeping packet and the timestamp.
2225
- """
2226
- terminator, packet = transport.read_packet()
2227
- timestamp = format_datetime()
2228
-
2229
- packet = SpaceWirePacket.create_packet(packet)
2230
-
2231
- if not isinstance(packet, egse.spw.HousekeepingPacket):
2232
- raise NoHousekeepingPacketError(
2233
- f"Expected a HousekeepingPacket, but got {packet.__class__.__module__}.{packet.__class__.__name__}")
2234
-
2235
- LOGGER.info(f"Housekeeping Packet received: {packet.type!s}")
2236
-
2237
- return packet, timestamp
2238
-
2239
-
2240
- def process_hk_packet(hk_packet: HousekeepingPacket, timestamp: str,
2241
- storage: StorageProxy, origin: str, frame_number: int,
2242
- mon_socket: zmq.Socket, dist_socket: zmq.Socket):
2243
- """
2244
- Saves the housekeeping packet and associated timestamp for this frame. The data and timestamp
2245
- are also published on the monitoring and data distribution message queue.
2246
-
2247
- Args:
2248
- hk_packet: the HousekeepingPacket
2249
- timestamp: the timestamp associated with the reception of this packet
2250
- frame_number: the current frame number
2251
- storage: the proxy that is used to communicate with the Storage manager
2252
- origin: the registration identifier for the Storage manager
2253
- mon_socket: the ZeroMQ socket to which monitoring sync signals are sent
2254
- dist_socket: the ZeroMQ socket to which SpW data is sent (for real-time view)
2255
-
2256
- Returns:
2257
- Nothing.
2258
- """
2259
-
2260
- LOGGER.debug(f"Saving Housekeeping packet: {hk_packet.type!s}, "
2261
- f"frame counter={hk_packet.frame_counter}, "
2262
- f"sequence counter={hk_packet.sequence_counter}")
2263
-
2264
- response = storage.save(
2265
- {
2266
- "origin": origin,
2267
- "data":
2268
- {
2269
- f"/{frame_number}/hk": hk_packet,
2270
- }
2271
- }
2272
- )
2273
-
2274
- LOGGER.debug(f"Response from saving HK Packet: {response}")
2275
-
2276
- msg_id = MessageIdentifier.SYNC_HK_PACKET.to_bytes(1, 'big')
2277
-
2278
- pickle_string = pickle.dumps((hk_packet.type, timestamp))
2279
- mon_socket.send_multipart([msg_id, pickle_string])
2280
-
2281
- pickle_string = pickle.dumps((hk_packet, timestamp))
2282
- dist_socket.send_multipart([msg_id, pickle_string])
2283
-
2284
-
2285
- def read_and_process_data_packets(
2286
- transport: SpaceWireInterface, storage: StorageProxy, origin_spw_data: str,
2287
- start_time: float, mode: int, register_map: RegisterMap, data_attr: dict,
2288
- internals: DPUInternals, dist_socket: zmq.Socket
2289
- ):
2290
- """
2291
- Read the data packets when they are available depending on the mode.
2292
-
2293
- Args:
2294
- transport: the SpaceWire interfaces that is used for communication to the N-FEE
2295
- storage: the proxy that is used to communicate with the Storage manager
2296
- origin_spw_data: the registration identifier for the Storage manager
2297
- start_time: the approximate time that the readout cycle started
2298
- mode: FPGA mode
2299
- register_map: the DPU Processor's copy of the N-FEE register map
2300
- data_attr: register values to be saved with the data
2301
- internals: use for expected_last_packet_flags (these will be updated within this function)
2302
- dist_socket: the ZeroMQ socket to which SpW data is sent (for real-time view)
2303
-
2304
- Raises:
2305
- NoDataPacketError when the expected packet is not a data packet.
2306
- """
2307
-
2308
- if internals.dump_mode or mode not in (
2309
- n_fee_mode.FULL_IMAGE_PATTERN_MODE,
2310
- n_fee_mode.WINDOWING_PATTERN_MODE,
2311
- n_fee_mode.FULL_IMAGE_MODE,
2312
- ):
2313
- return
2314
-
2315
- timestamp = format_datetime()
2316
- msg_id = MessageIdentifier.SYNC_DATA_PACKET.to_bytes(1, 'big')
2317
-
2318
- data_count = 0
2319
-
2320
- # Initialise the flags that determine if the last packet has arrived for
2321
- # all expected data packets.
2322
-
2323
- actual_last_packet_flags = [False, False, False, False]
2324
-
2325
- # Read the data, until all the expected last packet bits are set.
2326
- # This should be within next 4 seconds.
2327
-
2328
- LOGGER.info("Reading data packets....")
2329
-
2330
- terminator, packet = transport.read_packet()
2331
-
2332
- data_packet: DataPacket = SpaceWirePacket.create_packet(packet)
2333
-
2334
- if (not isinstance(data_packet, DataDataPacket) and
2335
- not isinstance(data_packet, OverscanDataPacket)):
2336
- LOGGER.critical(f"DataPacket expected, got {data_packet}")
2337
- raise NoDataPacketError(f"Expected a data packet, but got {data_packet.__class__.__name__}")
2338
-
2339
- # LOGGER.debug(f"Got data packet of length {len(packet)}")
2340
-
2341
- LOGGER.debug(f"Saving data packets: {data_packet.type!s}")
2342
-
2343
- item_data = {f"/{internals.frame_number}/data/{data_count}": data_packet}
2344
-
2345
- attrs = [(k, v) for k, v in data_attr.items()]
2346
- item_data.update({f"/{internals.frame_number}/data:ATTRS": attrs})
2347
-
2348
- response = storage.save(
2349
- {
2350
- "origin": origin_spw_data,
2351
- "data": item_data
2352
- }
2353
- )
2354
-
2355
- if isinstance(response, Exception):
2356
- LOGGER.warning(f"Response from saving data packet: {response}")
2357
-
2358
- pickle_string = pickle.dumps((data_packet, timestamp))
2359
- dist_socket.send_multipart([msg_id, pickle_string])
2360
-
2361
- data_count += 1
2362
-
2363
- # Update the expected flags with the possibly new register values, but only
2364
- # after we had the 400ms pulse which updates the settings from the
2365
- # register map. The test is needed because the register here on the DPU
2366
- # processing side can be updated also on 200ms sync pulses, but the changes
2367
- # only take effect on the 400ms pulse in the N-FEE.
2368
-
2369
- if data_packet.type.frame_number == 0:
2370
- internals.expected_last_packet_flags = get_expected_last_packet_flags(register_map, internals.sensor_sel_enum)
2371
- LOGGER.debug(f"{internals.expected_last_packet_flags=}")
2372
-
2373
- idx = get_index_for_last_packet_flags(data_packet.type.packet_type, data_packet.type.ccd_side,
2374
- internals.ccd_sides_enum)
2375
- # LOGGER.debug(f"{idx=}, {data_packet.type.packet_type=}, {data_packet.type.ccd_side=}")
2376
- actual_last_packet_flags[idx] = data_packet.type.last_packet
2377
-
2378
- while not got_all_last_packets(
2379
- actual_last_packet_flags, internals.expected_last_packet_flags):
2380
-
2381
- terminator, packet = transport.read_packet()
2382
- data_packet: DataPacket = SpaceWirePacket.create_packet(packet)
2383
-
2384
- if (not isinstance(data_packet, DataDataPacket) and
2385
- not isinstance(data_packet, OverscanDataPacket)):
2386
- LOGGER.critical(f"DataPacket expected, got {data_packet}")
2387
- raise NoDataPacketError(
2388
- f"Expected a data packet, but got {data_packet.__class__.__name__}")
2389
-
2390
- # LOGGER.debug(f"Saving data packet: {data_packet.type!s}")
2391
-
2392
- response = storage.save(
2393
- {
2394
- "origin": origin_spw_data,
2395
- "data":
2396
- {
2397
- f"/{internals.frame_number}/data/{data_count}": data_packet,
2398
- }
2399
- }
2400
- )
2401
-
2402
- if isinstance(response, Exception):
2403
- LOGGER.warning(f"Response from saving data packet: {response}")
2404
-
2405
- pickle_string = pickle.dumps((data_packet, timestamp))
2406
- dist_socket.send_multipart([msg_id, pickle_string])
2407
-
2408
- data_count += 1
2409
-
2410
- #LOGGER.debug(f"Got data packet of length {len(packet)}")
2411
- #LOGGER.debug(f"DataPacketHeader: {data_packet.header.type_as_object}")
2412
-
2413
- idx = get_index_for_last_packet_flags(data_packet.type.packet_type, data_packet.type.ccd_side,
2414
- internals.ccd_sides_enum)
2415
- actual_last_packet_flags[idx] = data_packet.type.last_packet
2416
-
2417
- # Sending data packets shall not take more than 4 seconds, and if we
2418
- # wait longer than 6.25 seconds, all RMAP commands that we send will be
2419
- # discarded.
2420
-
2421
- if time.perf_counter() > start_time + 5.25:
2422
- raise TimeExceededError(
2423
- "Retrieving data packets exceeded the allowed 4.0 seconds, "
2424
- "breaking out of the data loop."
2425
- )
2426
-
2427
-
2428
- def send_commands_to_n_fee(
2429
- transport: SpaceWireInterface, storage: StorageProxy, origin: str,
2430
- register_map: RegisterMap,
2431
- command_q: multiprocessing.Queue,
2432
- response_q: multiprocessing.Queue,
2433
- internals: DPUInternals
2434
- ):
2435
- """
2436
- Send RMAP commands to the N-FEE. The commands are read from the command queue that is shared
2437
- with the DPU Controller. The response from the N-FEE is put on the response queue, also shared
2438
- with the DPU Controller.
2439
-
2440
- !!! note
2441
- The current implementation allows only one command from the command queue per sync cycle.
2442
-
2443
- Args:
2444
- transport: the SpaceWire interfaces that is used for communication to the N-FEE
2445
- storage: the proxy that is used to communicate with the Storage manager
2446
- origin: the registration identifier for the Storage manager
2447
- register_map: the DPU Processor's copy of the N-FEE register map
2448
- command_q: the command queue
2449
- response_q: the response queue
2450
- internals: for some commands we need access to DPUInternals, e.g. num_cycles, dump_mode_int
2451
-
2452
- Raises:
2453
- Exceptions are caught and put on the response queue.
2454
- """
2455
-
2456
- if internals.clear_error_flags:
2457
- LOGGER.debug("Set the clear-error-flags register parameter.")
2458
- try:
2459
- _ = command_set_clear_error_flags(transport, register_map)
2460
- except ValueError as exc:
2461
- LOGGER.error("The clear-error-flags register parameter could not be set due to a ValueError.", exc_info=exc)
2462
-
2463
- internals.clear_error_flags = False
2464
-
2465
- command = response = None
2466
- kwargs = {}
2467
- try:
2468
- (command, args, kwargs) = command_q.get_nowait()
2469
-
2470
- # When num_cycles is not specified, don't even set it to 0, the N-FEE will stay in the
2471
- # current configuration until commanded otherwise.
2472
-
2473
- if num_cycles := kwargs.get("num_cycles"):
2474
- LOGGER.debug(f"Set internals.num_cycle to {num_cycles}.")
2475
- internals.num_cycles = num_cycles
2476
-
2477
- # Some commanding requires to go back into internal sync dump mode
2478
-
2479
- dump_mode_int = kwargs.get("dump_mode_int", False)
2480
- LOGGER.debug(f"Set internals.dump_mode_int to {dump_mode_int}.")
2481
- internals.dump_mode_int = dump_mode_int
2482
-
2483
- LOGGER.debug(f"Executing Command: {command.__name__}, {args=}")
2484
- response = command(transport, register_map, *args)
2485
- LOGGER.debug(f"Command executed: {command.__name__}, {args=}, {response=}")
2486
-
2487
- LOGGER.debug(f"Saving command: {command.__name__}, {args=}")
2488
-
2489
- response_save = storage.save({
2490
- "origin": origin,
2491
- "data": {
2492
- f"/{internals.frame_number}/command/": f"{command.__name__}, {args=}, {kwargs=}",
2493
- }
2494
- })
2495
-
2496
- LOGGER.debug(f"Response from saving Command: {response_save}")
2497
-
2498
- except queue.Empty:
2499
- pass
2500
- except (Exception,) as exc:
2501
- LOGGER.error(
2502
- f"Exception during command execution in DPU Processor: "
2503
- f"{command}", exc_info=exc
2504
- )
2505
- raise NFEECommandError(
2506
- f"An exception occurred sending the command {command} "
2507
- f"to the N-FEE.") from exc
2508
- finally:
2509
- if command is not None and kwargs.get('response', True):
2510
- response_q.put((command, response))
2511
-
2512
-
2513
- def process_high_priority_commands(
2514
- priority_q: multiprocessing.Queue,
2515
- response_q: multiprocessing.Queue,
2516
- n_fee_state: tuple, dpu_internals: DPUInternals, reg_map: RegisterMap):
2517
- """
2518
- Execute high priority commands from the DPU Control Server / Controller. The `n_fee_state` and
2519
- the `dpu_internals` tuples are passed to the high priority commands before any other arguments
2520
- that were passed on the command queue.
2521
-
2522
- Args:
2523
- priority_q: the command queue with priority
2524
- response_q: the response queue
2525
- n_fee_state: a namedtuple containing the current state of the N-FEE
2526
- dpu_internals: the internal settings of the DPU might be requested or set
2527
- by a high priority command
2528
- reg_map: the current register map from the DPU Processor
2529
- """
2530
-
2531
- command = response = None
2532
- try:
2533
- (command, args) = priority_q.get_nowait()
2534
- response = command(n_fee_state, dpu_internals, reg_map, *args)
2535
- LOGGER.debug(f"Command executed: {command.__name__}, {args=}, {response=}")
2536
- except queue.Empty:
2537
- pass
2538
- except (Exception,) as exc:
2539
- LOGGER.error(
2540
- f"Exception during command execution in DPU Processor: "
2541
- f"{command}", exc_info=exc
2542
- )
2543
- raise NFEECommandError(
2544
- f"An exception occurred sending the command {command} "
2545
- f"to the N-FEE.") from exc
2546
- finally:
2547
- if command is not None:
2548
- response_q.put((command, response))
2549
-
2550
-
2551
- def get_index_for_last_packet_flags(packet_type: int, ccd_side: int, ccd_sides_enum):
2552
- """
2553
- Returns the index into the last packet flags list.
2554
-
2555
- The last packet flags list is organised as follows:
2556
-
2557
- * index 0: data packet, E-side
2558
- * index 1: data packet, F-side
2559
- * index 2: overscan data packet, E-side
2560
- * index:3: overscan data packet, F-side
2561
-
2562
- Args:
2563
- packet_type: the packet type as read from the packet header [datapacket=0, overscan=1,
2564
- housekeeping=2]
2565
- ccd_side: the ccd side as read from the packet header
2566
- ccd_sides_enum: Enumeration with information on E and F
2567
-
2568
- Returns:
2569
- The index for the last packet flags list.
2570
- """
2571
- if ccd_side == ccd_sides_enum.E_SIDE.value:
2572
- return packet_type * 2
2573
- else:
2574
- return packet_type * 2 + 1
2575
-
2576
-
2577
- def get_expected_last_packet_flags(register_map: Mapping, sensor_sel_enum: Enum) -> List[bool]:
2578
- """
2579
- Build and returns a list of flags that define if a last packet is expected.
2580
-
2581
- A last packet flag is expected for normal data packets and overscan data
2582
- packets. For both these data packets we can expect E-side and F-side packets
2583
- with a last packet flag. That brings the total expected flags to four. This
2584
- function examines the register values `v_start`, `v_end`, and `sensor_sel`.
2585
-
2586
- The flags are ordered as follows:
2587
-
2588
- 1. data packet and E-side
2589
- 2. data packet and F-side
2590
- 3. overscan data packet and E-side
2591
- 4. overscan data packet and F-side
2592
-
2593
- Housekeeping packets are not considered here.
2594
-
2595
- For comparing the flags with the actual data,
2596
- use the function `got_all_last_packets(actual, expected)`.
2597
-
2598
- Args:
2599
- register_map: the current Register map for the N-FEE
2600
- sensor_sel_enum:
2601
- Returns:
2602
- a list of flags.
2603
- """
2604
- sensor_sel_from_register = register_map["sensor_sel"]
2605
-
2606
- e_side = bool(sensor_sel_from_register & sensor_sel_enum.E_SIDE)
2607
- f_side = bool(sensor_sel_from_register & sensor_sel_enum.F_SIDE)
2608
- v_start = register_map["v_start"]
2609
- v_end = register_map["v_end"]
2610
- data_packet = v_start < 4510
2611
- overscan_packet = v_end > 4509
2612
-
2613
- return [
2614
- data_packet and e_side,
2615
- data_packet and f_side,
2616
- overscan_packet and e_side,
2617
- overscan_packet and f_side
2618
- ]
2619
-
2620
- def create_expected_last_packet_flags(n_fee_state: NFEEState.StateTuple, sensor_sel_enum: Enum):
2621
- """
2622
- Build and returns a list of flags that define if a last packet is expected.
2623
-
2624
- A last packet flag is expected for normal data packets and overscan data
2625
- packets. For both these data packets we can expect E-side and F-side packets
2626
- with a last packet flag. That brings the total expected flags to four. This
2627
- function examines the register values `v_start`, `v_end`, and `sensor_sel`.
2628
-
2629
- The flags are ordered as follows:
2630
-
2631
- 1. data packet and E-side
2632
- 2. data packet and F-side
2633
- 3. overscan data packet and E-side
2634
- 4. overscan data packet and F-side
2635
-
2636
- Housekeeping packets are not considered here.
2637
-
2638
- For comparing the flags with the actual data,
2639
- use the function `got_all_last_packets(actual, expected)`.
2640
-
2641
- Args:
2642
- n_fee_state: a namedtuple containing the current N-FEE State
2643
- sensor_sel_enum: Enumeration with the sensor_sel
2644
- Returns:
2645
- a list of flags.
2646
- """
2647
- sensor_sel_from_nfee_state = n_fee_state.sensor_sel
2648
-
2649
- v_start = n_fee_state.v_start
2650
- v_end = n_fee_state.v_end
2651
-
2652
- e_side = bool(sensor_sel_from_nfee_state & sensor_sel_enum.E_SIDE)
2653
- f_side = bool(sensor_sel_from_nfee_state & sensor_sel_enum.F_SIDE)
2654
- data_packet = v_start < 4510
2655
- overscan_packet = v_end > 4509
2656
-
2657
- return [
2658
- data_packet and e_side,
2659
- data_packet and f_side,
2660
- overscan_packet and e_side,
2661
- overscan_packet and f_side
2662
- ]
2663
-
2664
-
2665
- def got_all_last_packets(actual, expected):
2666
- """
2667
- Returns True if all the expected last packet flags have been seen.
2668
-
2669
- Args:
2670
- actual: the flags that have been seen so far
2671
- expected: the expected flags
2672
-
2673
- Returns:
2674
- True if 'actual' matches 'expected', False otherwise.
2675
- """
2676
- rc = all([x == y for (x, y) in zip(actual, expected)])
2677
- # LOGGER.info(f"{expected=}, {actual=}, {rc=}")
2678
- return rc
2679
-
2680
-
2681
- if __name__ == "__main__":
2682
-
2683
- def do_something(idx):
2684
- LOGGER.info(f"Hello! {idx=}")
2685
-
2686
- moni = DPUMonitoring()
2687
- moni.connect()
2688
-
2689
- for idx in range(3):
2690
- # moni.on_long_pulse_do(do_something, idx)
2691
-
2692
- # timecode, timestamp = moni.wait_for_timecode()
2693
- # LOGGER.info(f"{timecode=}, {timestamp=}")
2694
-
2695
- filenames = moni.wait_for_hdf5_filename()
2696
- LOGGER.info(f"{filenames=}")
2697
-
2698
- moni.disconnect()