cgse 2024.7.0__py3-none-any.whl → 2025.0.2__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.2.dist-info/METADATA +38 -0
  4. cgse-2025.0.2.dist-info/RECORD +5 -0
  5. {cgse-2024.7.0.dist-info → cgse-2025.0.2.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/system.py DELETED
@@ -1,1611 +0,0 @@
1
- """
2
- The system module defines convenience functions that provide information on system specific
3
- functionality like, file system interactions, timing, operating system interactions, etc.
4
-
5
- The module has external dependencies to:
6
-
7
- * __distro__: for determining the Linux distribution
8
- * __psutil__: for system statistics
9
-
10
- """
11
- from __future__ import annotations
12
-
13
- import builtins
14
- import collections
15
- import contextlib
16
- import datetime
17
- import functools
18
- import importlib
19
- import inspect
20
- import itertools
21
- import logging
22
- import operator
23
- import os
24
- import platform # For getting the operating system name
25
- import re
26
- import signal
27
- import socket
28
- import subprocess # For executing a shell command
29
- import sys
30
- import time
31
- from collections import namedtuple
32
- from pathlib import Path
33
- from types import FunctionType
34
- from types import ModuleType
35
- from typing import Any
36
- from typing import Callable
37
- from typing import Iterable
38
- from typing import List
39
- from typing import Optional
40
- from typing import Tuple
41
- from typing import Union
42
-
43
- import distro # For determining the Linux distribution
44
- import psutil
45
- from rich.text import Text
46
- from rich.tree import Tree
47
-
48
- EPOCH_1958_1970 = 378691200
49
- TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f%z"
50
-
51
- logger = logging.getLogger(__name__)
52
-
53
- from contextlib import contextmanager
54
- import logging
55
-
56
-
57
- # Code below is copied from https://gist.github.com/simon-weber/7853144
58
-
59
-
60
- @contextmanager
61
- def all_logging_disabled(highest_level=logging.CRITICAL, flag=True):
62
- """
63
- Context manager to temporarily disable logging messages during its execution.
64
-
65
- Args:
66
- highest_level (int, optional): The maximum logging level to be disabled.
67
- Defaults to logging.CRITICAL.
68
- Note: Adjust this only if a custom level greater than CRITICAL is defined.
69
- flag (bool, optional): If True, disables all logging; if False, no changes are made.
70
- Defaults to True.
71
-
72
- Example:
73
- >>> with all_logging_disabled():
74
- ... # Your code with logging messages disabled
75
-
76
- Note:
77
- This context manager is designed to prevent any logging messages triggered during its body
78
- from being processed. It temporarily disables logging and restores the previous state afterward.
79
- """
80
-
81
- # two kind-of hacks here:
82
- # * can't get the highest logging level in effect => delegate to the user
83
- # * can't get the current module-level override => use an undocumented
84
- # (but non-private!) interface
85
-
86
- previous_level = logging.root.manager.disable
87
-
88
- if flag:
89
- logging.disable(highest_level)
90
-
91
- try:
92
- yield
93
- finally:
94
- logging.disable(previous_level)
95
-
96
-
97
- def get_active_loggers() -> dict:
98
- """
99
- Retrieves information about active loggers and their respective log levels.
100
-
101
- Returns a dictionary where keys are the names of active loggers, and values
102
- are the corresponding log levels in string format.
103
-
104
- Returns:
105
- dict: A dictionary mapping logger names to their log levels.
106
-
107
- Note:
108
- This function provides a snapshot of the currently active loggers and
109
- their log levels at the time of the function call.
110
-
111
- """
112
-
113
- return {
114
- name: logging.getLevelName(logging.getLogger(name).level) for name in sorted(logging.Logger.manager.loggerDict)
115
- }
116
-
117
-
118
- # The code below was taken from https://stackoverflow.com/a/69639238/4609203
119
-
120
-
121
- def ignore_m_warning(modules=None):
122
- """
123
- Ignore RuntimeWarning by `runpy` that occurs when executing a module with `python -m package.module`,
124
- while that module is also imported.
125
-
126
- The original warning message is:
127
-
128
- '<package.module>' found in sys.modules after import of package '<package'>,
129
- but prior to execution of '<package.module>'
130
- """
131
- if not isinstance(modules, (list, tuple)):
132
- modules = [modules]
133
-
134
- try:
135
- import warnings
136
- import re
137
-
138
- msg = "'{module}' found in sys.modules after import of package"
139
- for module in modules:
140
- module_msg = re.escape(msg.format(module=module))
141
- warnings.filterwarnings("ignore", message=module_msg, category=RuntimeWarning, module="runpy") # ignore -m
142
- except (ImportError, KeyError, AttributeError, Exception):
143
- pass
144
-
145
-
146
- def now(utc: bool = True):
147
- """Returns a datetime object for the current time in UTC or local time."""
148
- if utc:
149
- return datetime.datetime.now(tz=datetime.timezone.utc)
150
- else:
151
- return datetime.datetime.now()
152
-
153
-
154
- def format_datetime(dt: Union[str, datetime.datetime] = None, fmt: str = None, width: int = 6, precision: int = 3):
155
- """Format a datetime as YYYY-mm-ddTHH:MM:SS.μs+0000.
156
-
157
- If the given argument is not timezone aware, the last part, i.e. `+0000` will not be there.
158
-
159
- If no argument is given, the timestamp is generated as
160
- `datetime.datetime.now(tz=datetime.timezone.utc)`.
161
-
162
- The `dt` argument can also be a string with the following values: today, yesterday, tomorrow,
163
- and 'day before yesterday'. The format will then be '%Y%m%d' unless specified.
164
-
165
- Optionally, a format string can be passed in to customize the formatting of the timestamp.
166
- This format string will be used with the `strftime()` method and should obey those conventions.
167
-
168
- Example:
169
- >>> format_datetime(datetime.datetime(2020, 6, 13, 14, 45, 45, 696138))
170
- '2020-06-13T14:45:45.696'
171
- >>> format_datetime(datetime.datetime(2020, 6, 13, 14, 45, 45, 696138), precision=6)
172
- '2020-06-13T14:45:45.696138'
173
- >>> format_datetime(datetime.datetime(2020, 6, 13, 14, 45, 59, 999501), precision=3)
174
- '2020-06-13T14:45:59.999'
175
- >>> format_datetime(datetime.datetime(2020, 6, 13, 14, 45, 59, 999501), precision=6)
176
- '2020-06-13T14:45:59.999501'
177
- >>> _ = format_datetime()
178
- ...
179
- >>> format_datetime("yesterday")
180
- '20220214'
181
- >>> format_datetime("yesterday", fmt="%d/%m/%Y")
182
- '14/02/2022'
183
-
184
- Args:
185
- dt (datetime): a datetime object or an agreed string like yesterday, tomorrow, ...
186
- fmt (str): a format string that is accepted by `strftime()`
187
- width (int): the width to use for formatting the microseconds
188
- precision (int): the precision for the microseconds
189
- Returns:
190
- a string representation of the current time in UTC, e.g. `2020-04-29T12:30:04.862+0000`.
191
-
192
- Raises:
193
- A ValueError will be raised when the given dt argument string is not understood.
194
- """
195
- dt = dt or datetime.datetime.now(tz=datetime.timezone.utc)
196
- if isinstance(dt, str):
197
- fmt = fmt or "%Y%m%d"
198
- if dt.lower() == "yesterday":
199
- dt = datetime.date.today() - datetime.timedelta(days=1)
200
- elif dt.lower() == "today":
201
- dt = datetime.date.today()
202
- elif dt.lower() == "day before yesterday":
203
- dt = datetime.date.today() - datetime.timedelta(days=2)
204
- elif dt.lower() == "tomorrow":
205
- dt = datetime.date.today() + datetime.timedelta(days=1)
206
- else:
207
- raise ValueError(f"Unknown date passed as an argument: {dt}")
208
-
209
- if fmt:
210
- timestamp = dt.strftime(fmt)
211
- else:
212
- width = min(width, precision)
213
- timestamp = (
214
- f"{dt.strftime('%Y-%m-%dT%H:%M')}:"
215
- f"{dt.second:02d}.{dt.microsecond//10**(6-precision):0{width}d}{dt.strftime('%z')}"
216
- )
217
-
218
- return timestamp
219
-
220
-
221
- SECONDS_IN_A_DAY = 24 * 60 * 60
222
- SECONDS_IN_AN_HOUR = 60 * 60
223
- SECONDS_IN_A_MINUTE = 60
224
-
225
-
226
- def humanize_seconds(seconds: float, include_micro_seconds: bool = True):
227
- """
228
- The number of seconds is represented as "[#D]d [#H]h[#M]m[#S]s.MS" where:
229
-
230
- * `#D` is the number of days if days > 0
231
- * `#H` is the number of hours if hours > 0
232
- * `#M` is the number of minutes if minutes > 0 or hours > 0
233
- * `#S` is the number of seconds
234
- * `MS` is the number of microseconds
235
-
236
- Examples:
237
- >>> humanize_seconds(20)
238
- '20s.000'
239
- >>> humanize_seconds(10*24*60*60)
240
- '10d 00s.000'
241
- >>> humanize_seconds(10*86400 + 3*3600 + 42.023)
242
- '10d 03h00m42s.023'
243
- >>> humanize_seconds(10*86400 + 3*3600 + 42.023, include_micro_seconds=False)
244
- '10d 03h00m42s'
245
-
246
- Returns:
247
- a string representation for the number of seconds.
248
- """
249
- micro_seconds = round((seconds - int(seconds)) * 1000)
250
- rest = int(seconds)
251
-
252
- days = rest // SECONDS_IN_A_DAY
253
- rest -= SECONDS_IN_A_DAY * days
254
-
255
- hours = rest // SECONDS_IN_AN_HOUR
256
- rest -= SECONDS_IN_AN_HOUR * hours
257
-
258
- minutes = rest // SECONDS_IN_A_MINUTE
259
- rest -= SECONDS_IN_A_MINUTE * minutes
260
-
261
- seconds = rest
262
-
263
- result = ""
264
- if days:
265
- result += f"{days}d "
266
-
267
- if hours:
268
- result += f"{hours:02d}h"
269
-
270
- if minutes or hours:
271
- result += f"{minutes:02d}m"
272
-
273
- result += f"{seconds:02d}s"
274
- if include_micro_seconds:
275
- result += f".{micro_seconds:03d}"
276
-
277
- return result
278
-
279
-
280
- def str_to_datetime(datetime_string: str):
281
- """Convert the given string to a datetime object.
282
-
283
- Args:
284
- - datatime_string: String representing a datetime, in the format %Y-%m-%dT%H:%M:%S.%f%z.
285
-
286
- Returns: Datetime object.
287
- """
288
-
289
- return datetime.datetime.strptime(datetime_string.strip("\r"), TIME_FORMAT)
290
-
291
-
292
- def duration(dt_start: str | datetime.datetime, dt_end: str | datetime.datetime) -> datetime.timedelta:
293
- """
294
- Returns a `timedelta` object with the duration, i.e. time difference between dt_start and dt_end.
295
-
296
- Notes:
297
- If you need the number of seconds of your measurement, use the `total_seconds()` method of
298
- the timedelta object.
299
-
300
- Even if you —by accident— switch the start and end time arguments, the duration will
301
- be calculated as expected.
302
-
303
- Args:
304
- dt_start: start time of the measurement
305
- dt_end: end time of the measurement
306
-
307
- Returns:
308
- The time difference (duration) between dt_start and dt_end.
309
- """
310
- if isinstance(dt_start, str):
311
- dt_start = str_to_datetime(dt_start)
312
- if isinstance(dt_end, str):
313
- dt_end = str_to_datetime(dt_end)
314
-
315
- return dt_end - dt_start if dt_end > dt_start else dt_start - dt_end
316
-
317
-
318
- def time_since_epoch_1958(datetime_string: str):
319
- """Calculate the time since epoch 1958 for the given string representation of a datetime.
320
-
321
- Args:
322
- - datetime_string: String representing a datetime, in the format %Y-%m-%dT%H:%M:%S.%f%z.
323
-
324
- Returns: Time since the 1958 epoch [s].
325
- """
326
-
327
- time_since_epoch_1970 = str_to_datetime(datetime_string).timestamp() # Since Jan 1st, 1970, midnight
328
-
329
- return time_since_epoch_1970 + EPOCH_1958_1970
330
-
331
-
332
- class Timer(object):
333
- """
334
- Context manager to benchmark some lines of code.
335
-
336
- When the context exits, the elapsed time is sent to the default logger (level=INFO).
337
-
338
- Elapsed time can be logged with the `log_elapsed()` method and requested in fractional seconds
339
- by calling the class instance. When the contexts goes out of scope, the elapsed time will not
340
- increase anymore.
341
-
342
- Log messages are sent to the logger (including egse_logger for egse.system) and the logging
343
- level can be passed in as an optional argument. Default logging level is INFO.
344
-
345
- Examples:
346
- >>> with Timer("Some calculation") as timer:
347
- ... # do some calculations
348
- ... timer.log_elapsed()
349
- ... # do some more calculations
350
- ... print(f"Elapsed seconds: {timer()}") # doctest: +ELLIPSIS
351
- Elapsed seconds: ...
352
-
353
- Args:
354
- name (str): a name for the Timer, will be printed in the logging message
355
- precision (int): the precision for the presentation of the elapsed time
356
- (number of digits behind the comma ;)
357
- log_level (int): the log level to report the timing [default=INFO]
358
-
359
- Returns:
360
- a context manager class that records the elapsed time.
361
- """
362
-
363
- def __init__(self, name="Timer", precision=3, log_level=logging.INFO):
364
- self.name = name
365
- self.precision = precision
366
- self.log_level = log_level
367
- caller_info = get_caller_info(level=2)
368
- self.filename = caller_info.filename
369
- self.func = caller_info.function
370
- self.lineno = caller_info.lineno
371
-
372
- def __enter__(self):
373
- # start is a value containing the start time in fractional seconds
374
- # end is a function which returns the time in fractional seconds
375
- self.start = time.perf_counter()
376
- self.end = time.perf_counter
377
- return self
378
-
379
- def __exit__(self, ty, val, tb):
380
- # The context goes out of scope here and we fix the elapsed time
381
- self._total_elapsed = time.perf_counter()
382
-
383
- # Overwrite self.end() so that it always returns the fixed end time
384
- self.end = self._end
385
-
386
- logger.log(self.log_level, f"{self.name} [{self.filename}:{self.func}:{self.lineno}]: "
387
- f"{self.end() - self.start:0.{self.precision}f} seconds")
388
- return False
389
-
390
- def __call__(self):
391
- return self.end() - self.start
392
-
393
- def log_elapsed(self):
394
- """Sends the elapsed time info to the default logger."""
395
- logger.log(self.log_level, f"{self.name} [{self.func}:{self.lineno}]: "
396
- f"{self.end() - self.start:0.{self.precision}f} seconds elapsed")
397
-
398
- def get_elapsed(self) -> float:
399
- """Returns the elapsed time for this timer as a float in seconds."""
400
- return self.end() - self.start
401
-
402
- def _end(self):
403
- return self._total_elapsed
404
-
405
-
406
- def ping(host, timeout: float = 3.0):
407
- """
408
- Sends a ping request to the given host.
409
-
410
- Remember that a host may not respond to a ping (ICMP) request even if the host name is valid.
411
-
412
- Args:
413
- host (str): hostname or IP address (as a string)
414
- timeout (float): timeout in seconds
415
-
416
- Returns:
417
- True when host responds to a ping request.
418
-
419
- Reference:
420
- https://stackoverflow.com/a/32684938
421
- """
422
-
423
- # Option for the number of packets as a function of
424
- param = "-n" if platform.system().lower() == "windows" else "-c"
425
-
426
- # Building the command. Ex: "ping -c 1 google.com"
427
- command = ["ping", param, "1", host]
428
-
429
- try:
430
- return subprocess.call(command, stdout=subprocess.DEVNULL, timeout=timeout) == 0
431
- except subprocess.TimeoutExpired:
432
- logging.info(f"Ping to {host} timed out in {timeout} seconds.")
433
- return False
434
-
435
-
436
- def get_host_ip() -> Optional[str]:
437
- """Returns the IP address."""
438
-
439
- host_ip = None
440
-
441
- # The following code needs internet access
442
-
443
- try:
444
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
445
- # sock.connect(("8.8.8.8", 80))
446
- sock.connect(("10.255.255.255", 1))
447
- host_ip = sock.getsockname()[0]
448
- sock.close()
449
- except Exception as exc:
450
- logger.warning(f"Exception caught: {exc}")
451
-
452
- if host_ip:
453
- return host_ip
454
-
455
- # This may still return 127.0.0.1 when hostname is defined in /etc/hosts
456
- try:
457
- host_name = socket.gethostname()
458
- host_ip = socket.gethostbyname(host_name)
459
- return host_ip
460
- except (Exception,):
461
- return None
462
-
463
-
464
- CallerInfo = namedtuple("CallerInfo", "filename function lineno")
465
-
466
-
467
- def get_caller_info(level=1) -> CallerInfo:
468
- """
469
- Returns the filename, function name and lineno of the caller.
470
-
471
- The level indicates how many levels to go back in the stack.
472
- When level is 0 information about this function will be returned. That is usually not
473
- what you want so the default level is 1 which returns information about the function
474
- where the call to `get_caller_info` was made.
475
-
476
- There is no check
477
- Args:
478
- level (int): the number of levels to go back in the stack
479
-
480
- Returns:
481
- a namedtuple: CallerInfo['filename', 'function', 'lineno'].
482
- """
483
- frame = inspect.currentframe()
484
- for _ in range(level):
485
- if frame.f_back is None:
486
- break
487
- frame = frame.f_back
488
- frame_info = inspect.getframeinfo(frame)
489
-
490
- return CallerInfo(frame_info.filename, frame_info.function, frame_info.lineno)
491
-
492
-
493
- def get_referenced_var_name(obj: Any) -> List[str]:
494
- """
495
- Returns a list of variable names that reference the given object.
496
- The names can be both in the local and global namespace of the object.
497
-
498
- Args:
499
- obj (Any): object for which the variable names are returned
500
-
501
- Returns:
502
- a list of variable names.
503
- """
504
- frame = inspect.currentframe().f_back
505
- f_locals = frame.f_locals
506
- f_globals = frame.f_globals
507
- if "self" in f_locals:
508
- f_locals = frame.f_back.f_locals
509
- name_set = [k for k, v in {**f_locals, **f_globals}.items() if v is obj]
510
- return name_set or []
511
-
512
-
513
- class AttributeDict(dict):
514
- """
515
- This class is and acts like a dictionary but has the additional functionality
516
- that all keys in the dictionary are also accessible as instance attributes.
517
-
518
- >>> ad = AttributeDict({'a': 1, 'b': 2, 'c': 3})
519
-
520
- >>> assert ad.a == ad['a']
521
- >>> assert ad.b == ad['b']
522
- >>> assert ad.c == ad['c']
523
-
524
- Similarly, adding or defining attributes will make them also keys in the dict.
525
-
526
- >>> ad.d = 4 # creates a new attribute
527
- >>> print(ad['d']) # prints 4
528
- 4
529
- """
530
-
531
- def __init__(self, *args, label: str = None, **kwargs):
532
- super().__init__(*args, **kwargs)
533
- self.__dict__["_label"] = label
534
-
535
- __setattr__ = dict.__setitem__
536
- __delattr__ = dict.__delitem__
537
-
538
- def __getattr__(self, key):
539
- try:
540
- return self[key]
541
- except KeyError:
542
- raise AttributeError(key)
543
-
544
- def __rich__(self) -> Tree:
545
- label = self.__dict__["_label"] or "AttributeDict"
546
- tree = Tree(label, guide_style="dim")
547
- walk_dict_tree(self, tree, text_style="dark grey")
548
- return tree
549
-
550
- def __repr__(self):
551
- # We only want the first 10 key:value pairs
552
-
553
- count = 10
554
- sub_msg = ", ".join(f"{k!r}:{v!r}" for k, v in itertools.islice(self.items(), 0, count))
555
-
556
- # if we left out key:value pairs, print a ', ...' to indicate incompleteness
557
-
558
- return self.__class__.__name__ + f"({{{sub_msg}{', ...' if len(self) > count else ''}}})"
559
-
560
-
561
- def walk_dict_tree(dictionary: dict, tree: Tree, text_style: str = "green"):
562
- for k, v in dictionary.items():
563
- if isinstance(v, dict):
564
- branch = tree.add(f"[purple]{k}", style="", guide_style="dim")
565
- walk_dict_tree(v, branch, text_style=text_style)
566
- else:
567
- text = Text.assemble((str(k), "medium_purple1"), ": ", (str(v), text_style))
568
- tree.add(text)
569
-
570
-
571
- def recursive_dict_update(this: dict, other: dict):
572
- """
573
- Recursively update a dictionary `this` with the content of another dictionary `other`.
574
-
575
- Any key in `this` dictionary will be recursively updated with the value of the same key in the
576
- `other` dictionary.
577
-
578
- Please note that the update will be in-place, i.e. the `this` dictionaory will be
579
- changed/updated.
580
-
581
- >>> global_settings = {"A": "GA", "B": "GB", "C": "GC"}
582
- >>> local_settings = {"B": "LB", "D": "LD"}
583
- >>> {**global_settings, **local_settings}
584
- {'A': 'GA', 'B': 'LB', 'C': 'GC', 'D': 'LD'}
585
-
586
- >>> global_settings = {"A": "GA", "B": "GB", "C": "GC", "R": {"X": "GX", "Y": "GY"}}
587
- >>> local_settings = {"B": "LB", "D": "LD", "R": {"Y": "LY"}}
588
- >>> recursive_dict_update(global_settings, local_settings)
589
- {'A': 'GA', 'B': 'LB', 'C': 'GC', 'R': {'X': 'GX', 'Y': 'LY'}, 'D': 'LD'}
590
-
591
- >>> global_settings = {"A": {"B": {"C": {"D": 42}}}}
592
- >>> local_settings = {"A": {"B": {"C": 13, "D": 73}}}
593
- >>> recursive_dict_update(global_settings, local_settings)
594
- {'A': {'B': {'C': 13, 'D': 73}}}
595
-
596
- Args:
597
- this (dict): The origin dictionary
598
- other (dict): Changes that shall be applied to `this`
599
-
600
- Returns:
601
- A new dictionary with the recursive updates.
602
- """
603
-
604
- if not isinstance(this, dict) or not isinstance(other, dict):
605
- raise ValueError("Expected arguments of type dict.")
606
-
607
- for key, value in other.items():
608
- if isinstance(value, dict) and isinstance(this.get(key), dict):
609
- this[key] = recursive_dict_update(this[key], other[key])
610
- else:
611
- this[key] = other[key]
612
-
613
- return this
614
-
615
-
616
- def flatten_dict(source_dict: dict):
617
- """
618
- Flatten the given dictionary concatenating the keys with a colon ':'.
619
-
620
- >>> d = {"A": 1, "B": {"E": {"F": 2}}, "C": {"D": 3}}
621
- >>> flatten_dict(d)
622
- {'A': 1, 'B:E:F': 2, 'C:D': 3}
623
-
624
- >>> d = {"A": 'a', "B": {"C": {"D": 'd', "E": 'e'}, "F": 'f'}}
625
- >>> flatten_dict(d)
626
- {'A': 'a', 'B:C:D': 'd', 'B:C:E': 'e', 'B:F': 'f'}
627
-
628
- Args:
629
- source_dict: the original dictionary that will be flattened
630
-
631
- Returns:
632
- A new flattened dictionary.
633
- """
634
-
635
- def expand(key, value):
636
- if isinstance(value, dict):
637
- return [(key + ":" + k, v) for k, v in flatten_dict(value).items()]
638
- else:
639
- return [(key, value)]
640
-
641
- items = [item for k, v in source_dict.items() for item in expand(k, v)]
642
-
643
- return dict(items)
644
-
645
-
646
- def get_system_stats():
647
- """
648
- Gather system information about the CPUs and memory usage and return a dictionary with the
649
- following information:
650
-
651
- * cpu_load: load average over a period of 1, 5,and 15 minutes given in in percentage
652
- (i.e. related to the number of CPU cores that are installed on your system) [percentage]
653
- * cpu_count: physical and logical CPU count, i.e. the number of CPU cores (incl. hyper-threads)
654
- * total_ram: total physical ram available [bytes]
655
- * avail_ram: the memory that can be given instantly to processes without the system going
656
- into swap. This is calculated by summing different memory values depending on the platform
657
- [bytes]
658
- * boot_time: the system boot time expressed in seconds since the epoch [s]
659
- * since: boot time of the system, aka Up time [str]
660
-
661
- Returns:
662
- a dictionary with CPU and memory statistics.
663
- """
664
- statistics = {}
665
-
666
- # Get Physical and Logical CPU Count
667
-
668
- physical_and_logical_cpu_count = psutil.cpu_count()
669
- statistics["cpu_count"] = physical_and_logical_cpu_count
670
-
671
- # Load average
672
- # This is the average system load calculated over a given period of time of 1, 5 and 15 minutes.
673
- #
674
- # The numbers returned by psutil.getloadavg() only make sense if
675
- # related to the number of CPU cores installed on the system.
676
- #
677
- # Here we are converting the load average into percentage.
678
- # The higher the percentage the higher the load.
679
-
680
- cpu_load = [x / physical_and_logical_cpu_count * 100 for x in psutil.getloadavg()]
681
- statistics["cpu_load"] = cpu_load
682
-
683
- # Memory usage
684
-
685
- vmem = psutil.virtual_memory()
686
-
687
- statistics["total_ram"] = vmem.total
688
- statistics["avail_ram"] = vmem.available
689
-
690
- # boot_time = seconds since the epoch timezone
691
- # the Unix epoch is 00:00:00 UTC on 1 January 1970.
692
-
693
- boot_time = psutil.boot_time()
694
- statistics["boot_time"] = boot_time
695
- statistics["since"] = datetime.datetime.fromtimestamp(boot_time, tz=datetime.timezone.utc).strftime(
696
- "%Y-%m-%d %H:%M:%S"
697
- )
698
-
699
- return statistics
700
-
701
-
702
- def get_system_name() -> str:
703
- """Returns the name of the system in lower case.
704
-
705
- Returns:
706
- name: 'linux', 'darwin', 'windows', ...
707
- """
708
- return platform.system().lower()
709
-
710
-
711
- def get_os_name() -> str:
712
- """Returns the name of the OS in lower case.
713
-
714
- If no name could be determined, 'unknown' is returned.
715
-
716
- Returns:
717
- os: 'macos', 'centos'
718
- """
719
- sys_name = get_system_name()
720
- if sys_name == "darwin":
721
- return "macos"
722
- if sys_name == "linux":
723
- return distro.id().lower()
724
- if sys_name == "windows":
725
- return "windows"
726
- return "unknown"
727
-
728
-
729
- def get_os_version() -> str:
730
- """Return the version of the OS.
731
-
732
- If no version could be determined, 'unknown' is returned.
733
-
734
- Returns:
735
- version: as '10.15' or '8.0' or 'unknown'
736
- """
737
-
738
- # Don't use `distro.version()` to get the macOS version. That function will return the version
739
- # of the Darwin kernel.
740
-
741
- os_name = get_os_name()
742
- sys_name = get_system_name()
743
- if os_name == "unknown":
744
- return "unknown"
745
- if os_name == "macos":
746
- version, _, _ = platform.mac_ver()
747
- return ".".join(version.split(".")[:2])
748
- if sys_name == "linux":
749
- return distro.version()
750
-
751
- # FIXME: add other OS here for their version number
752
-
753
- return "unknown"
754
-
755
-
756
- def wait_until(condition, *args, interval=0.1, timeout=1, verbose=False, **kwargs) -> int:
757
- """
758
- Sleep until the given condition is fulfilled. The arguments are passed into the condition
759
- callable which is called in a while loop until the condition is met or the timeout is reached.
760
-
761
- Note that the condition can be a function, method or callable class object.
762
- An example of the latter is:
763
-
764
- class SleepUntilCount:
765
- def __init__(self, end):
766
- self._end = end
767
- self._count = 0
768
-
769
- def __call__(self, *args, **kwargs):
770
- self._count += 1
771
- if self._count >= self._end:
772
- return True
773
- else:
774
- return False
775
-
776
-
777
- Args:
778
- condition: a callable that returns True when the condition is met, False otherwise
779
- interval: the sleep interval between condition checks [s, default=0.1]
780
- timeout: the period after which the function returns, even when the condition is
781
- not met [s, default=1]
782
- verbose: log debugging messages if True
783
- *args: any arguments that will be passed into the condition function
784
-
785
- Returns:
786
- True when function timed out, False otherwise
787
- """
788
-
789
- if inspect.isfunction(condition) or inspect.ismethod(condition):
790
- func_name = condition.__name__
791
- else:
792
- func_name = condition.__class__.__name__
793
-
794
- caller = get_caller_info(level=2)
795
-
796
- start = time.time()
797
-
798
- while not condition(*args, **kwargs):
799
- if time.time() - start > timeout:
800
- logger.warning(
801
- f"Timeout after {timeout} sec, from {caller.filename} at {caller.lineno},"
802
- f" {func_name}{args} not met."
803
- )
804
- return True
805
- time.sleep(interval)
806
-
807
- if verbose:
808
- logger.debug(f"wait_until finished successfully, {func_name}{args}{kwargs} is met.")
809
-
810
- return False
811
-
812
-
813
- def waiting_for(condition, *args, interval=0.1, timeout=1, verbose=False, **kwargs):
814
- """
815
- Sleep until the given condition is fulfilled. The arguments are passed into the condition
816
- callable which is called in a while loop until the condition is met or the timeout is reached.
817
-
818
- Note that the condition can be a function, method or callable class object.
819
- An example of the latter is:
820
-
821
- class SleepUntilCount:
822
- def __init__(self, end):
823
- self._end = end
824
- self._count = 0
825
-
826
- def __call__(self, *args, **kwargs):
827
- self._count += 1
828
- if self._count >= self._end:
829
- return True
830
- else:
831
- return False
832
-
833
-
834
- Args:
835
- condition: a callable that returns True when the condition is met, False otherwise
836
- interval: the sleep interval between condition checks [s, default=0.1]
837
- timeout: the period after which the function returns, even when the condition is
838
- not met [s, default=1]
839
- verbose: log debugging messages if True
840
- *args: any arguments that will be passed into the condition function
841
-
842
- Raises:
843
- A TimeoutError when the condition was not fulfilled within the timeout period.
844
- """
845
-
846
- if inspect.isfunction(condition) or inspect.ismethod(condition):
847
- func_name = condition.__name__
848
- else:
849
- func_name = condition.__class__.__name__
850
-
851
- caller = get_caller_info(level=2)
852
-
853
- start = time.time()
854
-
855
- while not condition(*args, **kwargs):
856
- if time.time() - start > timeout:
857
- raise TimeoutError(
858
- f"Timeout after {timeout} sec, from {caller.filename} at {caller.lineno},"
859
- f" {func_name}{args} not met."
860
- )
861
- time.sleep(interval)
862
-
863
- duration = time.time() - start
864
-
865
- if verbose:
866
- logger.debug(f"waiting_for finished successfully after {duration:.3f}s, {func_name}{args}{kwargs} is met.")
867
-
868
- return duration
869
-
870
-
871
- def has_internet(host="8.8.8.8", port=53, timeout=3):
872
- """Returns True if we have internet connection.
873
-
874
- Host: 8.8.8.8 (google-public-dns-a.google.com)
875
- OpenPort: 53/tcp
876
- Service: domain (DNS/TCP)
877
-
878
- .. Note::
879
-
880
- This might give the following error codes:
881
-
882
- * [Errno 51] Network is unreachable
883
- * [Errno 61] Connection refused (because the port is blocked?)
884
- * timed out
885
-
886
- Source: https://stackoverflow.com/a/33117579
887
- """
888
- try:
889
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
890
- s.settimeout(timeout)
891
- s.connect((host, port))
892
- return True
893
- except socket.error as ex:
894
- logging.info(f"No Internet: Unable to open socket to {host}:{port} [{ex}]")
895
- return False
896
- finally:
897
- if s is not None:
898
- s.close()
899
-
900
-
901
- def do_every(period: float, func: callable, *args) -> None:
902
- """
903
-
904
- This method executes a function periodically, taking into account
905
- that the function that is executed will take time also and using a
906
- simple `sleep()` will cause a drift. This method will not drift.
907
-
908
- You can use this function in combination with the threading module to execute the
909
- function in the background, but be careful as the function might not be thread safe.
910
-
911
- ```
912
- timer_thread = threading.Thread(target=do_every, args=(10, func))
913
- timer_thread.daemon = True
914
- timer_thread.start()
915
- ```
916
-
917
- Args:
918
- period: a time interval between successive executions [seconds]
919
- func: the function to be executed
920
- *args: optional arguments to be passed to the function
921
- """
922
-
923
- # Code from SO:https://stackoverflow.com/a/28034554/4609203
924
- # The max in the yield line serves to protect sleep from negative numbers in case the
925
- # function being called takes longer than the period specified. In that case it would
926
- # execute immediately and make up the lost time in the timing of the next execution.
927
-
928
- def g_tick():
929
- next_time = time.time()
930
- while True:
931
- next_time += period
932
- yield max(next_time - time.time(), 0)
933
-
934
- g = g_tick()
935
- while True:
936
- time.sleep(next(g))
937
- func(*args)
938
-
939
-
940
- @contextlib.contextmanager
941
- def chdir(dirname=None):
942
- """
943
- Context manager to temporarily change directory.
944
-
945
- Args:
946
- dirname (str or Path): temporary folder name to switch to within the context
947
-
948
- Examples:
949
-
950
- >>> with chdir('/tmp'):
951
- ... # do stuff in this writable tmp folder
952
- ... pass
953
-
954
- """
955
- current_dir = os.getcwd()
956
- try:
957
- if dirname is not None:
958
- os.chdir(dirname)
959
- yield
960
- finally:
961
- os.chdir(current_dir)
962
-
963
-
964
- @contextlib.contextmanager
965
- def env_var(**kwargs):
966
- """
967
- Context manager to run some code that need alternate settings for environment variables.
968
-
969
- Args:
970
- **kwargs: dictionary with environment variables that are needed
971
-
972
- Examples:
973
-
974
- >>> with env_var(PLATO_DATA_STORAGE_LOCATION="/Users/rik/data"):
975
- ... # do stuff that needs these alternate setting
976
- ... pass
977
-
978
- """
979
- saved_env = {}
980
- try:
981
- for k, v in kwargs.items():
982
- saved_env[k] = os.environ.get(k)
983
- os.environ[k] = v
984
- yield
985
- finally:
986
- for k, v in saved_env.items():
987
- if v is not None:
988
- os.environ[k] = v
989
- else:
990
- del os.environ[k]
991
-
992
-
993
- def filter_by_attr(elements: Iterable, **attrs) -> List:
994
- """
995
- A helper that returns the elements from the iterable that meet all the traits passed in `attrs`.
996
-
997
- The attributes are compared to their value with the `operator.eq` function. However,
998
- when the given value for an attribute is a tuple, the first element in the tuple is
999
- considered a comparison function and the second value the actual value. The attribute
1000
- is then compared to the value using this function.
1001
-
1002
- ```
1003
- result = filter_by_attr(setups, camera__model="EM", site_id=(is_in, ("CSL", "INTA")))
1004
- ```
1005
- The function `is_in` is defined as follows:
1006
- ```
1007
- def is_in(a, b):
1008
- return a in b
1009
- ```
1010
- but you can of course also use a lambda function: `lambda a, b: a in b`.
1011
-
1012
- One function is treated special, it is the built-in function `hasattr`. Using this function,
1013
- the value can be `True` or `False`. Use this to return all elements in the iterable
1014
- that have the attribute, or not. The following example returns all Setups where the
1015
- `gse.ogse.fwc_factor` is not defined:
1016
- ```
1017
- result = filter_by_attr(setups, camera__model="EM", gse__ogse__fwc_factor=(hasattr, False)))
1018
- ```
1019
-
1020
- When multiple attributes are specified, they are checked using logical AND, not logical OR.
1021
- Meaning they have to meet every attribute passed in and not one of them.
1022
-
1023
- To have a nested attribute search (i.e. search by `gse.hexapod.ID`) then
1024
- pass in `gse__hexapod__ID` as the keyword argument.
1025
-
1026
- If nothing is found that matches the attributes passed, then an empty list is returned.
1027
-
1028
- When an attribute is not part of the iterated object, that attribute is silently ignored.
1029
-
1030
- Args:
1031
- elements: An iterable to search through.
1032
- attrs: Keyword arguments that denote attributes to search with.
1033
- """
1034
-
1035
- # This code is based on and originates from the get(iterable, **attr) function in the
1036
- # discord/utils.py package (https://github.com/Rapptz/discord.py). After my own version,
1037
- # Ruud van der Ham, improved the code drastically to the version it is now.
1038
-
1039
- def check(attr_, func, value_, el):
1040
- try:
1041
- a = operator.attrgetter(attr_)(el)
1042
- return value_ if func is hasattr else func(a, value_)
1043
- except AttributeError:
1044
- return not value_ if func is hasattr else False
1045
-
1046
- attr_func_values = []
1047
- for attr, value in attrs.items():
1048
- if not (isinstance(value, (tuple, list)) and len(value) == 2 and callable(value[0])):
1049
- value = (operator.eq, value)
1050
- attr_func_values.append((attr.replace("__", "."), *value))
1051
-
1052
- return [el for el in elements if all(check(attr, func, value, el) for attr, func, value in attr_func_values)]
1053
-
1054
-
1055
- def replace_environment_variable(input_string: str):
1056
- """Returns the `input_string` with all occurrences of ENV['var'].
1057
-
1058
- >>> replace_environment_variable("ENV['HOME']/data/CSL")
1059
- '/Users/rik/data/CSL'
1060
-
1061
- Args:
1062
- input_string (str): the string to replace
1063
- Returns:
1064
- The input string with the ENV['var'] replaced, or None when the environment variable
1065
- doesn't exists.
1066
- """
1067
-
1068
- match = re.search(r"(.*)ENV\[['\"](\w+)['\"]\](.*)", input_string)
1069
- if not match:
1070
- return input_string
1071
- pre_match = match.group(1)
1072
- var = match.group(2)
1073
- post_match = match.group(3)
1074
-
1075
- result = os.getenv(var, None)
1076
-
1077
- return pre_match + result + post_match if result else None
1078
-
1079
-
1080
- def read_last_line(filename: str, max_line_length=5000):
1081
- """Returns the last line of a (text) file.
1082
-
1083
- The argument `max_line_length` should be at least the length of the last line in the file,
1084
- because this value is used to backtrack from the end of the file as an optimization.
1085
-
1086
- Args:
1087
- filename (Option[PurePath, str]): the filename as a string or Path
1088
- max_line_length (int): the maximum length of the lines in the file
1089
- Returns:
1090
- The last line in the file (whitespace stripped from the right). An empty string is returned
1091
- when the file is empty, `None` is returned when the file doesn't exist.
1092
- """
1093
- filename = Path(filename)
1094
-
1095
- if not filename.exists():
1096
- return None
1097
-
1098
- with filename.open("rb") as file:
1099
- file.seek(0, 2) # 2 is relative to end of file
1100
- size = file.tell()
1101
- if size:
1102
- file.seek(max(0, size - max_line_length))
1103
- return file.readlines()[-1].decode("utf-8").rstrip("\n")
1104
- else:
1105
- return ""
1106
-
1107
-
1108
- def read_last_lines(filename: str, num_lines: int):
1109
- """Return the last lines of a text file.
1110
-
1111
- Args:
1112
- - filename: Filename.
1113
- - num_lines: Number of lines at the back of the file that should be read and returned.
1114
-
1115
- Returns: Last lines of a text file.
1116
- """
1117
-
1118
- # See: https://www.geeksforgeeks.org/python-reading-last-n-lines-of-a-file/
1119
- # (Method 3: Through exponential search)
1120
-
1121
- filename = Path(filename)
1122
-
1123
- assert num_lines > 1
1124
-
1125
- if not filename.exists():
1126
- return None
1127
-
1128
- assert num_lines >= 0
1129
-
1130
- # Declaring variable to implement exponential search
1131
-
1132
- pos = num_lines + 1
1133
-
1134
- # List to store last N lines
1135
-
1136
- lines = []
1137
-
1138
- with open(filename) as f:
1139
- while len(lines) <= num_lines:
1140
- try:
1141
- f.seek(-pos, 2)
1142
-
1143
- except IOError:
1144
- f.seek(0)
1145
- break
1146
-
1147
- finally:
1148
- lines = list(f)
1149
-
1150
- # Increasing value of variable exponentially
1151
-
1152
- pos *= 2
1153
-
1154
- return lines[-num_lines:]
1155
-
1156
-
1157
- def is_namespace(module) -> bool:
1158
- """
1159
- Checks if a module represents a namespace package.
1160
-
1161
- A namespace package is defined as a module that has a '__path__' attribute
1162
- and no '__file__' attribute.
1163
-
1164
- Args:
1165
- module: The module to be checked.
1166
-
1167
- Returns:
1168
- bool: True if the module is a namespace package, False otherwise.
1169
-
1170
- Note:
1171
- A namespace package is a special kind of package that does not contain
1172
- an actual implementation but serves as a container for other packages
1173
- or modules.
1174
-
1175
- """
1176
-
1177
- if hasattr(module, '__path__') and getattr(module, '__file__', None) is None:
1178
- return True
1179
- else:
1180
- return False
1181
-
1182
-
1183
- def get_package_location(module) -> List[Path]:
1184
- """
1185
- Retrieves the file system locations associated with a Python package.
1186
-
1187
- This function takes a module, module name, or fully qualified module path,
1188
- and returns a list of Path objects representing the file system locations
1189
- associated with the package. If the module is a namespace package, it returns
1190
- the paths of all namespaces; otherwise, it returns the location of the module.
1191
-
1192
- Args:
1193
- module (Union[FunctionType, ModuleType, str]): The module or module name to
1194
- retrieve locations for.
1195
-
1196
- Returns:
1197
- List[Path]: A list of Path objects representing the file system locations.
1198
-
1199
- Note:
1200
- If the module is not found or is not a valid module, an empty list is returned.
1201
-
1202
- """
1203
-
1204
- if isinstance(module, FunctionType):
1205
- module_name = module.__module__
1206
- elif isinstance(module, ModuleType):
1207
- module_name = module.__name__
1208
- elif isinstance(module, str):
1209
- module_name = module
1210
- else:
1211
- return []
1212
-
1213
- try:
1214
- module = importlib.import_module(module)
1215
- except TypeError as exc:
1216
- return []
1217
-
1218
- if is_namespace(module):
1219
- return [
1220
- Path(location)
1221
- for location in module.__path__
1222
- ]
1223
- else:
1224
- location = get_module_location(module)
1225
- return [] if location is None else [location]
1226
-
1227
-
1228
- def get_module_location(arg) -> Optional[Path]:
1229
- """
1230
- Returns the location of the module as a Path object.
1231
-
1232
- The function accepts a string (module name), a function, or a module.
1233
- For functions and modules, the module name is determined internally.
1234
-
1235
- Args:
1236
- arg (Union[FunctionType, ModuleType, str]): The function, module, or module name.
1237
-
1238
- Returns:
1239
- Optional[Path]: The location of the module as a Path object, or None if the location
1240
- cannot be determined or an invalid argument is provided.
1241
-
1242
- Example:
1243
- >>> get_module_location('egse')
1244
- Path('/path/to/egse')
1245
-
1246
- >>> get_module_location(egse.system)
1247
- Path('/path/to/egse/system')
1248
-
1249
- Note:
1250
- If the module is not found or is not a valid module, None is returned.
1251
-
1252
- """
1253
- if isinstance(arg, FunctionType):
1254
- module_name = arg.__module__
1255
- elif isinstance(arg, ModuleType):
1256
- module_name = arg.__name__
1257
- elif isinstance(arg, str):
1258
- module_name = arg
1259
- else:
1260
- return None
1261
-
1262
- try:
1263
- module = importlib.import_module(module_name)
1264
- except TypeError as exc:
1265
- return None
1266
-
1267
- return Path(module.__file__).parent.resolve()
1268
-
1269
-
1270
- def get_full_classname(obj: object) -> str:
1271
- """
1272
- Returns the fully qualified class name for the given object.
1273
-
1274
- Args:
1275
- obj (object): The object for which to retrieve the fully qualified class name.
1276
-
1277
- Returns:
1278
- str: The fully qualified class name, including the module.
1279
-
1280
- Example:
1281
- >>> get_full_classname("example")
1282
- 'builtins.str'
1283
-
1284
- >>> get_full_classname(42)
1285
- 'builtins.int'
1286
-
1287
- Note:
1288
- The function considers various scenarios, such as objects being classes,
1289
- built-ins, or literals like int, float, or complex numbers.
1290
-
1291
- """
1292
-
1293
- if type(obj) is type or obj.__class__.__module__ == str.__module__:
1294
- try:
1295
- module = obj.__module__
1296
- name = obj.__qualname__
1297
- except (TypeError, AttributeError):
1298
- module = type(obj).__module__
1299
- name = obj.__class__.__qualname__
1300
- else:
1301
- module = obj.__class__.__module__
1302
- name = obj.__class__.__qualname__
1303
-
1304
- return module + "." + name
1305
-
1306
-
1307
- def find_class(class_name: str):
1308
- """Find and returns a class based on the fully qualified name.
1309
-
1310
- A class name can be preceded with the string `class//`. This is used in YAML
1311
- files where the class is then instantiated on load.
1312
-
1313
- Args:
1314
- class_name (str): a fully qualified name for the class
1315
- Returns:
1316
- The class object corresponding to the fully qualified class name.
1317
- Raises:
1318
- AttributeError: when the class is not found in the module.
1319
- ValueError: when the class_name can not be parsed.
1320
- ModuleNotFoundError: if the module could not be found.
1321
- """
1322
- if class_name.startswith("class//"):
1323
- class_name = class_name[7:]
1324
-
1325
- module_name, class_name = class_name.rsplit(".", 1)
1326
- module = importlib.import_module(module_name)
1327
- return getattr(module, class_name)
1328
-
1329
-
1330
- def type_name(var):
1331
- """Returns the name of the type of var."""
1332
- return type(var).__name__
1333
-
1334
-
1335
- def check_argument_type(obj: object, name: str, target_class: Union[type, Tuple[type]], allow_none: bool = False):
1336
- """Check that the given object is of a specific (sub)type of the given target_class.
1337
-
1338
- The target_class can be a tuple of types.
1339
-
1340
- Raises:
1341
- TypeError when not of the required type or None when not allowed.
1342
- """
1343
- if obj is None and allow_none:
1344
- return
1345
- if obj is None:
1346
- raise TypeError(f"The argument '{name}' cannot be None.")
1347
- if not isinstance(obj, target_class):
1348
- raise TypeError(f"The argument '{name}' must be of type {target_class}, but is {type(obj)}")
1349
-
1350
-
1351
- def check_str_for_slash(arg: str):
1352
- """Check if there is a slash in the given string, and raise a ValueError if so."""
1353
-
1354
- if "/" in arg:
1355
- ValueError(f"The given argument can not contain slashes, {arg=}.")
1356
-
1357
-
1358
- def check_is_a_string(var, allow_none=False):
1359
- """
1360
- Checks if the given variable is a string and raises a TypeError if the check fails.
1361
-
1362
- Args:
1363
- var: The variable to be checked.
1364
- allow_none (bool, optional): If True, allows the variable to be None without raising an error.
1365
- Defaults to False.
1366
-
1367
- Raises:
1368
- TypeError: If the variable is not a string or is None (when allow_none is False).
1369
-
1370
- Example:
1371
- >>> check_is_a_string("example")
1372
-
1373
- Note:
1374
- This function is designed to validate that the input variable is a string.
1375
- If `allow_none` is set to True, it allows the variable to be None without raising an error.
1376
-
1377
- """
1378
-
1379
- if var is None and allow_none:
1380
- return
1381
- if var is None and not allow_none:
1382
- raise TypeError("The given variable cannot be None.")
1383
- if not isinstance(var, str):
1384
- raise TypeError(f"var must be a string, however {type(var)=}")
1385
-
1386
-
1387
- def sanity_check(flag: bool, msg: str):
1388
- """
1389
- Checks a boolean flag and raises an AssertionError with the provided message if the check fails.
1390
-
1391
- This function serves as a replacement for the 'assert' statement in production code.
1392
- Using this ensures that your checks are not removed during optimizations.
1393
-
1394
- Args:
1395
- flag (bool): The boolean flag to be checked.
1396
- msg (str): The message to be included in the AssertionError if the check fails.
1397
-
1398
- Raises:
1399
- AssertionError: If the flag is False.
1400
-
1401
- Example:
1402
- >>> sanity_check(x > 0, "x must be greater than 0")
1403
-
1404
- Note:
1405
- This function is designed for production code to perform runtime checks
1406
- that won't be removed during optimizations.
1407
-
1408
- """
1409
-
1410
- if not flag:
1411
- raise AssertionError(msg)
1412
-
1413
-
1414
- class NotSpecified:
1415
- """Class for NOT_SPECIFIED constant.
1416
- Is used so that a parameter can have a default value other than None.
1417
-
1418
- Evaluate to False when converted to boolean.
1419
- """
1420
-
1421
- def __nonzero__(self):
1422
- """Always returns False. Called when to converting to bool in Python 2."""
1423
- return False
1424
-
1425
- def __bool__(self):
1426
- """Always returns False. Called when to converting to bool in Python 3."""
1427
- return False
1428
-
1429
-
1430
- NOT_SPECIFIED = NotSpecified()
1431
-
1432
- # Do not try to catch SIGKILL (9) that will just terminate your script without any warning
1433
-
1434
- SIGNAL_NAME = {
1435
- 1: "SIGHUP",
1436
- 2: "SIGINT",
1437
- 3: "SIGQUIT",
1438
- 6: "SIGABRT",
1439
- 15: "SIGTERM",
1440
- 30: "SIGUSR1",
1441
- 31: "SIGUSR2",
1442
- }
1443
-
1444
-
1445
- class SignalCatcher:
1446
- """
1447
- This class registers handler to signals. When a signal is caught, the handler is
1448
- executed and a flag for termination or user action is set to True. Check for this
1449
- flag in your application loop.
1450
-
1451
- Termination signals: 1 HUP, 2 INT, 3 QUIT, 6 ABORT, 15 TERM
1452
- User signals: 30 USR1, 31 USR2
1453
- """
1454
-
1455
- def __init__(self):
1456
- self.term_signal_received = False
1457
- self.user_signal_received = False
1458
- self.term_signals = [1, 2, 3, 6, 15]
1459
- self.user_signals = [30, 31]
1460
- for signal_number in self.term_signals:
1461
- signal.signal(signal_number, self.handler)
1462
- for signal_number in self.user_signals:
1463
- signal.signal(signal_number, self.handler)
1464
-
1465
- self._signal_number = None
1466
- self._signal_name = None
1467
-
1468
- @property
1469
- def signal_number(self):
1470
- return self._signal_number
1471
-
1472
- @property
1473
- def signal_name(self):
1474
- return self._signal_name
1475
-
1476
- def handler(self, signal_number, frame):
1477
- """Handle the known signals by setting the appropriate flag."""
1478
- logger.warning(f"Received signal {SIGNAL_NAME[signal_number]} [{signal_number}].")
1479
- if signal_number in self.term_signals:
1480
- self.term_signal_received = True
1481
- if signal_number in self.user_signals:
1482
- self.user_signal_received = True
1483
- self._signal_number = signal_number
1484
- self._signal_name = SIGNAL_NAME[signal_number]
1485
-
1486
- def clear(self, term: bool = False):
1487
- """
1488
- Call this method to clear the user signal after handling.
1489
- Termination signals are not cleared by default since the application is supposed to terminate.
1490
- Pass in a `term=True` to also clear the TERM signals, e.g. when you want to ignore some
1491
- TERM signals.
1492
- """
1493
- self.user_signal_received = False
1494
- if term:
1495
- self.term_signal_received = False
1496
- self._signal_number = None
1497
- self._signal_name = None
1498
-
1499
-
1500
- def is_in(a, b):
1501
- """Returns result of `a in b`."""
1502
- return a in b
1503
-
1504
-
1505
- def is_not_in(a, b):
1506
- """Returns result of `a not in b`."""
1507
- return a not in b
1508
-
1509
-
1510
- def is_in_ipython():
1511
- """Returns True if the code is running in IPython."""
1512
- return hasattr(builtins, "__IPYTHON__")
1513
-
1514
-
1515
- _function_timing = {}
1516
-
1517
-
1518
- def execution_time(func):
1519
- """
1520
- A decorator to save the execution time of the function. Use this decorator
1521
- if you want —by default and always— have an idea of the average execution time
1522
- of the given function.
1523
-
1524
- Use this in conjunction with the `get_average_execution_time()` function to
1525
- retrieve the average execution time for the given function.
1526
- """
1527
-
1528
- @functools.wraps(func)
1529
- def wrapper(*args, **kwargs):
1530
- return save_average_execution_time(func, *args, **kwargs)
1531
-
1532
- return wrapper
1533
-
1534
-
1535
- def save_average_execution_time(func: Callable, *args, **kwargs):
1536
- """
1537
- Executes the function 'func' with the given arguments and saves the execution time. All positional
1538
- arguments (in args) and keyword arguments (in kwargs) are passed into the function. The execution
1539
- time is saved in a deque of maximum 100 elements. When more times are added, the oldest times are
1540
- discarded. This function is used in conjunction with the `get_average_execution_time()` function.
1541
- """
1542
-
1543
- with Timer(log_level=logging.NOTSET) as timer:
1544
- response = func(*args, **kwargs)
1545
-
1546
- if func not in _function_timing:
1547
- _function_timing[func] = collections.deque(maxlen=100)
1548
-
1549
- _function_timing[func].append(timer.get_elapsed())
1550
-
1551
- return response
1552
-
1553
-
1554
- def get_average_execution_time(func: Callable) -> float:
1555
- """
1556
- Returns the average execution time of the given function. The function 'func' shall be previously executed using
1557
- the `save_average_execution_time()` function which remembers the last 100 execution times of the function.
1558
- You can also decorate your function with `@execution_time` to permanently monitor it.
1559
- The average time is a moving average over the last 100 times. If the function was never called before, 0.0 is
1560
- returned.
1561
-
1562
- This function can be used when setting a frequency to execute a certain function. When the average execution time
1563
- of the function is longer than the execution interval, the frequency shall be decreased or the process will get
1564
- stalled.
1565
- """
1566
-
1567
- # If the function was previously wrapped with the `@execution_time` wrapper, we need to get
1568
- # to the original function object because that's the one that is saved.
1569
-
1570
- with contextlib.suppress(AttributeError):
1571
- func = func.__wrapped__
1572
-
1573
- try:
1574
- d = _function_timing[func]
1575
- return sum(d) / len(d)
1576
- except KeyError:
1577
- return 0.0
1578
-
1579
-
1580
- def get_average_execution_times() -> dict:
1581
- """
1582
- Returns a dictionary with "function name": average execution time, for all function that have been
1583
- monitored in this process.
1584
- """
1585
- return {func.__name__: get_average_execution_time(func) for func in _function_timing}
1586
-
1587
-
1588
- def clear_average_execution_times():
1589
- """Clear out all function timing for this process."""
1590
- _function_timing.clear()
1591
-
1592
-
1593
- def get_system_architecture() -> str:
1594
- """
1595
- Returns the machine type. This is a string describing the processor architecture,
1596
- like i386 or arm64, but the exact string is not defined. An empty string can be returned when
1597
- the type cannot be determined.
1598
- """
1599
- return platform.machine()
1600
-
1601
-
1602
- ignore_m_warning("egse.system")
1603
-
1604
- if __name__ == "__main__":
1605
- print(f"Host IP: {get_host_ip()}")
1606
- print(f"System name: {get_system_name()}")
1607
- print(f"OS name: {get_os_name()}")
1608
- print(f"OS version: {get_os_version()}")
1609
- print(f"Architecture: {get_system_architecture()}")
1610
- print(f"Python version: {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")
1611
- print("Running in IPython") if is_in_ipython() else None