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.
- README.md +27 -0
- bump.py +85 -0
- cgse-2025.0.2.dist-info/METADATA +38 -0
- cgse-2025.0.2.dist-info/RECORD +5 -0
- {cgse-2024.7.0.dist-info → cgse-2025.0.2.dist-info}/WHEEL +1 -2
- cgse-2024.7.0.dist-info/COPYING +0 -674
- cgse-2024.7.0.dist-info/COPYING.LESSER +0 -165
- cgse-2024.7.0.dist-info/METADATA +0 -144
- cgse-2024.7.0.dist-info/RECORD +0 -660
- cgse-2024.7.0.dist-info/entry_points.txt +0 -75
- cgse-2024.7.0.dist-info/top_level.txt +0 -2
- egse/__init__.py +0 -12
- egse/__main__.py +0 -32
- egse/aeu/aeu.py +0 -5238
- egse/aeu/aeu_awg.yaml +0 -265
- egse/aeu/aeu_crio.yaml +0 -273
- egse/aeu/aeu_cs.py +0 -627
- egse/aeu/aeu_devif.py +0 -321
- egse/aeu/aeu_main_ui.py +0 -903
- egse/aeu/aeu_metrics.py +0 -131
- egse/aeu/aeu_protocol.py +0 -463
- egse/aeu/aeu_psu.yaml +0 -204
- egse/aeu/aeu_ui.py +0 -873
- egse/aeu/arbdata/FccdRead.arb +0 -2
- egse/aeu/arbdata/FccdRead_min_points.arb +0 -2
- egse/aeu/arbdata/HeaterSync_FccdRead.arb +0 -2
- egse/aeu/arbdata/HeaterSync_ccdRead25.arb +0 -2
- egse/aeu/arbdata/HeaterSync_ccdRead31_25.arb +0 -2
- egse/aeu/arbdata/HeaterSync_ccdRead37_50.arb +0 -2
- egse/aeu/arbdata/HeaterSync_ccdRead43_75.arb +0 -2
- egse/aeu/arbdata/HeaterSync_ccdRead50.arb +0 -2
- egse/aeu/arbdata/Heater_FccdRead_min_points.arb +0 -2
- egse/aeu/arbdata/ccdRead25.arb +0 -2
- egse/aeu/arbdata/ccdRead25_150ms.arb +0 -2
- egse/aeu/arbdata/ccdRead31_25.arb +0 -2
- egse/aeu/arbdata/ccdRead31_25_150ms.arb +0 -2
- egse/aeu/arbdata/ccdRead37_50.arb +0 -2
- egse/aeu/arbdata/ccdRead37_50_150ms.arb +0 -2
- egse/aeu/arbdata/ccdRead43_75.arb +0 -2
- egse/aeu/arbdata/ccdRead43_75_150ms.arb +0 -2
- egse/aeu/arbdata/ccdRead50.arb +0 -2
- egse/aeu/arbdata/ccdRead50_150ms.arb +0 -2
- egse/alert/__init__.py +0 -1049
- egse/alert/alertman.yaml +0 -37
- egse/alert/alertman_cs.py +0 -233
- egse/alert/alertman_ui.py +0 -600
- egse/alert/gsm/beaglebone.py +0 -138
- egse/alert/gsm/beaglebone.yaml +0 -51
- egse/alert/gsm/beaglebone_cs.py +0 -108
- egse/alert/gsm/beaglebone_devif.py +0 -122
- egse/alert/gsm/beaglebone_protocol.py +0 -46
- egse/bits.py +0 -318
- egse/camera.py +0 -44
- egse/collimator/__init__.py +0 -0
- egse/collimator/fcul/__init__.py +0 -0
- egse/collimator/fcul/ogse.py +0 -1077
- egse/collimator/fcul/ogse.yaml +0 -14
- egse/collimator/fcul/ogse_cs.py +0 -154
- egse/collimator/fcul/ogse_devif.py +0 -358
- egse/collimator/fcul/ogse_protocol.py +0 -132
- egse/collimator/fcul/ogse_sim.py +0 -431
- egse/collimator/fcul/ogse_ui.py +0 -1108
- egse/command.py +0 -699
- egse/config.py +0 -410
- egse/confman/__init__.py +0 -1058
- egse/confman/confman.yaml +0 -70
- egse/confman/confman_cs.py +0 -240
- egse/confman/confman_ui.py +0 -381
- egse/confman/setup_ui.py +0 -565
- egse/control.py +0 -632
- egse/coordinates/__init__.py +0 -534
- egse/coordinates/avoidance.py +0 -100
- egse/coordinates/cslmodel.py +0 -127
- egse/coordinates/laser_tracker_to_dict.py +0 -122
- egse/coordinates/point.py +0 -707
- egse/coordinates/pyplot.py +0 -194
- egse/coordinates/referenceFrame.py +0 -1279
- egse/coordinates/refmodel.py +0 -737
- egse/coordinates/rotationMatrix.py +0 -85
- egse/coordinates/transform3d_addon.py +0 -419
- egse/csl/__init__.py +0 -50
- egse/csl/commanding.py +0 -78
- egse/csl/icons/hexapod-connected-selected.svg +0 -30
- egse/csl/icons/hexapod-connected.svg +0 -30
- egse/csl/icons/hexapod-homing-selected.svg +0 -68
- egse/csl/icons/hexapod-homing.svg +0 -68
- egse/csl/icons/hexapod-retract-selected.svg +0 -56
- egse/csl/icons/hexapod-retract.svg +0 -51
- egse/csl/icons/hexapod-zero-selected.svg +0 -56
- egse/csl/icons/hexapod-zero.svg +0 -56
- egse/csl/icons/logo-puna.svg +0 -92
- egse/csl/icons/stop.svg +0 -1
- egse/csl/initialisation.py +0 -102
- egse/csl/mech_pos_settings.yaml +0 -18
- egse/das.py +0 -1240
- egse/das.yaml +0 -7
- egse/data/conf/SETUP_CSL_00000_170620_150000.yaml +0 -5
- egse/data/conf/SETUP_CSL_00001_170620_151010.yaml +0 -69
- egse/data/conf/SETUP_CSL_00002_170620_151020.yaml +0 -69
- egse/data/conf/SETUP_CSL_00003_170620_151030.yaml +0 -69
- egse/data/conf/SETUP_CSL_00004_170620_151040.yaml +0 -69
- egse/data/conf/SETUP_CSL_00005_170620_151050.yaml +0 -69
- egse/data/conf/SETUP_CSL_00006_170620_151060.yaml +0 -69
- egse/data/conf/SETUP_CSL_00007_170620_151070.yaml +0 -69
- egse/data/conf/SETUP_CSL_00008_170620_151080.yaml +0 -75
- egse/data/conf/SETUP_CSL_00010_210308_083016.yaml +0 -138
- egse/data/conf/SETUP_INTA_00000_170620_150000.yaml +0 -4
- egse/data/conf/SETUP_SRON_00000_170620_150000.yaml +0 -4
- egse/decorators.py +0 -514
- egse/device.py +0 -269
- egse/dpu/__init__.py +0 -2698
- egse/dpu/ccd_ui.py +0 -514
- egse/dpu/dpu.py +0 -783
- egse/dpu/dpu.yaml +0 -153
- egse/dpu/dpu_cs.py +0 -272
- egse/dpu/dpu_ui.py +0 -671
- egse/dpu/fitsgen.py +0 -2096
- egse/dpu/fitsgen_ui.py +0 -399
- egse/dpu/hdf5_model.py +0 -332
- egse/dpu/hdf5_ui.py +0 -277
- egse/dpu/hdf5_viewer.py +0 -506
- egse/dpu/hk_ui.py +0 -468
- egse/dpu_commands.py +0 -81
- egse/dsi/__init__.py +0 -33
- egse/dsi/_libesl.py +0 -232
- egse/dsi/constants.py +0 -296
- egse/dsi/esl.py +0 -630
- egse/dsi/rmap.py +0 -444
- egse/dsi/rmapci.py +0 -39
- egse/dsi/spw.py +0 -335
- egse/dsi/spw_state.py +0 -29
- egse/dummy.py +0 -318
- egse/dyndummy.py +0 -179
- egse/env.py +0 -278
- egse/exceptions.py +0 -88
- egse/fdir/__init__.py +0 -26
- egse/fdir/fdir_manager.py +0 -85
- egse/fdir/fdir_manager.yaml +0 -37
- egse/fdir/fdir_manager_controller.py +0 -136
- egse/fdir/fdir_manager_cs.py +0 -164
- egse/fdir/fdir_manager_interface.py +0 -15
- egse/fdir/fdir_remote.py +0 -73
- egse/fdir/fdir_remote.yaml +0 -30
- egse/fdir/fdir_remote_controller.py +0 -30
- egse/fdir/fdir_remote_cs.py +0 -94
- egse/fdir/fdir_remote_interface.py +0 -9
- egse/fdir/fdir_remote_popup.py +0 -26
- egse/fee/__init__.py +0 -106
- egse/fee/f_fee_register.yaml +0 -43
- egse/fee/feesim.py +0 -914
- egse/fee/n_fee_hk.py +0 -768
- egse/fee/nfee.py +0 -188
- egse/filterwheel/__init__.py +0 -4
- egse/filterwheel/eksma/__init__.py +0 -49
- egse/filterwheel/eksma/fw8smc4.py +0 -657
- egse/filterwheel/eksma/fw8smc4.yaml +0 -121
- egse/filterwheel/eksma/fw8smc4_cs.py +0 -144
- egse/filterwheel/eksma/fw8smc4_devif.py +0 -473
- egse/filterwheel/eksma/fw8smc4_protocol.py +0 -82
- egse/filterwheel/eksma/fw8smc4_ui.py +0 -940
- egse/filterwheel/eksma/fw8smc5.py +0 -115
- egse/filterwheel/eksma/fw8smc5.yaml +0 -105
- egse/filterwheel/eksma/fw8smc5_controller.py +0 -307
- egse/filterwheel/eksma/fw8smc5_cs.py +0 -141
- egse/filterwheel/eksma/fw8smc5_interface.py +0 -65
- egse/filterwheel/eksma/fw8smc5_simulator.py +0 -29
- egse/filterwheel/eksma/fw8smc5_ui.py +0 -1065
- egse/filterwheel/eksma/testpythonfw.py +0 -215
- egse/fov/__init__.py +0 -65
- egse/fov/fov_hk.py +0 -710
- egse/fov/fov_ui.py +0 -859
- egse/fov/fov_ui_controller.py +0 -140
- egse/fov/fov_ui_model.py +0 -200
- egse/fov/fov_ui_view.py +0 -345
- egse/gimbal/__init__.py +0 -32
- egse/gimbal/symetrie/__init__.py +0 -26
- egse/gimbal/symetrie/alpha.py +0 -586
- egse/gimbal/symetrie/generic_gimbal_ui.py +0 -1521
- egse/gimbal/symetrie/gimbal.py +0 -877
- egse/gimbal/symetrie/gimbal.yaml +0 -168
- egse/gimbal/symetrie/gimbal_cs.py +0 -183
- egse/gimbal/symetrie/gimbal_protocol.py +0 -138
- egse/gimbal/symetrie/gimbal_ui.py +0 -361
- egse/gimbal/symetrie/pmac.py +0 -1006
- egse/gimbal/symetrie/pmac_regex.py +0 -83
- egse/graph.py +0 -132
- egse/gui/__init__.py +0 -47
- egse/gui/buttons.py +0 -378
- egse/gui/focalplane.py +0 -1285
- egse/gui/formatter.py +0 -10
- egse/gui/led.py +0 -162
- egse/gui/limitswitch.py +0 -143
- egse/gui/mechanisms.py +0 -587
- egse/gui/states.py +0 -148
- egse/gui/stripchart.py +0 -729
- egse/gui/styles.qss +0 -48
- egse/gui/switch.py +0 -112
- egse/h5.py +0 -274
- egse/help/__init__.py +0 -0
- egse/help/help_ui.py +0 -126
- egse/hexapod/__init__.py +0 -32
- egse/hexapod/symetrie/__init__.py +0 -137
- egse/hexapod/symetrie/alpha.py +0 -874
- egse/hexapod/symetrie/dynalpha.py +0 -1387
- egse/hexapod/symetrie/hexapod_ui.py +0 -1516
- egse/hexapod/symetrie/pmac.py +0 -1010
- egse/hexapod/symetrie/pmac_regex.py +0 -83
- egse/hexapod/symetrie/puna.py +0 -1167
- egse/hexapod/symetrie/puna.yaml +0 -193
- egse/hexapod/symetrie/puna_cs.py +0 -195
- egse/hexapod/symetrie/puna_protocol.py +0 -134
- egse/hexapod/symetrie/puna_ui.py +0 -433
- egse/hexapod/symetrie/punaplus.py +0 -107
- egse/hexapod/symetrie/zonda.py +0 -872
- egse/hexapod/symetrie/zonda.yaml +0 -337
- egse/hexapod/symetrie/zonda_cs.py +0 -172
- egse/hexapod/symetrie/zonda_devif.py +0 -414
- egse/hexapod/symetrie/zonda_protocol.py +0 -123
- egse/hexapod/symetrie/zonda_ui.py +0 -449
- egse/hk.py +0 -791
- egse/icons/aeu-cs-start.svg +0 -117
- egse/icons/aeu-cs-stop.svg +0 -118
- egse/icons/aeu-cs.svg +0 -107
- egse/icons/aeu_cs-started.svg +0 -112
- egse/icons/aeu_cs-stopped.svg +0 -112
- egse/icons/aeu_cs.svg +0 -55
- egse/icons/alert.svg +0 -1
- egse/icons/arrow-double-left.png +0 -0
- egse/icons/arrow-double-right.png +0 -0
- egse/icons/arrow-up.svg +0 -11
- egse/icons/backward.svg +0 -1
- egse/icons/busy.svg +0 -1
- egse/icons/cleaning.svg +0 -115
- egse/icons/color-scheme.svg +0 -1
- egse/icons/cs-connected-alert.svg +0 -91
- egse/icons/cs-connected-disabled.svg +0 -43
- egse/icons/cs-connected.svg +0 -89
- egse/icons/cs-not-connected.svg +0 -44
- egse/icons/double-left-arrow.svg +0 -1
- egse/icons/double-right-arrow.svg +0 -1
- egse/icons/erase-disabled.svg +0 -19
- egse/icons/erase.svg +0 -59
- egse/icons/fitsgen-start.svg +0 -47
- egse/icons/fitsgen-stop.svg +0 -48
- egse/icons/fitsgen.svg +0 -1
- egse/icons/forward.svg +0 -1
- egse/icons/fov-hk-start.svg +0 -33
- egse/icons/fov-hk-stop.svg +0 -37
- egse/icons/fov-hk.svg +0 -1
- egse/icons/front-desk.svg +0 -1
- egse/icons/home-actioned.svg +0 -15
- egse/icons/home-disabled.svg +0 -15
- egse/icons/home.svg +0 -13
- egse/icons/info.svg +0 -1
- egse/icons/invalid.png +0 -0
- egse/icons/led-green.svg +0 -20
- egse/icons/led-grey.svg +0 -20
- egse/icons/led-orange.svg +0 -20
- egse/icons/led-red.svg +0 -20
- egse/icons/led-square-green.svg +0 -134
- egse/icons/led-square-grey.svg +0 -134
- egse/icons/led-square-orange.svg +0 -134
- egse/icons/led-square-red.svg +0 -134
- egse/icons/limit-switch-all-green.svg +0 -115
- egse/icons/limit-switch-all-red.svg +0 -117
- egse/icons/limit-switch-el+.svg +0 -116
- egse/icons/limit-switch-el-.svg +0 -117
- egse/icons/location-marker.svg +0 -1
- egse/icons/logo-dpu.svg +0 -48
- egse/icons/logo-gimbal.svg +0 -112
- egse/icons/logo-huber.svg +0 -23
- egse/icons/logo-ogse.svg +0 -31
- egse/icons/logo-puna.svg +0 -92
- egse/icons/logo-tcs.svg +0 -29
- egse/icons/logo-zonda.svg +0 -66
- egse/icons/maximize.svg +0 -1
- egse/icons/meter.svg +0 -1
- egse/icons/more.svg +0 -45
- egse/icons/n-fee-hk-start.svg +0 -24
- egse/icons/n-fee-hk-stop.svg +0 -25
- egse/icons/n-fee-hk.svg +0 -83
- egse/icons/observing-off.svg +0 -46
- egse/icons/observing-on.svg +0 -46
- egse/icons/open-document-hdf5.png +0 -0
- egse/icons/open-document-hdf5.svg +0 -21
- egse/icons/ops-mode.svg +0 -1
- egse/icons/play-green.svg +0 -17
- egse/icons/plugged-disabled.svg +0 -27
- egse/icons/plugged.svg +0 -21
- egse/icons/pm_ui.svg +0 -1
- egse/icons/power-button-green.svg +0 -27
- egse/icons/power-button-red.svg +0 -27
- egse/icons/power-button.svg +0 -27
- egse/icons/radar.svg +0 -1
- egse/icons/radioactive.svg +0 -2
- egse/icons/reload.svg +0 -1
- egse/icons/remote-control-off.svg +0 -28
- egse/icons/remote-control-on.svg +0 -28
- egse/icons/repeat-blue.svg +0 -15
- egse/icons/repeat.svg +0 -1
- egse/icons/settings.svg +0 -1
- egse/icons/shrink.svg +0 -1
- egse/icons/shutter.svg +0 -1
- egse/icons/sign-off.svg +0 -1
- egse/icons/sign-on.svg +0 -1
- egse/icons/sim-mode.svg +0 -1
- egse/icons/small-buttons-go.svg +0 -20
- egse/icons/small-buttons-minus.svg +0 -51
- egse/icons/small-buttons-plus.svg +0 -51
- egse/icons/sponge.svg +0 -220
- egse/icons/start-button-disabled.svg +0 -84
- egse/icons/start-button.svg +0 -50
- egse/icons/stop-button-disabled.svg +0 -84
- egse/icons/stop-button.svg +0 -50
- egse/icons/stop-red.svg +0 -17
- egse/icons/stop.svg +0 -1
- egse/icons/switch-disabled-square.svg +0 -87
- egse/icons/switch-disabled.svg +0 -15
- egse/icons/switch-off-square.svg +0 -87
- egse/icons/switch-off.svg +0 -72
- egse/icons/switch-on-square.svg +0 -87
- egse/icons/switch-on.svg +0 -61
- egse/icons/temperature-control.svg +0 -44
- egse/icons/th_ui_logo.svg +0 -1
- egse/icons/unplugged.svg +0 -23
- egse/icons/unvalid.png +0 -0
- egse/icons/user-interface.svg +0 -1
- egse/icons/vacuum.svg +0 -1
- egse/icons/valid.png +0 -0
- egse/icons/zoom-to-pixel-dark.svg +0 -64
- egse/icons/zoom-to-pixel-white.svg +0 -36
- egse/images/big-rotation-stage.png +0 -0
- egse/images/connected-100.png +0 -0
- egse/images/cross.svg +0 -6
- egse/images/disconnected-100.png +0 -0
- egse/images/gui-icon.png +0 -0
- egse/images/home.svg +0 -6
- egse/images/info-icon.png +0 -0
- egse/images/led-black.svg +0 -89
- egse/images/led-green.svg +0 -85
- egse/images/led-orange.svg +0 -85
- egse/images/led-red.svg +0 -85
- egse/images/load-icon.png +0 -0
- egse/images/load-setup.png +0 -0
- egse/images/load.png +0 -0
- egse/images/pause.png +0 -0
- egse/images/play-button.svg +0 -8
- egse/images/play.png +0 -0
- egse/images/process-status.png +0 -0
- egse/images/restart.png +0 -0
- egse/images/search.png +0 -0
- egse/images/sma.png +0 -0
- egse/images/start.png +0 -0
- egse/images/stop-button.svg +0 -8
- egse/images/stop.png +0 -0
- egse/images/switch-off.svg +0 -48
- egse/images/switch-on.svg +0 -48
- egse/images/undo.png +0 -0
- egse/images/update-button.svg +0 -11
- egse/imageviewer/exposureselection.py +0 -475
- egse/imageviewer/imageviewer.py +0 -198
- egse/imageviewer/matchfocalplane.py +0 -179
- egse/imageviewer/subfieldposition.py +0 -133
- egse/lampcontrol/__init__.py +0 -4
- egse/lampcontrol/beaglebone/beaglebone.py +0 -178
- egse/lampcontrol/beaglebone/beaglebone.yaml +0 -62
- egse/lampcontrol/beaglebone/beaglebone_cs.py +0 -106
- egse/lampcontrol/beaglebone/beaglebone_devif.py +0 -150
- egse/lampcontrol/beaglebone/beaglebone_protocol.py +0 -73
- egse/lampcontrol/energetiq/__init__.py +0 -22
- egse/lampcontrol/energetiq/eq99.yaml +0 -98
- egse/lampcontrol/energetiq/lampEQ99.py +0 -283
- egse/lampcontrol/energetiq/lampEQ99_cs.py +0 -128
- egse/lampcontrol/energetiq/lampEQ99_devif.py +0 -158
- egse/lampcontrol/energetiq/lampEQ99_encode_decode_errors.py +0 -73
- egse/lampcontrol/energetiq/lampEQ99_protocol.py +0 -71
- egse/lampcontrol/energetiq/lampEQ99_ui.py +0 -465
- egse/lib/CentOS-7/EtherSpaceLink_v34_86.dylib +0 -0
- egse/lib/CentOS-8/ESL-RMAP_v34_86.dylib +0 -0
- egse/lib/CentOS-8/EtherSpaceLink_v34_86.dylib +0 -0
- egse/lib/Debian/ESL-RMAP_v34_86.dylib +0 -0
- egse/lib/Debian/EtherSpaceLink_v34_86.dylib +0 -0
- egse/lib/Debian/libetherspacelink_v35_21.dylib +0 -0
- egse/lib/Linux/ESL-RMAP_v34_86.dylib +0 -0
- egse/lib/Linux/EtherSpaceLink_v34_86.dylib +0 -0
- egse/lib/Ubuntu-20/ESL-RMAP_v34_86.dylib +0 -0
- egse/lib/Ubuntu-20/EtherSpaceLink_v34_86.dylib +0 -0
- egse/lib/gssw/python3-gssw_2.2.3+31f63c9f-1_all.deb +0 -0
- egse/lib/ximc/__pycache__/pyximc.cpython-38 2.pyc +0 -0
- egse/lib/ximc/__pycache__/pyximc.cpython-38.pyc +0 -0
- egse/lib/ximc/libximc.framework/Frameworks/libbindy.dylib +0 -0
- egse/lib/ximc/libximc.framework/Frameworks/libxiwrapper.dylib +0 -0
- egse/lib/ximc/libximc.framework/Headers/ximc.h +0 -5510
- egse/lib/ximc/libximc.framework/Resources/Info.plist +0 -42
- egse/lib/ximc/libximc.framework/Resources/keyfile.sqlite +0 -0
- egse/lib/ximc/libximc.framework/libbindy.so +0 -0
- egse/lib/ximc/libximc.framework/libximc +0 -0
- egse/lib/ximc/libximc.framework/libximc.so +0 -0
- egse/lib/ximc/libximc.framework/libximc.so.7.0.0 +0 -0
- egse/lib/ximc/libximc.framework/libxiwrapper.so +0 -0
- egse/lib/ximc/pyximc.py +0 -922
- egse/listener.py +0 -179
- egse/logger/__init__.py +0 -243
- egse/logger/log_cs.py +0 -321
- egse/metrics.py +0 -102
- egse/mixin.py +0 -464
- egse/monitoring.py +0 -95
- egse/ni/alarms/__init__.py +0 -26
- egse/ni/alarms/cdaq9375.py +0 -300
- egse/ni/alarms/cdaq9375.yaml +0 -89
- egse/ni/alarms/cdaq9375_cs.py +0 -130
- egse/ni/alarms/cdaq9375_devif.py +0 -183
- egse/ni/alarms/cdaq9375_protocol.py +0 -48
- egse/obs_inspection.py +0 -165
- egse/observer.py +0 -41
- egse/obsid.py +0 -163
- egse/powermeter/__init__.py +0 -0
- egse/powermeter/ni/__init__.py +0 -38
- egse/powermeter/ni/cdaq9184.py +0 -224
- egse/powermeter/ni/cdaq9184.yaml +0 -73
- egse/powermeter/ni/cdaq9184_cs.py +0 -130
- egse/powermeter/ni/cdaq9184_devif.py +0 -201
- egse/powermeter/ni/cdaq9184_protocol.py +0 -48
- egse/powermeter/ni/cdaq9184_ui.py +0 -544
- egse/powermeter/thorlabs/__init__.py +0 -25
- egse/powermeter/thorlabs/pm100a.py +0 -380
- egse/powermeter/thorlabs/pm100a.yaml +0 -132
- egse/powermeter/thorlabs/pm100a_cs.py +0 -136
- egse/powermeter/thorlabs/pm100a_devif.py +0 -127
- egse/powermeter/thorlabs/pm100a_protocol.py +0 -80
- egse/powermeter/thorlabs/pm100a_ui.py +0 -725
- egse/process.py +0 -451
- egse/procman/__init__.py +0 -834
- egse/procman/cannot_start_process_popup.py +0 -43
- egse/procman/procman.yaml +0 -49
- egse/procman/procman_cs.py +0 -201
- egse/procman/procman_ui.py +0 -2081
- egse/protocol.py +0 -605
- egse/proxy.py +0 -531
- egse/randomwalk.py +0 -140
- egse/reg.py +0 -585
- egse/reload.py +0 -122
- egse/reprocess.py +0 -693
- egse/resource.py +0 -333
- egse/rmap.py +0 -406
- egse/rst.py +0 -135
- egse/search.py +0 -182
- egse/serialdevice.py +0 -190
- egse/services.py +0 -247
- egse/services.yaml +0 -68
- egse/settings.py +0 -379
- egse/settings.yaml +0 -980
- egse/setup.py +0 -1181
- egse/shutter/__init__.py +0 -0
- egse/shutter/thorlabs/__init__.py +0 -19
- egse/shutter/thorlabs/ksc101.py +0 -205
- egse/shutter/thorlabs/ksc101.yaml +0 -105
- egse/shutter/thorlabs/ksc101_cs.py +0 -136
- egse/shutter/thorlabs/ksc101_devif.py +0 -201
- egse/shutter/thorlabs/ksc101_protocol.py +0 -71
- egse/shutter/thorlabs/ksc101_ui.py +0 -548
- egse/shutter/thorlabs/sc10.py +0 -82
- egse/shutter/thorlabs/sc10.yaml +0 -52
- egse/shutter/thorlabs/sc10_controller.py +0 -81
- egse/shutter/thorlabs/sc10_cs.py +0 -108
- egse/shutter/thorlabs/sc10_interface.py +0 -25
- egse/shutter/thorlabs/sc10_simulator.py +0 -30
- egse/simulator.py +0 -41
- egse/slack.py +0 -61
- egse/socketdevice.py +0 -218
- egse/sockets.py +0 -218
- egse/spw.py +0 -1401
- egse/stages/__init__.py +0 -12
- egse/stages/aerotech/ensemble.py +0 -245
- egse/stages/aerotech/ensemble.yaml +0 -205
- egse/stages/aerotech/ensemble_controller.py +0 -275
- egse/stages/aerotech/ensemble_cs.py +0 -110
- egse/stages/aerotech/ensemble_interface.py +0 -132
- egse/stages/aerotech/ensemble_parameters.py +0 -433
- egse/stages/aerotech/ensemble_simulator.py +0 -27
- egse/stages/aerotech/mgse_sim.py +0 -188
- egse/stages/arun/smd3.py +0 -110
- egse/stages/arun/smd3.yaml +0 -68
- egse/stages/arun/smd3_controller.py +0 -470
- egse/stages/arun/smd3_cs.py +0 -112
- egse/stages/arun/smd3_interface.py +0 -53
- egse/stages/arun/smd3_simulator.py +0 -27
- egse/stages/arun/smd3_stop.py +0 -16
- egse/stages/huber/__init__.py +0 -49
- egse/stages/huber/smc9300.py +0 -920
- egse/stages/huber/smc9300.yaml +0 -63
- egse/stages/huber/smc9300_cs.py +0 -178
- egse/stages/huber/smc9300_devif.py +0 -345
- egse/stages/huber/smc9300_protocol.py +0 -113
- egse/stages/huber/smc9300_sim.py +0 -547
- egse/stages/huber/smc9300_ui.py +0 -973
- egse/state.py +0 -173
- egse/statemachine.py +0 -274
- egse/storage/__init__.py +0 -1067
- egse/storage/persistence.py +0 -2295
- egse/storage/storage.yaml +0 -79
- egse/storage/storage_cs.py +0 -231
- egse/styles/dark.qss +0 -343
- egse/styles/default.qss +0 -48
- egse/synoptics/__init__.py +0 -417
- egse/synoptics/syn.yaml +0 -9
- egse/synoptics/syn_cs.py +0 -195
- egse/system.py +0 -1611
- egse/tcs/__init__.py +0 -14
- egse/tcs/tcs.py +0 -879
- egse/tcs/tcs.yaml +0 -14
- egse/tcs/tcs_cs.py +0 -202
- egse/tcs/tcs_devif.py +0 -292
- egse/tcs/tcs_protocol.py +0 -180
- egse/tcs/tcs_sim.py +0 -177
- egse/tcs/tcs_ui.py +0 -543
- egse/tdms.py +0 -171
- egse/tempcontrol/__init__.py +0 -23
- egse/tempcontrol/agilent/agilent34970.py +0 -109
- egse/tempcontrol/agilent/agilent34970.yaml +0 -44
- egse/tempcontrol/agilent/agilent34970_cs.py +0 -114
- egse/tempcontrol/agilent/agilent34970_devif.py +0 -182
- egse/tempcontrol/agilent/agilent34970_protocol.py +0 -96
- egse/tempcontrol/agilent/agilent34972.py +0 -111
- egse/tempcontrol/agilent/agilent34972.yaml +0 -44
- egse/tempcontrol/agilent/agilent34972_cs.py +0 -115
- egse/tempcontrol/agilent/agilent34972_devif.py +0 -189
- egse/tempcontrol/agilent/agilent34972_protocol.py +0 -98
- egse/tempcontrol/beaglebone/beaglebone.py +0 -341
- egse/tempcontrol/beaglebone/beaglebone.yaml +0 -110
- egse/tempcontrol/beaglebone/beaglebone_cs.py +0 -117
- egse/tempcontrol/beaglebone/beaglebone_protocol.py +0 -134
- egse/tempcontrol/beaglebone/beaglebone_ui.py +0 -674
- egse/tempcontrol/digalox/digalox.py +0 -115
- egse/tempcontrol/digalox/digalox.yaml +0 -36
- egse/tempcontrol/digalox/digalox_cs.py +0 -108
- egse/tempcontrol/digalox/digalox_protocol.py +0 -56
- egse/tempcontrol/keithley/__init__.py +0 -33
- egse/tempcontrol/keithley/daq6510.py +0 -662
- egse/tempcontrol/keithley/daq6510.yaml +0 -105
- egse/tempcontrol/keithley/daq6510_cs.py +0 -163
- egse/tempcontrol/keithley/daq6510_devif.py +0 -343
- egse/tempcontrol/keithley/daq6510_protocol.py +0 -79
- egse/tempcontrol/keithley/daq6510_sim.py +0 -186
- egse/tempcontrol/lakeshore/__init__.py +0 -33
- egse/tempcontrol/lakeshore/lsci.py +0 -361
- egse/tempcontrol/lakeshore/lsci.yaml +0 -162
- egse/tempcontrol/lakeshore/lsci_cs.py +0 -174
- egse/tempcontrol/lakeshore/lsci_devif.py +0 -292
- egse/tempcontrol/lakeshore/lsci_protocol.py +0 -76
- egse/tempcontrol/lakeshore/lsci_ui.py +0 -387
- egse/tempcontrol/ni/__init__.py +0 -0
- egse/tempcontrol/spid/spid.py +0 -109
- egse/tempcontrol/spid/spid.yaml +0 -81
- egse/tempcontrol/spid/spid_controller.py +0 -279
- egse/tempcontrol/spid/spid_cs.py +0 -136
- egse/tempcontrol/spid/spid_protocol.py +0 -107
- egse/tempcontrol/spid/spid_ui.py +0 -723
- egse/tempcontrol/srs/__init__.py +0 -22
- egse/tempcontrol/srs/ptc10.py +0 -867
- egse/tempcontrol/srs/ptc10.yaml +0 -227
- egse/tempcontrol/srs/ptc10_cs.py +0 -128
- egse/tempcontrol/srs/ptc10_devif.py +0 -116
- egse/tempcontrol/srs/ptc10_protocol.py +0 -39
- egse/tempcontrol/srs/ptc10_ui.py +0 -906
- egse/ups/apc/apc.py +0 -236
- egse/ups/apc/apc.yaml +0 -45
- egse/ups/apc/apc_cs.py +0 -101
- egse/ups/apc/apc_protocol.py +0 -125
- egse/user.yaml +0 -7
- egse/vacuum/beaglebone/beaglebone.py +0 -149
- egse/vacuum/beaglebone/beaglebone.yaml +0 -44
- egse/vacuum/beaglebone/beaglebone_cs.py +0 -108
- egse/vacuum/beaglebone/beaglebone_devif.py +0 -159
- egse/vacuum/beaglebone/beaglebone_protocol.py +0 -192
- egse/vacuum/beaglebone/beaglebone_ui.py +0 -638
- egse/vacuum/instrutech/igm402.py +0 -91
- egse/vacuum/instrutech/igm402.yaml +0 -90
- egse/vacuum/instrutech/igm402_controller.py +0 -124
- egse/vacuum/instrutech/igm402_cs.py +0 -108
- egse/vacuum/instrutech/igm402_interface.py +0 -49
- egse/vacuum/instrutech/igm402_simulator.py +0 -36
- egse/vacuum/keller/kellerBus.py +0 -256
- egse/vacuum/keller/leo3.py +0 -100
- egse/vacuum/keller/leo3.yaml +0 -38
- egse/vacuum/keller/leo3_controller.py +0 -81
- egse/vacuum/keller/leo3_cs.py +0 -101
- egse/vacuum/keller/leo3_interface.py +0 -33
- egse/vacuum/mks/evision.py +0 -86
- egse/vacuum/mks/evision.yaml +0 -75
- egse/vacuum/mks/evision_cs.py +0 -101
- egse/vacuum/mks/evision_devif.py +0 -313
- egse/vacuum/mks/evision_interface.py +0 -60
- egse/vacuum/mks/evision_simulator.py +0 -24
- egse/vacuum/mks/evision_ui.py +0 -701
- egse/vacuum/pfeiffer/acp40.py +0 -87
- egse/vacuum/pfeiffer/acp40.yaml +0 -60
- egse/vacuum/pfeiffer/acp40_controller.py +0 -117
- egse/vacuum/pfeiffer/acp40_cs.py +0 -109
- egse/vacuum/pfeiffer/acp40_interface.py +0 -40
- egse/vacuum/pfeiffer/acp40_simulator.py +0 -37
- egse/vacuum/pfeiffer/tc400.py +0 -87
- egse/vacuum/pfeiffer/tc400.yaml +0 -83
- egse/vacuum/pfeiffer/tc400_controller.py +0 -136
- egse/vacuum/pfeiffer/tc400_cs.py +0 -109
- egse/vacuum/pfeiffer/tc400_interface.py +0 -70
- egse/vacuum/pfeiffer/tc400_simulator.py +0 -35
- egse/vacuum/pfeiffer/tpg261.py +0 -80
- egse/vacuum/pfeiffer/tpg261.yaml +0 -66
- egse/vacuum/pfeiffer/tpg261_controller.py +0 -150
- egse/vacuum/pfeiffer/tpg261_cs.py +0 -109
- egse/vacuum/pfeiffer/tpg261_interface.py +0 -59
- egse/vacuum/pfeiffer/tpg261_simulator.py +0 -23
- egse/version.py +0 -174
- egse/visitedpositions.py +0 -398
- egse/windowing.py +0 -213
- egse/zmq/__init__.py +0 -28
- egse/zmq/spw.py +0 -160
- egse/zmq_ser.py +0 -41
- scripts/alerts/cold.yaml +0 -278
- scripts/alerts/example_alerts.yaml +0 -54
- scripts/alerts/transition.yaml +0 -14
- scripts/alerts/warm.yaml +0 -49
- scripts/analyse_n_fee_hk_data.py +0 -52
- scripts/check_hdf5_files.py +0 -192
- scripts/check_register_sync.py +0 -47
- scripts/check_tcs_calib_coef.py +0 -90
- scripts/correct_ccd_cold_temperature_cal.py +0 -157
- scripts/create_hdf5_report.py +0 -293
- scripts/csl_model.py +0 -420
- scripts/csl_restore_setup.py +0 -229
- scripts/export-grafana-dashboards.py +0 -49
- scripts/fdir/cs_recovery/fdir_cs_recovery.py +0 -54
- scripts/fdir/fdir_table.yaml +0 -70
- scripts/fdir/fdir_test_recovery.py +0 -10
- scripts/fdir/hw_recovery/fdir_agilent_hw_recovery.py +0 -73
- scripts/fdir/limit_recovery/fdir_agilent_limit.py +0 -61
- scripts/fdir/limit_recovery/fdir_bb_heater_limit.py +0 -59
- scripts/fdir/limit_recovery/fdir_ensemble_limit.py +0 -33
- scripts/fdir/limit_recovery/fdir_pressure_limit_recovery.py +0 -71
- scripts/fix_csv.py +0 -80
- scripts/ias/correct_ccd_temp_cal_elfique.py +0 -43
- scripts/ias/correct_ccd_temp_cal_floreffe.py +0 -43
- scripts/ias/correct_trp_swap_achel.py +0 -199
- scripts/inta/correct_ccd_temp_cal_duvel.py +0 -43
- scripts/inta/correct_ccd_temp_cal_gueuze.py +0 -43
- scripts/n_fee_supply_voltage_calculation.py +0 -92
- scripts/playground.py +0 -30
- scripts/print_hdf5_hk_data.py +0 -68
- scripts/print_register_map.py +0 -43
- scripts/remove_lines_between_matches.py +0 -188
- scripts/sron/commanding/control_heaters.py +0 -44
- scripts/sron/commanding/pumpdown.py +0 -46
- scripts/sron/commanding/set_pid_setpoint.py +0 -19
- scripts/sron/commanding/shutdown_bbb_heaters.py +0 -10
- scripts/sron/commanding/shutdown_pumps.py +0 -33
- scripts/sron/correct_mgse_coordinates_brigand_chimay.py +0 -272
- scripts/sron/correct_trp_swap_brigand.py +0 -204
- scripts/sron/gimbal_conversions.py +0 -75
- scripts/sron/tm_gen/tm_gen_agilent.py +0 -37
- scripts/sron/tm_gen/tm_gen_heaters.py +0 -4
- scripts/sron/tm_gen/tm_gen_spid.py +0 -13
- scripts/update_operational_cgse.py +0 -268
- scripts/update_operational_cgse_old.py +0 -273
egse/storage/__init__.py
DELETED
|
@@ -1,1067 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module provides storage functionality for the Common-EGSE.
|
|
3
|
-
|
|
4
|
-

|
|
5
|
-
|
|
6
|
-
### Introduction
|
|
7
|
-
|
|
8
|
-
All control servers and other components that need to save data through the storage manager
|
|
9
|
-
need to register to the storage manager first. That can be done by creating a `StorageProxy`
|
|
10
|
-
and sending a register message for the component. The name that is registered is also used by
|
|
11
|
-
all `save` messages to identify the component and dispatch the correct persistence function.
|
|
12
|
-
|
|
13
|
-
All control servers (that inherit from the ControlServer class) will automatically register to
|
|
14
|
-
the Storage Manager when the control server (CS) starts. When the CS quits normally (by the
|
|
15
|
-
`quit_server()` command of the Service proxy) the control server will automatically be
|
|
16
|
-
unregistered from the Storage manager.
|
|
17
|
-
|
|
18
|
-
By default, when the Storage is active, it will save all the information that it gets for those
|
|
19
|
-
components that have registered to the Storage manager.
|
|
20
|
-
|
|
21
|
-
```python
|
|
22
|
-
storage_proxy = StorageProxy()
|
|
23
|
-
...
|
|
24
|
-
storage_proxy.register({'origin': reg_name,
|
|
25
|
-
'persistence_class': CSV,
|
|
26
|
-
'prep': {'column_names': [], 'mode': 'w'}})
|
|
27
|
-
...
|
|
28
|
-
|
|
29
|
-
storage_proxy.save({'origin': reg_name, 'data': data})
|
|
30
|
-
...
|
|
31
|
-
storage_proxy.unregister({'origin': reg_name})
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### Commands
|
|
35
|
-
|
|
36
|
-
The Storage component shall understand the following commands:
|
|
37
|
-
|
|
38
|
-
* registration of a component
|
|
39
|
-
|
|
40
|
-
* `storage_proxy.register({'origin': str, 'persistence_class'; <class>, 'prep': dict})`
|
|
41
|
-
* `storage_proxy.unregister({'origin': str})`
|
|
42
|
-
|
|
43
|
-
* accept the following commands from the configuration manager
|
|
44
|
-
|
|
45
|
-
* `storage_proxy.start_observation(obsid: str)`
|
|
46
|
-
* `storage_proxy.end_observation(obsid: str)`
|
|
47
|
-
|
|
48
|
-
* accept housekeeping and data packets from the DPU simulator housekeeping and accept
|
|
49
|
-
housekeeping and status data from any control server
|
|
50
|
-
|
|
51
|
-
* `storage_proxy.save({'origin': name, 'data': data})`
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
When an observation is started, the Storage Manager will 'fork' the data stream and save the
|
|
55
|
-
control server information in a separate set of files. The filenames will carry the `obsid`
|
|
56
|
-
(see below).
|
|
57
|
-
|
|
58
|
-
### What data is saved?
|
|
59
|
-
|
|
60
|
-
* Housekeeping data from other control servers, e.g. device CS or the configuration manager
|
|
61
|
-
* Status data from other control servers
|
|
62
|
-
* SpaceWire packets from the N-FEE and the F-FEE, this includes housekeeping and CCD data
|
|
63
|
-
* Image data from the camera, i.e. processed Spacewire data packets
|
|
64
|
-
|
|
65
|
-
### How is data saved?
|
|
66
|
-
|
|
67
|
-
* Different data types are stored in specific formats according to the PersistenceLayer
|
|
68
|
-
that was chosen
|
|
69
|
-
* The following file formats are supported:
|
|
70
|
-
* CSV - for tabular data, typically housekeeping and status information
|
|
71
|
-
* TXT - for logging information
|
|
72
|
-
* FITS - for image data
|
|
73
|
-
* HDF5 - for SpaceWire data and housekeeping packets
|
|
74
|
-
|
|
75
|
-
### How are the files named?
|
|
76
|
-
|
|
77
|
-
We have two sets of files:
|
|
78
|
-
|
|
79
|
-
1. files that contain only the data that was collected for an observation, i.e.
|
|
80
|
-
between the calls to `start_observation` and `end_observation`. These files are located
|
|
81
|
-
in the `obs` sub-folder of the main data store location. The filename is constructed from
|
|
82
|
-
the test id, the site id and the setup id, followed by the data source identifier and a
|
|
83
|
-
timestamp. An example from the PUNA Hexapod housekeeping file:
|
|
84
|
-
`00031_CSL_00008_PUNA_20200701_210711.csv`.
|
|
85
|
-
|
|
86
|
-
2. files that contain all the housekeeping for each of the data sources regardless of an
|
|
87
|
-
observation is running or not. All data that is collected during a test day will be stored in
|
|
88
|
-
these files. Outside of an observation context there will be no CCD image data collected.
|
|
89
|
-
These files are located in the `daily` sub-folder of the main data store location. The filename
|
|
90
|
-
is constructed from the date, the site id and the data source identifier. An example for the
|
|
91
|
-
same PUNA Hexapod file: `20200701_CSL_PUNA.csv`.
|
|
92
|
-
|
|
93
|
-
The timestamp that is used is the time of file creation.
|
|
94
|
-
|
|
95
|
-
"""
|
|
96
|
-
from __future__ import annotations
|
|
97
|
-
|
|
98
|
-
import abc
|
|
99
|
-
import datetime
|
|
100
|
-
import logging
|
|
101
|
-
import os
|
|
102
|
-
import shutil
|
|
103
|
-
from functools import partial
|
|
104
|
-
from pathlib import Path
|
|
105
|
-
from pathlib import PurePath
|
|
106
|
-
from typing import Dict
|
|
107
|
-
from typing import List
|
|
108
|
-
from typing import Tuple
|
|
109
|
-
from typing import Union
|
|
110
|
-
|
|
111
|
-
from egse.command import ClientServerCommand
|
|
112
|
-
from egse.config import find_files
|
|
113
|
-
from egse.control import ControlServer
|
|
114
|
-
from egse.control import Failure
|
|
115
|
-
from egse.control import Response
|
|
116
|
-
from egse.control import Success
|
|
117
|
-
from egse.control import is_control_server_active
|
|
118
|
-
from egse.decorators import dynamic_interface
|
|
119
|
-
from egse.env import get_data_storage_location
|
|
120
|
-
from egse.exceptions import Error
|
|
121
|
-
from egse.listener import Event
|
|
122
|
-
from egse.listener import EventInterface
|
|
123
|
-
from egse.obsid import ObservationIdentifier
|
|
124
|
-
from egse.obsid import TEST_LAB
|
|
125
|
-
from egse.protocol import CommandProtocol
|
|
126
|
-
from egse.proxy import Proxy
|
|
127
|
-
from egse.settings import Settings
|
|
128
|
-
from egse.setup import Setup
|
|
129
|
-
from egse.setup import get_setup
|
|
130
|
-
from egse.storage.persistence import HDF5
|
|
131
|
-
from egse.storage.persistence import PersistenceLayer
|
|
132
|
-
from egse.system import format_datetime
|
|
133
|
-
from egse.zmq_ser import bind_address
|
|
134
|
-
from egse.zmq_ser import connect_address
|
|
135
|
-
|
|
136
|
-
logger = logging.getLogger(__name__)
|
|
137
|
-
|
|
138
|
-
CTRL_SETTINGS = Settings.load("Storage Control Server")
|
|
139
|
-
SITE = Settings.load("SITE")
|
|
140
|
-
COMMAND_SETTINGS = Settings.load(filename="storage.yaml")
|
|
141
|
-
DEVICE_SETTINGS = Settings.load(filename="storage.yaml")
|
|
142
|
-
CCD_SETTINGS = Settings.load("CCD")
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def is_storage_manager_active(timeout: float = 0.5):
|
|
146
|
-
"""Check if the Storage Manager is running.
|
|
147
|
-
|
|
148
|
-
Returns:
|
|
149
|
-
True if the Storage Manager is running and replied with the expected answer.
|
|
150
|
-
"""
|
|
151
|
-
|
|
152
|
-
endpoint = connect_address(
|
|
153
|
-
CTRL_SETTINGS.PROTOCOL, CTRL_SETTINGS.HOSTNAME, CTRL_SETTINGS.COMMANDING_PORT
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
return is_control_server_active(endpoint, timeout)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def register_to_storage_manager(origin: str, persistence_class: PersistenceLayer, prep: Dict):
|
|
160
|
-
"""
|
|
161
|
-
Register the component to the Storage manager.
|
|
162
|
-
|
|
163
|
-
For information on what should go into the `prep` keyword argument, please check the proper
|
|
164
|
-
persistence class.
|
|
165
|
-
|
|
166
|
-
Args:
|
|
167
|
-
origin (str): the name of the component, by which it will be registered
|
|
168
|
-
persistence_class: a concrete class that will be used to store the data
|
|
169
|
-
prep (dict): preparation meta data for the persistence class
|
|
170
|
-
"""
|
|
171
|
-
|
|
172
|
-
try:
|
|
173
|
-
with StorageProxy() as proxy:
|
|
174
|
-
rc = proxy.register(
|
|
175
|
-
{
|
|
176
|
-
"origin": origin,
|
|
177
|
-
"persistence_class": persistence_class,
|
|
178
|
-
"prep": prep,
|
|
179
|
-
}
|
|
180
|
-
)
|
|
181
|
-
if not rc.successful:
|
|
182
|
-
logger.warning(f"Couldn't register to the Storage manager: {rc}")
|
|
183
|
-
else:
|
|
184
|
-
logger.info(rc)
|
|
185
|
-
except ConnectionError as exc:
|
|
186
|
-
logger.warning(f"Couldn't connect to the Storage manager for registration: {exc}")
|
|
187
|
-
raise
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
def unregister_from_storage_manager(origin: str):
|
|
191
|
-
"""Unregister the component from the Storage manager."""
|
|
192
|
-
|
|
193
|
-
try:
|
|
194
|
-
with StorageProxy() as proxy:
|
|
195
|
-
rc = proxy.unregister({"origin": origin})
|
|
196
|
-
if not rc.successful:
|
|
197
|
-
logger.warning(f"Couldn't unregister from the Storage manager: {rc}")
|
|
198
|
-
else:
|
|
199
|
-
logger.info(rc)
|
|
200
|
-
except ConnectionError as exc:
|
|
201
|
-
logger.warning(f"Couldn't connect to the Storage manager for de-registration: {exc}")
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def cycle_daily_files():
|
|
205
|
-
"""
|
|
206
|
-
Create a new daily file for each registered item when no such file exists.
|
|
207
|
-
"""
|
|
208
|
-
with StorageProxy() as storage:
|
|
209
|
-
storage.cycle_daily_files()
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
class AlreadyRegisteredError(Error):
|
|
213
|
-
"""This error indicates that an item is already registered and cannot be registered again."""
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
class Registry:
|
|
217
|
-
"""
|
|
218
|
-
A registry for registration of components in the system that need to save data through
|
|
219
|
-
the Storage Manager.
|
|
220
|
-
"""
|
|
221
|
-
|
|
222
|
-
def __init__(self):
|
|
223
|
-
self._register = dict()
|
|
224
|
-
|
|
225
|
-
def __contains__(self, name: str):
|
|
226
|
-
"""Returns True if an item with 'name' has been registered."""
|
|
227
|
-
if isinstance(name, str):
|
|
228
|
-
return name in self._register.keys()
|
|
229
|
-
|
|
230
|
-
raise ValueError(
|
|
231
|
-
f"You can only check if something is contained in the Registry "
|
|
232
|
-
f"by a key of type string, item is of type '{type(name)}'."
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
def __len__(self):
|
|
236
|
-
"""Returns the number of registrations."""
|
|
237
|
-
return len(self._register)
|
|
238
|
-
|
|
239
|
-
def __iter__(self):
|
|
240
|
-
return iter(self._register.keys())
|
|
241
|
-
|
|
242
|
-
def get(self, name: str):
|
|
243
|
-
"""Returns the registered item for the given name (identifier)."""
|
|
244
|
-
return self._register.get(name)
|
|
245
|
-
|
|
246
|
-
def register(self, name: str, item):
|
|
247
|
-
"""Register the item by the given name in the register.
|
|
248
|
-
|
|
249
|
-
Args:
|
|
250
|
-
name (str): the key to identify this registration, usually the name of
|
|
251
|
-
the control server or the class that registers
|
|
252
|
-
item: an object that contains information about what information needs to be saved
|
|
253
|
-
and how
|
|
254
|
-
"""
|
|
255
|
-
if not isinstance(name, str):
|
|
256
|
-
raise ValueError("The name of the item to register must be a string.")
|
|
257
|
-
if name in self:
|
|
258
|
-
raise AlreadyRegisteredError(
|
|
259
|
-
f"An item with name '{name}' is already registered, please unregister first."
|
|
260
|
-
)
|
|
261
|
-
self._register[name] = item
|
|
262
|
-
|
|
263
|
-
def unregister(self, name: str):
|
|
264
|
-
"""Unregister the item with the given name from the register.
|
|
265
|
-
|
|
266
|
-
Args:
|
|
267
|
-
name (str): the key by which the registration was done.
|
|
268
|
-
"""
|
|
269
|
-
if not isinstance(name, str):
|
|
270
|
-
raise ValueError("The name of the item to unregister must be a string.")
|
|
271
|
-
if name not in self:
|
|
272
|
-
raise KeyError(f"There is no item with name '{name}' in this Register.")
|
|
273
|
-
del self._register[name]
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
class StoragePacket(metaclass=abc.ABCMeta):
|
|
277
|
-
"""Base packet for all data send to Storage."""
|
|
278
|
-
|
|
279
|
-
def __init__(self, origin=None, data=None, metadata=None):
|
|
280
|
-
self._timestamp = datetime.datetime.now(tz=datetime.timezone.utc)
|
|
281
|
-
self._origin = origin
|
|
282
|
-
self._data = data
|
|
283
|
-
self._metadata = metadata
|
|
284
|
-
|
|
285
|
-
@property
|
|
286
|
-
def timestamp(self):
|
|
287
|
-
return self._timestamp
|
|
288
|
-
|
|
289
|
-
@property
|
|
290
|
-
def origin(self):
|
|
291
|
-
return self._origin
|
|
292
|
-
|
|
293
|
-
@property
|
|
294
|
-
def data(self):
|
|
295
|
-
return self._data
|
|
296
|
-
|
|
297
|
-
@property
|
|
298
|
-
def metadata(self):
|
|
299
|
-
return self._metadata
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
class StorageInterface:
|
|
303
|
-
"""
|
|
304
|
-
This interface is for control servers to register to the Storage Manager and for the
|
|
305
|
-
configuration manager to start and stop an observation/test.
|
|
306
|
-
|
|
307
|
-
The interface should be implemented by the StorageController and the StorageProxy (and
|
|
308
|
-
possibly a StorageSimulator should we need that).
|
|
309
|
-
"""
|
|
310
|
-
|
|
311
|
-
@dynamic_interface
|
|
312
|
-
def save(self, item: dict) -> Response:
|
|
313
|
-
"""Saves the data part from the item.
|
|
314
|
-
Args:
|
|
315
|
-
item (dict): a dictionary that identifies the origin and the data to be stored.
|
|
316
|
-
"""
|
|
317
|
-
raise NotImplementedError
|
|
318
|
-
|
|
319
|
-
@dynamic_interface
|
|
320
|
-
def read(self, item: dict) -> Response:
|
|
321
|
-
"""Reads data from storage defined by the `origin` in item.
|
|
322
|
-
Args:
|
|
323
|
-
item (dict): a dictionary that identifies the origin and an optional filter.
|
|
324
|
-
"""
|
|
325
|
-
raise NotImplementedError
|
|
326
|
-
|
|
327
|
-
@dynamic_interface
|
|
328
|
-
def register(self, item: dict, use_counter: bool = False) -> Response:
|
|
329
|
-
"""Registers the item to the storage manager.
|
|
330
|
-
|
|
331
|
-
The item shall have the following keys:
|
|
332
|
-
|
|
333
|
-
* origin (str): the name of the item, by which it will be registered
|
|
334
|
-
* persistence_class (class):
|
|
335
|
-
|
|
336
|
-
Args:
|
|
337
|
-
item (dict): a dictionary that identifies the component to be registered
|
|
338
|
-
|
|
339
|
-
Returns:
|
|
340
|
-
Success: when the item could be registered successfully
|
|
341
|
-
Failure: when the registration fails, `Failure.cause` provides cause exception
|
|
342
|
-
"""
|
|
343
|
-
raise NotImplementedError
|
|
344
|
-
|
|
345
|
-
@dynamic_interface
|
|
346
|
-
def unregister(self, item: dict) -> Response:
|
|
347
|
-
"""Unregisters the item from the storage manager.
|
|
348
|
-
|
|
349
|
-
Args:
|
|
350
|
-
item (dict): a dictionary that identifies the component to be registered
|
|
351
|
-
|
|
352
|
-
Returns:
|
|
353
|
-
Success: when the item could be unregistered successfully
|
|
354
|
-
Failure: when the de-registration fails, `Failure.cause` provides cause exception
|
|
355
|
-
"""
|
|
356
|
-
raise NotImplementedError
|
|
357
|
-
|
|
358
|
-
@dynamic_interface
|
|
359
|
-
def get_registry_names(self):
|
|
360
|
-
"""Returns the names of the registered components.
|
|
361
|
-
|
|
362
|
-
Returns:
|
|
363
|
-
a list of names/identifiers for the registered components.
|
|
364
|
-
"""
|
|
365
|
-
raise NotImplementedError
|
|
366
|
-
|
|
367
|
-
@dynamic_interface
|
|
368
|
-
def start_observation(self, obsid: ObservationIdentifier, camera_name: str = None) -> Response:
|
|
369
|
-
""" "Start an observation for the given obsid.
|
|
370
|
-
|
|
371
|
-
When a new obsevation is started the following actions will be taken:
|
|
372
|
-
|
|
373
|
-
* Housekeeping and telemetry from registered components (mainly control servers)
|
|
374
|
-
will be forked into a newly created file for that component.
|
|
375
|
-
* Camera data will be saved as follows:
|
|
376
|
-
* SpaceWire packets will go into an HDF5 file for this observation
|
|
377
|
-
* CCD data will be assembled into image data and saved in FITS format
|
|
378
|
-
|
|
379
|
-
Args:
|
|
380
|
-
camera_name: the name of the camera or None
|
|
381
|
-
obsid: a unique observation identifier
|
|
382
|
-
|
|
383
|
-
Returns:
|
|
384
|
-
Success: when a new observation with the given obsid could be started properly.
|
|
385
|
-
Failure: when the previous observation was not finished yet (no end_observation was
|
|
386
|
-
send), or when a failure happened during the preparations for a new observation.
|
|
387
|
-
"""
|
|
388
|
-
raise NotImplementedError
|
|
389
|
-
|
|
390
|
-
@dynamic_interface
|
|
391
|
-
def end_observation(self, obsid: ObservationIdentifier) -> Response:
|
|
392
|
-
"""Ends the currently running observation.
|
|
393
|
-
|
|
394
|
-
When a running observation is ended, the following actions will be taken:
|
|
395
|
-
|
|
396
|
-
* All files of registered components will be closed.
|
|
397
|
-
* Housekeeping and telemetry will continue to be saved to the global repository
|
|
398
|
-
|
|
399
|
-
Args:
|
|
400
|
-
obsid: the observation identifier of the currently running observation
|
|
401
|
-
|
|
402
|
-
Returns:
|
|
403
|
-
Success: when the current observation could be ended successfully.
|
|
404
|
-
Failure: when the given obsid doesn't match the current observation.
|
|
405
|
-
|
|
406
|
-
"""
|
|
407
|
-
raise NotImplementedError
|
|
408
|
-
|
|
409
|
-
@dynamic_interface
|
|
410
|
-
def get_obsid(self):
|
|
411
|
-
"""Return the observation identifier."""
|
|
412
|
-
pass
|
|
413
|
-
|
|
414
|
-
@dynamic_interface
|
|
415
|
-
def cycle_daily_files(self):
|
|
416
|
-
pass
|
|
417
|
-
|
|
418
|
-
@dynamic_interface
|
|
419
|
-
def get_storage_location(self):
|
|
420
|
-
pass
|
|
421
|
-
|
|
422
|
-
@dynamic_interface
|
|
423
|
-
def get_filenames(self, item: dict) -> List[Path]:
|
|
424
|
-
"""Return the filename(s) associated with this registered item."""
|
|
425
|
-
pass
|
|
426
|
-
|
|
427
|
-
@dynamic_interface
|
|
428
|
-
def new_registration(self, item: dict, use_counter=False):
|
|
429
|
-
"""
|
|
430
|
-
Create a new data file for the given item. If the item was previously registered, close that
|
|
431
|
-
registration and open a new registration. The use_counter parameter determines if an
|
|
432
|
-
incremented counter is used to construct a unique filename.
|
|
433
|
-
|
|
434
|
-
Args:
|
|
435
|
-
- item: Dictionary that identifies the component to be registered.
|
|
436
|
-
- use_counter: Indicates whether or not a counter should be used in the filename.
|
|
437
|
-
"""
|
|
438
|
-
|
|
439
|
-
pass
|
|
440
|
-
|
|
441
|
-
@dynamic_interface
|
|
442
|
-
def get_disk_usage(self):
|
|
443
|
-
""" Return the total, used, and free disk space [bytes].
|
|
444
|
-
|
|
445
|
-
Returns:
|
|
446
|
-
- Total disk space [bytes].
|
|
447
|
-
- Used disk space [bytes].
|
|
448
|
-
- Free disk space [bytes].
|
|
449
|
-
"""
|
|
450
|
-
|
|
451
|
-
pass
|
|
452
|
-
|
|
453
|
-
@dynamic_interface
|
|
454
|
-
def get_loaded_setup_id(self) -> str:
|
|
455
|
-
"""
|
|
456
|
-
Returns the ID of the currently loaded Setup.
|
|
457
|
-
|
|
458
|
-
Note:
|
|
459
|
-
This is the Setup active on this control server. This command is mainly used to check that the Setup
|
|
460
|
-
loaded in this control server corresponds to the Setup loaded in the configuration manager.
|
|
461
|
-
|
|
462
|
-
Returns:
|
|
463
|
-
The ID of the Setup loaded in this control server.
|
|
464
|
-
"""
|
|
465
|
-
|
|
466
|
-
pass
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
def _disentangle_filename(filename: Union[str, Path]) -> Tuple:
|
|
470
|
-
"""Disentangle the given filename and return the test identifier, the site id and the Setup id.
|
|
471
|
-
|
|
472
|
-
It is assumed in this function that the filename is from a test observation and contains the
|
|
473
|
-
correct fields to be extracted. Only very limited checking is done if that is indeed the case.
|
|
474
|
-
|
|
475
|
-
Args:
|
|
476
|
-
filename (str, Path): the filename of a test observation
|
|
477
|
-
|
|
478
|
-
Returns:
|
|
479
|
-
A tuple containing the test_id (int), site_id (str) and setup_id (int). If the filename is
|
|
480
|
-
not recognized as a valid filename, the returned tuple contains all None.
|
|
481
|
-
|
|
482
|
-
"""
|
|
483
|
-
filename = Path(filename).resolve()
|
|
484
|
-
parts = filename.parts
|
|
485
|
-
|
|
486
|
-
if parts[-2] != 'obs':
|
|
487
|
-
name = parts[-1]
|
|
488
|
-
if not (name.rsplit('_', 3)[0].endswith('_SPW') or name.rsplit('_', 2)[0].endswith('_SPW')):
|
|
489
|
-
return None, None, None
|
|
490
|
-
|
|
491
|
-
name = parts[-1]
|
|
492
|
-
test_id, site_id, setup_id = name.split('_')[:3]
|
|
493
|
-
return int(test_id), site_id, int(setup_id)
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
def _construct_filename(
|
|
497
|
-
identifier: str, ext: str, obsid: ObservationIdentifier = None, use_counter=False,
|
|
498
|
-
location: str = None, site_id: str = None, camera_name: str = None
|
|
499
|
-
) -> PurePath:
|
|
500
|
-
"""Construct a filename for the data source.
|
|
501
|
-
|
|
502
|
-
We construct two types of filenames:
|
|
503
|
-
|
|
504
|
-
1. the observational files which store all the data that are collected during an observation.
|
|
505
|
-
There is one file per data source. The files are located in the `obs` sub-folder of the
|
|
506
|
-
storage location.
|
|
507
|
-
2. the daily files which store all the data from a data source regardless the state of an
|
|
508
|
-
observation. There is one file per data source. The files are located in the `daily`
|
|
509
|
-
sub-folder of the storage location.
|
|
510
|
-
|
|
511
|
-
Args:
|
|
512
|
-
identifier (str): an identifier for the source of the data, this string is usually what
|
|
513
|
-
is sent in the `origin` of the item dictionary.
|
|
514
|
-
ext (str): the extension of the file, this depends oon the persistence class that is
|
|
515
|
-
used for storing the data.
|
|
516
|
-
obsid (ObservationIdentifier): a unique identifier for the observation
|
|
517
|
-
use_counter: Indicates whether or not a counter should be included in the filename.
|
|
518
|
-
Returns:
|
|
519
|
-
The full path to the file as a `PurePath`.
|
|
520
|
-
"""
|
|
521
|
-
|
|
522
|
-
site_id = site_id or SITE.ID
|
|
523
|
-
location = location or get_data_storage_location(site_id=site_id)
|
|
524
|
-
|
|
525
|
-
if obsid:
|
|
526
|
-
|
|
527
|
-
timestamp = datetime.datetime.now(tz=datetime.timezone.utc).strftime("%Y%m%d_%H%M%S")
|
|
528
|
-
|
|
529
|
-
prefix = obsid.create_id(order=TEST_LAB, camera_name=camera_name)
|
|
530
|
-
location = location / Path("obs") / f"{prefix}"
|
|
531
|
-
if not os.path.exists(location):
|
|
532
|
-
os.makedirs(location)
|
|
533
|
-
|
|
534
|
-
if use_counter:
|
|
535
|
-
counter_file_path = location / f"{prefix}_{identifier}.count"
|
|
536
|
-
if not counter_file_path.exists():
|
|
537
|
-
pattern = f"{prefix}_{identifier}_{timestamp}_*.{ext}"
|
|
538
|
-
counter = determine_counter_from_dir_list(location, pattern)
|
|
539
|
-
_write_counter(counter, counter_file_path)
|
|
540
|
-
else:
|
|
541
|
-
counter = get_counter(counter_file_path)
|
|
542
|
-
name = f"{prefix}_{identifier}_{counter:05d}_{timestamp}.{ext}"
|
|
543
|
-
else:
|
|
544
|
-
name = f"{prefix}_{identifier}_{timestamp}.{ext}"
|
|
545
|
-
|
|
546
|
-
else:
|
|
547
|
-
|
|
548
|
-
timestamp = datetime.datetime.now(tz=datetime.timezone.utc).strftime("%Y%m%d")
|
|
549
|
-
|
|
550
|
-
location = location / Path("daily") / timestamp
|
|
551
|
-
if not os.path.exists(location):
|
|
552
|
-
os.makedirs(location)
|
|
553
|
-
|
|
554
|
-
if use_counter:
|
|
555
|
-
counter_file_path = location / f"{timestamp}_{site_id}_{identifier}.count"
|
|
556
|
-
if not counter_file_path.exists():
|
|
557
|
-
pattern = f"{timestamp}_{site_id}_{identifier}_*.{ext}"
|
|
558
|
-
counter = determine_counter_from_dir_list(location, pattern)
|
|
559
|
-
_write_counter(counter, counter_file_path)
|
|
560
|
-
else:
|
|
561
|
-
counter = get_counter(counter_file_path)
|
|
562
|
-
name = f"{timestamp}_{site_id}_{identifier}_{counter:05d}.{ext}"
|
|
563
|
-
else:
|
|
564
|
-
name = f"{timestamp}_{site_id}_{identifier}.{ext}"
|
|
565
|
-
|
|
566
|
-
return Path(location) / name
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
def _write_counter(counter: int, file_path: Path):
|
|
570
|
-
"""
|
|
571
|
-
Overwrites the given counter in the given file. The file contains nothing else then the counter.
|
|
572
|
-
If the file didn't exist before, it will be created.
|
|
573
|
-
|
|
574
|
-
Args:
|
|
575
|
-
counter: the counter to save
|
|
576
|
-
file_path: the file to which the counter shall be saved
|
|
577
|
-
"""
|
|
578
|
-
with file_path.open('w') as fd:
|
|
579
|
-
fd.write(f"{counter:d}")
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
def _read_counter(file_path: Path) -> int:
|
|
583
|
-
"""
|
|
584
|
-
Reads a counter from the given file. The file shall only contain the counter which must
|
|
585
|
-
be an integer on the first line of the file. If the given file doesn't exist, 0 is returned.
|
|
586
|
-
|
|
587
|
-
Args:
|
|
588
|
-
file_path: the full path of the file containing the counter
|
|
589
|
-
|
|
590
|
-
Returns:
|
|
591
|
-
The counter that is read from the file or 0 if file doesn't exist.
|
|
592
|
-
"""
|
|
593
|
-
try:
|
|
594
|
-
with file_path.open('r') as fd:
|
|
595
|
-
counter = fd.read().strip()
|
|
596
|
-
except FileNotFoundError:
|
|
597
|
-
counter = 0
|
|
598
|
-
return int(counter or 0)
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
def get_counter(file_path: Path) -> int:
|
|
602
|
-
"""
|
|
603
|
-
Read the counter from a dedicated file, add one and save the counter back to the file..
|
|
604
|
-
|
|
605
|
-
Args:
|
|
606
|
-
- file_path: full pathname of the file that contains the required counter
|
|
607
|
-
|
|
608
|
-
Returns:
|
|
609
|
-
The value of the next counter, 1 if no previous files were found or if an error occurred.
|
|
610
|
-
"""
|
|
611
|
-
|
|
612
|
-
counter = _read_counter(file_path)
|
|
613
|
-
counter += 1
|
|
614
|
-
_write_counter(counter, file_path)
|
|
615
|
-
|
|
616
|
-
return counter
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
def determine_counter_from_dir_list(location, pattern, index: int = -1):
|
|
620
|
-
"""
|
|
621
|
-
Determine counter for a new file at the given location and with the given pattern.
|
|
622
|
-
The next counter is determined from the sorted list of files that match the given pattern.
|
|
623
|
-
|
|
624
|
-
Args:
|
|
625
|
-
- location: Location where the file should be stored.
|
|
626
|
-
- pattern: Pattern for the filename.
|
|
627
|
-
- index: the location of the counter in the filename after it is split on '_' [default=-1]
|
|
628
|
-
|
|
629
|
-
Returns:
|
|
630
|
-
The value of the next counter, 1 if no previous files were found or if an error occurred.
|
|
631
|
-
"""
|
|
632
|
-
|
|
633
|
-
files = sorted(find_files(pattern=pattern, root=location))
|
|
634
|
-
|
|
635
|
-
# No filenames found showing the given pattern -> start counting at 1
|
|
636
|
-
|
|
637
|
-
if len(files) == 0:
|
|
638
|
-
return 1
|
|
639
|
-
|
|
640
|
-
last_file = files[-1]
|
|
641
|
-
|
|
642
|
-
parts = last_file.name.split("_")
|
|
643
|
-
|
|
644
|
-
try:
|
|
645
|
-
|
|
646
|
-
# Observation files have the following pattern:
|
|
647
|
-
# <test ID>_<lab ID>_<setup ID>_<storage mnemonic>_<day YYYYmmdd>_<time HHMMSS>[_<counter>]
|
|
648
|
-
# Daily files:
|
|
649
|
-
# <day>_<site ID>_<storage mnemonic>[_<counter>]
|
|
650
|
-
|
|
651
|
-
counter = int(parts[index].split(".")[0]) + 1
|
|
652
|
-
logger.debug(f"{counter = }")
|
|
653
|
-
return counter
|
|
654
|
-
|
|
655
|
-
except ValueError:
|
|
656
|
-
logger.warning("ValueError", exc_info=True)
|
|
657
|
-
return 1
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
class StorageController(StorageInterface, EventInterface):
|
|
661
|
-
"""
|
|
662
|
-
The Storage Controller handles the registration of components, the start and end of an
|
|
663
|
-
observation/test and the dispatching of the persistence functions in save.
|
|
664
|
-
"""
|
|
665
|
-
|
|
666
|
-
def __init__(self, control_server):
|
|
667
|
-
self._obsid: ObservationIdentifier | None = None
|
|
668
|
-
self._camera_name: str | None = None
|
|
669
|
-
self._registry = Registry()
|
|
670
|
-
self._cs: ControlServer = control_server
|
|
671
|
-
self._setup: Setup | None = None
|
|
672
|
-
|
|
673
|
-
def start_observation(self, obsid: ObservationIdentifier, camera_name: str = None) -> Response:
|
|
674
|
-
if self._obsid is not None:
|
|
675
|
-
return Failure(
|
|
676
|
-
"Can not start a new observation before the previous observation is ended."
|
|
677
|
-
)
|
|
678
|
-
|
|
679
|
-
self._obsid = obsid
|
|
680
|
-
self._camera_name = camera_name
|
|
681
|
-
if camera_name != self._setup.camera.ID.lower():
|
|
682
|
-
logger.error(
|
|
683
|
-
f"Mismatch in camera name between Setup in Storage Manager {self._setup.camera.ID.lower()} "
|
|
684
|
-
f"and Setup in Configuration Manager {camera_name}!"
|
|
685
|
-
)
|
|
686
|
-
|
|
687
|
-
# open a dedicated file for each registered item
|
|
688
|
-
|
|
689
|
-
for registered_name in self._registry:
|
|
690
|
-
registered_item = self._registry.get(registered_name)
|
|
691
|
-
|
|
692
|
-
if "persistence_count" in registered_item:
|
|
693
|
-
# no need to fork any files that contain persistence_counts
|
|
694
|
-
continue
|
|
695
|
-
|
|
696
|
-
if issubclass(registered_item["persistence_class"], HDF5):
|
|
697
|
-
# do not duplicate HDF5 files during an observation - issue #1186
|
|
698
|
-
continue
|
|
699
|
-
|
|
700
|
-
# NOTE: The following lines of code contain tests for special treatment of HDF5 files
|
|
701
|
-
# while the above check disables HDF5 files in OBS. We leave the code in for now until
|
|
702
|
-
# a definite decision is taken.
|
|
703
|
-
|
|
704
|
-
filename = _construct_filename(
|
|
705
|
-
registered_item["origin"],
|
|
706
|
-
registered_item["persistence_class"].extension,
|
|
707
|
-
obsid,
|
|
708
|
-
use_counter=issubclass(registered_item["persistence_class"], HDF5),
|
|
709
|
-
camera_name=camera_name
|
|
710
|
-
)
|
|
711
|
-
|
|
712
|
-
# logger.debug(f"{filename = }, {camera_name = }")
|
|
713
|
-
|
|
714
|
-
# we have more than one file open for this item DAILY and OBS.... take care of that
|
|
715
|
-
|
|
716
|
-
# Special case for HDF5 files as they need to be copied instead of created
|
|
717
|
-
|
|
718
|
-
if issubclass(registered_item["persistence_class"], HDF5):
|
|
719
|
-
daily_file_object: HDF5 = registered_item["persistence_objects"][0]
|
|
720
|
-
daily_file_path: Path = daily_file_object.get_filepath()
|
|
721
|
-
logger.debug(f"Copying {daily_file_path} to {filename}")
|
|
722
|
-
|
|
723
|
-
# Close the HDF5 file before copy, otherwise you will get
|
|
724
|
-
# a 'bad object header version number' when opening the destination.
|
|
725
|
-
|
|
726
|
-
daily_file_object.close()
|
|
727
|
-
shutil.copy(daily_file_path, filename)
|
|
728
|
-
daily_file_object.open(mode='a')
|
|
729
|
-
|
|
730
|
-
persistence_obj = registered_item["persistence_class"](
|
|
731
|
-
filename, prep=registered_item["prep"]
|
|
732
|
-
)
|
|
733
|
-
|
|
734
|
-
mode = "a" if persistence_obj.exists() else "w"
|
|
735
|
-
persistence_obj.open(mode=mode)
|
|
736
|
-
|
|
737
|
-
registered_item["persistence_objects"].append(persistence_obj)
|
|
738
|
-
|
|
739
|
-
return Success("Storage successfully started observation.")
|
|
740
|
-
|
|
741
|
-
def end_observation(self, obsid: ObservationIdentifier) -> Response:
|
|
742
|
-
if obsid != self._obsid:
|
|
743
|
-
return Failure(f"Given obsid doesn't match current obsid: {obsid} != {self._obsid}")
|
|
744
|
-
|
|
745
|
-
# close the dedicated file for each registered item
|
|
746
|
-
|
|
747
|
-
for registered_name in self._registry:
|
|
748
|
-
registered_item = self._registry.get(registered_name)
|
|
749
|
-
if "persistence_count" in registered_item:
|
|
750
|
-
# no need to close any files that contain persistence_counts
|
|
751
|
-
continue
|
|
752
|
-
try:
|
|
753
|
-
persistence_obj = registered_item["persistence_objects"].pop()
|
|
754
|
-
persistence_obj.close()
|
|
755
|
-
except IndexError as exc:
|
|
756
|
-
logger.warning(f"Trying to close a persistent object for {registered_name}, {exc=}")
|
|
757
|
-
|
|
758
|
-
self._obsid = None
|
|
759
|
-
self._camera_name = None
|
|
760
|
-
|
|
761
|
-
return Success("Storage successfully ended observation.")
|
|
762
|
-
|
|
763
|
-
def get_obsid(self):
|
|
764
|
-
return self._obsid
|
|
765
|
-
|
|
766
|
-
def save(self, item: dict) -> Response:
|
|
767
|
-
"""Saves the data contained in this item to the right location and format.
|
|
768
|
-
|
|
769
|
-
Args:
|
|
770
|
-
item: dictionary with at least the following keywords - origin, data
|
|
771
|
-
Returns:
|
|
772
|
-
Success: when the data has been properly saved.
|
|
773
|
-
"""
|
|
774
|
-
# TODO:
|
|
775
|
-
# this method might become a performance problem and the reason that we might have
|
|
776
|
-
# back pressure problem. Keep an eye on this and do performance tests.
|
|
777
|
-
|
|
778
|
-
# What needs to be done:
|
|
779
|
-
# * based on item['origin'], check if item component is registered
|
|
780
|
-
# * get register entry for item
|
|
781
|
-
# * for persistence in persistence list:
|
|
782
|
-
# persistence.create(data)
|
|
783
|
-
|
|
784
|
-
registered_item = self._registry.get(item["origin"])
|
|
785
|
-
|
|
786
|
-
if not registered_item:
|
|
787
|
-
return Failure(
|
|
788
|
-
f"Storage could not find a registration for {item['origin']}, no data saved."
|
|
789
|
-
)
|
|
790
|
-
|
|
791
|
-
for persistence_object in registered_item["persistence_objects"]:
|
|
792
|
-
persistence_object.create(item["data"])
|
|
793
|
-
|
|
794
|
-
return Success(f"Storage successfully saved the data for {item['origin']}.")
|
|
795
|
-
|
|
796
|
-
def read(self, item: dict):
|
|
797
|
-
|
|
798
|
-
registered_item = self._registry.get(item["origin"])
|
|
799
|
-
|
|
800
|
-
if not registered_item:
|
|
801
|
-
return Failure(
|
|
802
|
-
f"Storage could not find a registration for {item['origin']}, no data saved."
|
|
803
|
-
)
|
|
804
|
-
|
|
805
|
-
# FIXME:
|
|
806
|
-
# * wat als meerdere persistence_objects bestaan? alleen de eerste, alleen de laatste,
|
|
807
|
-
# samenvoegen? een nieuw keyword in item?
|
|
808
|
-
|
|
809
|
-
result = None
|
|
810
|
-
for persistence_object in registered_item["persistence_objects"]:
|
|
811
|
-
result = persistence_object.read(item["select"])
|
|
812
|
-
|
|
813
|
-
return Success(f"Storage successfully read the data from {item['origin']}.", result)
|
|
814
|
-
|
|
815
|
-
def register(self, item: dict, use_counter=False) -> Response:
|
|
816
|
-
|
|
817
|
-
if not isinstance(item, dict):
|
|
818
|
-
return Failure(
|
|
819
|
-
f"Could not register item, item must be a dictionary (item={type(item)})."
|
|
820
|
-
)
|
|
821
|
-
|
|
822
|
-
prep = item.get("prep", {})
|
|
823
|
-
|
|
824
|
-
# When we register an item, the file should always be 'created', unless this is a
|
|
825
|
-
# persistence count and we just need to append to the file, always.
|
|
826
|
-
|
|
827
|
-
# if "persistence_count" not in item:
|
|
828
|
-
# prep.update({"mode": "w"})
|
|
829
|
-
|
|
830
|
-
if "origin" not in item or "persistence_class" not in item:
|
|
831
|
-
return Failure("Could not register item, missing mandatory keyword(s).")
|
|
832
|
-
|
|
833
|
-
try:
|
|
834
|
-
self._registry.register(item["origin"], item)
|
|
835
|
-
|
|
836
|
-
if "filename" in item:
|
|
837
|
-
location = Path(get_data_storage_location(site_id=SITE.ID))
|
|
838
|
-
filename = location / item["filename"]
|
|
839
|
-
else:
|
|
840
|
-
filename = _construct_filename(item["origin"], item["persistence_class"].extension,
|
|
841
|
-
use_counter=use_counter)
|
|
842
|
-
|
|
843
|
-
persistence_obj = item["persistence_class"](filename, prep=prep)
|
|
844
|
-
mode = "a" if persistence_obj.exists() else "w"
|
|
845
|
-
persistence_obj.open(mode=mode)
|
|
846
|
-
|
|
847
|
-
# add the PersistenceLayer object to the registered item
|
|
848
|
-
|
|
849
|
-
item["persistence_objects"] = [persistence_obj]
|
|
850
|
-
|
|
851
|
-
# Special case when the components registration is done after an observation was
|
|
852
|
-
# started, unless we are handling a persistence_count, in which case there is only one
|
|
853
|
-
# file.
|
|
854
|
-
|
|
855
|
-
if (
|
|
856
|
-
self._obsid
|
|
857
|
-
and "persistence_count" not in item
|
|
858
|
-
and not issubclass(item["persistence_class"], HDF5)
|
|
859
|
-
):
|
|
860
|
-
filename = _construct_filename(
|
|
861
|
-
item["origin"], item["persistence_class"].extension, self._obsid,
|
|
862
|
-
use_counter=use_counter, camera_name=self._camera_name
|
|
863
|
-
)
|
|
864
|
-
|
|
865
|
-
persistence_obj = item["persistence_class"](filename, prep=prep)
|
|
866
|
-
mode = "a" if persistence_obj.exists() else "w"
|
|
867
|
-
persistence_obj.open(mode=mode)
|
|
868
|
-
|
|
869
|
-
item["persistence_objects"].append(persistence_obj)
|
|
870
|
-
|
|
871
|
-
msg = f"Storage successfully registered {item['origin']}"
|
|
872
|
-
logger.info(msg)
|
|
873
|
-
return Success(msg)
|
|
874
|
-
except AlreadyRegisteredError as exc:
|
|
875
|
-
msg = f"Could not register {item['origin']}: {exc}"
|
|
876
|
-
logger.error(msg)
|
|
877
|
-
return Success(f"{item['origin']} is already registered.")
|
|
878
|
-
except (ValueError, KeyError) as exc:
|
|
879
|
-
# FIXME:
|
|
880
|
-
# Should I unregister here? and if yes, should I not call the
|
|
881
|
-
# self.unregister(item) method instead?
|
|
882
|
-
self._registry.unregister(item["origin"])
|
|
883
|
-
msg = f"Could not register {item['origin']}: {exc}"
|
|
884
|
-
logger.error(msg)
|
|
885
|
-
return Failure(f"Could not register {item['origin']}", exc)
|
|
886
|
-
|
|
887
|
-
def unregister(self, item) -> Response:
|
|
888
|
-
try:
|
|
889
|
-
registered_item = self._registry.get(item["origin"])
|
|
890
|
-
if registered_item is None:
|
|
891
|
-
raise ValueError("The item is not registered.")
|
|
892
|
-
|
|
893
|
-
# Probably also should close the file and some other things
|
|
894
|
-
|
|
895
|
-
for persistence_obj in registered_item.get("persistence_objects", []):
|
|
896
|
-
persistence_obj.close()
|
|
897
|
-
|
|
898
|
-
self._registry.unregister(item["origin"])
|
|
899
|
-
|
|
900
|
-
msg = f"Storage successfully unregistered {item['origin']}"
|
|
901
|
-
logger.info(msg)
|
|
902
|
-
return Success(msg)
|
|
903
|
-
except (ValueError, KeyError) as exc:
|
|
904
|
-
return Failure(f"Could not unregister {item['origin']}", exc)
|
|
905
|
-
|
|
906
|
-
def get_registry_names(self):
|
|
907
|
-
return list(self._registry)
|
|
908
|
-
|
|
909
|
-
def cycle_daily_files(self):
|
|
910
|
-
|
|
911
|
-
logger.info("Cycling daily files for Storage Manager")
|
|
912
|
-
|
|
913
|
-
for reg_name in self._registry:
|
|
914
|
-
item = self._registry.get(reg_name)
|
|
915
|
-
if "persistence_count" in item:
|
|
916
|
-
# no need to cycle any files that contain persistence_counts
|
|
917
|
-
continue
|
|
918
|
-
if "persistence_objects" in item:
|
|
919
|
-
logger.info(f"Cycling daily file for {item['origin']}.")
|
|
920
|
-
|
|
921
|
-
# The first item in the list is always the daily persistence object, however, for
|
|
922
|
-
# the N-FEE_SPW origin, sometimes when the N-FEE is not ON, there is no persistence
|
|
923
|
-
# object. (see issue #1458) So, we catch this and continue.
|
|
924
|
-
|
|
925
|
-
try:
|
|
926
|
-
daily_persist_obj = item["persistence_objects"][0]
|
|
927
|
-
daily_persist_obj.close()
|
|
928
|
-
except IndexError:
|
|
929
|
-
logger.info(f"I'm ignoring that there is no persistence_object "
|
|
930
|
-
f"for {item['origin']} at this time.")
|
|
931
|
-
continue
|
|
932
|
-
|
|
933
|
-
# Create folder for the day
|
|
934
|
-
filename = _construct_filename(item['origin'], item['persistence_class'].extension)
|
|
935
|
-
|
|
936
|
-
persistence_obj: PersistenceLayer = item["persistence_class"](
|
|
937
|
-
filename, prep=item.get("prep")
|
|
938
|
-
)
|
|
939
|
-
mode = "a" if persistence_obj.exists() else "w"
|
|
940
|
-
persistence_obj.open(mode=mode)
|
|
941
|
-
|
|
942
|
-
# replace the previous daily persistence object with the current
|
|
943
|
-
|
|
944
|
-
item["persistence_objects"][0] = persistence_obj
|
|
945
|
-
|
|
946
|
-
else:
|
|
947
|
-
# We should never get here, since when an item is registered, the 'file' is
|
|
948
|
-
# opened and it should exist
|
|
949
|
-
logger.error(
|
|
950
|
-
f"Found a registered item {item} that has no persistence objects.",
|
|
951
|
-
stack_info=True,
|
|
952
|
-
)
|
|
953
|
-
|
|
954
|
-
def get_storage_location(self):
|
|
955
|
-
return get_data_storage_location(site_id=SITE.ID)
|
|
956
|
-
|
|
957
|
-
def get_filenames(self, item: dict) -> List[Path]:
|
|
958
|
-
registered_item = self._registry.get(item["origin"])
|
|
959
|
-
|
|
960
|
-
if not registered_item:
|
|
961
|
-
return []
|
|
962
|
-
|
|
963
|
-
return [
|
|
964
|
-
persistence_object.get_filepath()
|
|
965
|
-
for persistence_object in registered_item["persistence_objects"]
|
|
966
|
-
]
|
|
967
|
-
|
|
968
|
-
def new_registration(self, item: dict, use_counter=False) -> Response:
|
|
969
|
-
if item["origin"] in self.get_registry_names():
|
|
970
|
-
_ = self.unregister(item)
|
|
971
|
-
|
|
972
|
-
response = self.register(item, use_counter=use_counter)
|
|
973
|
-
logger.info(f"From register: {response=}")
|
|
974
|
-
return response
|
|
975
|
-
|
|
976
|
-
def get_disk_usage(self):
|
|
977
|
-
|
|
978
|
-
location = Path(get_data_storage_location(site_id=SITE.ID))
|
|
979
|
-
total, used, free = shutil.disk_usage(location)
|
|
980
|
-
return total, used, free
|
|
981
|
-
|
|
982
|
-
def get_loaded_setup_id(self) -> str:
|
|
983
|
-
return self._setup.get_id() if self._setup is not None else "no setup loaded"
|
|
984
|
-
|
|
985
|
-
def load_setup(self, setup_id: int = 0):
|
|
986
|
-
# Use get_setup() here instead of load_setup() in order to prevent recursively notifying and loading Setups.
|
|
987
|
-
# That is because the load_setup() method will notify the listeners that a new Setup has been loaded.
|
|
988
|
-
try:
|
|
989
|
-
setup = get_setup()
|
|
990
|
-
except Exception as exc:
|
|
991
|
-
raise RuntimeError(f"Exception caught: {exc!r}")
|
|
992
|
-
|
|
993
|
-
if setup is None:
|
|
994
|
-
raise RuntimeError("Couldn't get Setup from the configuration manager.")
|
|
995
|
-
|
|
996
|
-
if isinstance(setup, Failure):
|
|
997
|
-
raise setup
|
|
998
|
-
|
|
999
|
-
# time.sleep(20.0) # used as a test to check if this method is blocking the commanding... it is!
|
|
1000
|
-
|
|
1001
|
-
# logger.info(f"{setup_id = }, {setup.get_id() = }")
|
|
1002
|
-
|
|
1003
|
-
if 0 < setup_id != int(setup.get_id()):
|
|
1004
|
-
raise RuntimeError(f"Setup IDs do not match: {setup.get_id()} != {setup_id}, no Setup loaded.")
|
|
1005
|
-
else:
|
|
1006
|
-
self._setup = setup
|
|
1007
|
-
logger.info(f"Setup {setup.get_id()} loaded in the Storage manager.")
|
|
1008
|
-
|
|
1009
|
-
def handle_event(self, event: Event) -> str:
|
|
1010
|
-
logger.info(f"An event is received, {event=}")
|
|
1011
|
-
try:
|
|
1012
|
-
if event.type == 'new_setup':
|
|
1013
|
-
self._cs.schedule_task(partial(self.load_setup, setup_id=event.context['setup_id']))
|
|
1014
|
-
except KeyError as exc:
|
|
1015
|
-
return f"Expected event context to contain the following key: {exc}"
|
|
1016
|
-
return "ACK"
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
class StorageCommand(ClientServerCommand):
|
|
1020
|
-
pass
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
class StorageProxy(Proxy, StorageInterface, EventInterface):
|
|
1024
|
-
"""The StorageProxy class is used to connect to the Storage Manager (control server) and
|
|
1025
|
-
send commands remotely."""
|
|
1026
|
-
|
|
1027
|
-
def __init__(
|
|
1028
|
-
self,
|
|
1029
|
-
protocol=CTRL_SETTINGS.PROTOCOL,
|
|
1030
|
-
hostname=CTRL_SETTINGS.HOSTNAME,
|
|
1031
|
-
port=CTRL_SETTINGS.COMMANDING_PORT,
|
|
1032
|
-
):
|
|
1033
|
-
"""
|
|
1034
|
-
Args:
|
|
1035
|
-
protocol: the transport protocol [default is taken from settings file]
|
|
1036
|
-
hostname: location of the control server (IP address)
|
|
1037
|
-
[default is taken from settings file]
|
|
1038
|
-
port: TCP port on which the control server is listening for commands
|
|
1039
|
-
[default is taken from settings file]
|
|
1040
|
-
"""
|
|
1041
|
-
super().__init__(connect_address(protocol, hostname, port))
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
class StorageProtocol(CommandProtocol):
|
|
1045
|
-
def __init__(self, control_server: ControlServer):
|
|
1046
|
-
super().__init__()
|
|
1047
|
-
self.control_server = control_server
|
|
1048
|
-
|
|
1049
|
-
self.controller = StorageController(control_server)
|
|
1050
|
-
|
|
1051
|
-
self.load_commands(COMMAND_SETTINGS.Commands, StorageCommand, StorageController)
|
|
1052
|
-
|
|
1053
|
-
self.build_device_method_lookup_table(self.controller)
|
|
1054
|
-
|
|
1055
|
-
def get_bind_address(self):
|
|
1056
|
-
return bind_address(
|
|
1057
|
-
self.control_server.get_communication_protocol(),
|
|
1058
|
-
self.control_server.get_commanding_port(),
|
|
1059
|
-
)
|
|
1060
|
-
|
|
1061
|
-
def get_status(self) -> dict:
|
|
1062
|
-
return super().get_status()
|
|
1063
|
-
|
|
1064
|
-
def get_housekeeping(self) -> dict:
|
|
1065
|
-
return {
|
|
1066
|
-
"timestamp": format_datetime(),
|
|
1067
|
-
}
|