450 Commits

Author SHA1 Message Date
gac bernina ff057af10f gic, camera and fixes 2026-06-05 19:50:22 +02:00
gac bernina 1aa2afe35a fixes 2026-06-05 16:09:09 +02:00
gac bernina dcc3f6d774 fixes 2026-06-04 11:47:33 +02:00
gac bernina 23edabcbcb adding some string to failed namespace inits 2026-05-26 11:58:30 +02:00
gac bernina ff3899ef82 names in grid 2026-05-22 14:54:59 +02:00
gac bernina ae85841a57 las / lxt oder fix 2026-05-22 12:00:50 +02:00
gac bernina bb857d6a74 path adjustments away fromhome folder of gac-bernina 2026-05-22 11:56:47 +02:00
gac bernina 11364bc067 tweak fixes 2026-03-28 23:10:09 +01:00
gac bernina f8c133c7dd tweak addition 2026-03-28 21:19:06 +01:00
Henrik Lemke ca809d5a8a scan fixes example data 2026-03-28 21:11:32 +01:00
gac bernina bc397711b8 new config 2026-03-28 15:28:26 +01:00
Henrik Lemke 48edc92261 test motor tweak 2026-03-28 15:13:30 +01:00
sander_m 7f32de4b8e long time comit 2026-03-26 10:20:42 +01:00
sander_m 7797fc42f4 jet ih implementations 2025-12-03 21:50:08 +01:00
sander_m c982215c92 Merge branch 'dev' of https://github.com/paulscherrerinstitute/eco 2025-11-17 20:13:19 +01:00
sander_m 878e31594e jet stuff added 2025-11-17 19:58:20 +01:00
sander_m f0fbcabf55 fixing once more the recursion issues 2025-11-17 19:41:59 +01:00
sander_m 2c456d3a9a Merge branch 'master' of https://github.com/paulscherrerinstitute/eco into dev 2025-11-14 15:34:45 +01:00
sander_m 545d4d80ff more fixes 2025-11-14 15:32:30 +01:00
sander_m eabfd0dbf1 tmp adding 2025-11-14 15:30:05 +01:00
Henrik Lemke f8fdc20d3f added tk widget 2025-11-09 21:34:17 +01:00
Henrik Lemke f537827bfe widgets and group dymanics start 2025-11-09 11:27:13 +01:00
sander_m cc1dedcbca seems to got leakage free. 2025-11-07 11:26:10 +01:00
sander_m e6a0e037f4 now with most fixes 2025-11-04 17:26:04 +01:00
sander_m f49778ee5f am 2025-11-03 17:35:10 +01:00
sander_m 9b85d2ca76 rework collections quite there except memories 2025-11-03 10:10:34 +01:00
sander_m 2f56fac108 rework collection first debugging 2025-10-30 12:43:03 +01:00
sander_m 35fc5df9df post pandya 2025-10-28 21:46:01 +01:00
sander_m 9ad3b1d61e changed run_table to dynamically adjust to the current pgroup set in config_bernina.pgroup 2025-10-10 10:19:58 +02:00
sander_m 06f09ef07e am 2025-08-22 22:52:03 +02:00
sander_m bd272f227d am 2025-08-22 21:47:45 +02:00
sander_m 40f523c294 CounterValue and implementation 2025-08-22 17:14:34 +02:00
sander_m aa0b9f36a9 Changes assembly collections to behave more dynamic. 2025-08-05 14:34:25 +02:00
sander_m 8271bc837d post chapman 2025-08-05 10:23:22 +02:00
sander_m 07e73cb472 chapman start 2025-07-14 22:34:09 +02:00
sander_m 9dce2eb353 restrcture 1 2025-07-09 09:33:06 +02:00
sander_m d54109e6de after trigo 2025-06-24 15:33:02 +02:00
sander_m 8ade6cf615 fix of error handling with object proxy! 2025-04-25 13:07:44 +02:00
sander_m fea6dd0fdb fixes 2025-04-25 12:21:39 +02:00
sander_m fed6eb3ed1 improved dap a bit, added flighttube slits for xrd. 2025-03-24 22:01:18 +01:00
sander_m 0d11fea537 Virtual adjustable fixes 2025-03-06 15:37:58 +01:00
sander_m 1c5f1815c2 pi hex adjustment 2025-02-27 22:23:06 +01:00
sander_m 830bc9222b hack for scan fail issue on new instances. 2025-02-23 22:06:43 +01:00
sander_m 7f398e4b8c intermediate commit janod 2025-02-21 18:00:05 +01:00
sander_m 66cf353a36 before janod 2025-02-18 09:39:31 +01:00
sander_m 63c4a48317 lots of small changes: renamed nu to gamma, changes to config_bernina and recspace, FS adjustable cache now cleared before writing 2024-12-09 16:25:55 +01:00
sander_m 8c892f3fe3 added sli_cleanup 2024-11-03 21:33:42 +01:00
sander_m feaa6688dc am 2024-11-03 13:00:41 +01:00
sander_m e68bb84c1d fixes and addons 2024-11-03 12:59:28 +01:00
sander_m 87225bc18b implemented lxt 2024-10-29 23:22:32 +01:00
sander_m d2f9dd2b1d fix0 2024-10-14 20:30:48 +02:00
sander_m d682107ba0 fixes during 24h_staub 2024-10-11 11:40:47 +02:00
sander_m b620a87f61 tt_kb.calibrate() function send image and information about the calibration to the elog 2024-10-03 15:01:23 +02:00
sander_m 81994df4ec recspace: improved output when diffractometer limits do not allow one unique solution 2024-09-10 14:30:09 +02:00
sander_m b5406ca61f added JF16M visualization URDF model 2024-08-26 08:44:01 +02:00
sander_m 86fbd27e82 fx 2024-08-20 16:58:23 +02:00
sander_m 99a0876421 am 2024-06-30 23:10:53 +02:00
sander_m 80cddac125 am 2024-06-30 22:56:58 +02:00
sander_m 429fa93b42 am 2024-06-30 16:24:18 +02:00
sander_m 3716c65fae am 2024-06-30 14:21:21 +02:00
sander_m 7c5ae3a6f7 fixes 2024-06-28 17:16:07 +02:00
sander_m 224ae6266e added new tt_kb pipeline and central pixel set and get functions for the robot 2024-06-24 15:38:53 +02:00
sander_m 8a3143bb2f rough fix of run number issue 2024-06-23 11:20:12 +02:00
sander_m daeb80dc39 am 2024-06-22 21:33:38 +02:00
sander_m 670d7002c8 many 2024-06-22 20:20:40 +02:00
sander_m 98a31cfec2 news and fixes staub 2024-06-17 15:49:22 +02:00
sander_m 24a89f7aa2 fixes with system upgrades and file permissions 2024-04-19 13:54:29 +02:00
sander_m 1ef4b53e9f added diffcalc to the TX200 robot arm 2024-03-15 20:46:59 +01:00
sander_m 5ecc0ae388 fix attenuator enum 2024-03-11 10:04:56 +01:00
sander_m 8aa87381d4 Added archiver workaround and renewed getting data from assemblies etc. 2024-03-05 15:12:15 +01:00
sander_m ab3f36369c end yano state 2024-03-05 10:19:11 +01:00
sander_m c63c215e0b fixes fixes on run_table init 2024-02-27 15:14:17 +01:00
sander_m f61262fe30 updated attenuator class to new panell (start) 2024-02-08 17:08:26 +01:00
sander_m 2748af71f1 added general motion to robot and modified att 2024-02-02 16:11:05 +01:00
sander_m 14bb8b477d added general simulation command for motions and stored motions on the controller 2024-01-15 15:17:10 +01:00
sander_m 0d3188e918 committing changes beginning of 2024 2024-01-09 14:13:53 +01:00
sander_m 07df2011fb fix 2023-12-10 13:54:05 +01:00
sander_m 941b8f0b07 for liqjet bt 2023-12-08 11:04:16 +01:00
sander_m 83a0b8150a fixes 2023-12-03 22:22:46 +01:00
sander_m 4ac8d7f3e1 memory ease of use 2023-12-02 18:35:00 +01:00
sander_m 7edea7bba4 implemented robot visualiyation methods for live viewing of robot motions and simulation of motions 2023-11-13 16:23:18 +01:00
sander_m 3be8c9cb0a stuff... 2023-11-05 21:40:55 +01:00
sander_m 4a77f52686 added files 2023-11-02 09:32:52 +01:00
sander_m cf569258f1 first working robot implementation 2023-11-02 09:32:12 +01:00
sander_m 198f651bbf initial robot implementation 2023-10-30 17:03:16 +01:00
sander_m 25c1080d6c fixes 2023-10-24 21:59:50 +02:00
sander_m 7ccb05479a fixed some deprecation errors in run_table, fixes in xrd kappa 2023-09-29 13:33:30 +02:00
sander_m 634e904a92 mono, fix etc 2023-09-16 10:07:31 +02:00
sander_m 4d6c9edb7a added ps cahnnels to pprm and fix in run table 2023-09-12 11:18:21 +02:00
sander_m b619a83dda lxt Assemblification 2023-09-10 18:18:22 +02:00
sander_m 4815c71042 elog and fixes 2023-09-10 17:56:42 +02:00
sander_m 06980fa5b9 fixes devs for Ovuka 2023-09-07 16:57:34 +02:00
sander_m 3af72a61f2 added units to the PI hexpoad stages 2023-08-30 11:27:08 +02:00
sander_m 9dcc32aaa7 Changed config_cs, added set_cross method to basler cameras, added combined lxt delay stage to bernina, added FS adj limits to the PI hexapod axes, added optional check for limits in Virtual adjustables (kwarg check_limits, defaults to False), added time compensated motion with and without undulators to dcm 2023-08-30 10:27:01 +02:00
sander_m a3672aa4db added pipeline 2023-07-12 22:27:34 +02:00
sander_m 76514d59f9 power supplies wiener 2023-07-06 14:07:34 +02:00
sander_m 10fc9f4c18 start fixing xlt 2023-07-05 12:32:28 +02:00
sander_m 1534cfff9a fixes 2023-06-22 18:55:52 +02:00
sander_m 5f8361cc1e added calibration fucntionality on analog input (WIP4nice) 2023-06-05 13:03:08 +02:00
sander_m c37bd3b87d changes in env sensor wago alib and more. 2023-06-05 11:32:28 +02:00
sander_m 07b6287cd0 fit dsds and usd 2023-05-15 17:14:16 +02:00
sander_m 885fe6e142 MPOD added 2023-05-05 12:38:06 +02:00
sander_m eba351aeb7 setup tyburski 2023-05-02 11:06:13 +02:00
sander_m 4521410936 Fix/debug/add 2023-04-27 09:32:46 +02:00
sander_m 07f6d9666e fixes addition dsd 2023-04-26 12:15:36 +02:00
sander_m a7df23a8ae fixes and new reflaser 2023-04-25 21:28:03 +02:00
sander_m 22cd9d89be fixes 2023-03-15 13:33:43 +01:00
sander_m b44b65a15c fixes 2023-02-28 08:23:16 +01:00
sander_m 97f392ed17 fixes 2023-02-24 09:06:23 +01:00
sander_m f3c85e6842 fixes 2023-02-23 18:20:58 +01:00
sander_m 7436f8be5b fixes 2023-02-10 22:36:55 +01:00
sander_m 8df2a7c1e4 added status free quick scan options 2023-01-25 21:52:12 +01:00
sander_m 9f58ddb768 fixes 2023-01-20 21:59:55 +01:00
sander_m 195575c0cc added monitoring functionality for CA on assemblies 2022-12-05 16:17:01 +01:00
sander_m d1cacf8f5f add monitoring to scans 2022-12-05 13:19:59 +01:00
sander_m ec2d588c11 fixes 2022-11-28 22:43:16 +01:00
sander_m c791359a56 adjusts 2022-10-12 17:03:45 +02:00
sander_m 0287da40da updating mon_opt and after hua 2022-10-12 14:53:43 +02:00
sander_m caa5e5332e changes after kern 2022-09-19 10:24:07 +02:00
sander_m bba65d0756 changed pulse picker methods open/close to not change the trigger source to 62 and 63 2022-09-14 16:37:51 +02:00
sander_m 536329a595 fixes 2022-09-14 11:45:33 +02:00
sander_m 5f0b3852a7 fix camera 2022-09-08 09:53:16 +02:00
sander_m e7682b4c6b fixes 2022-09-07 10:22:07 +02:00
sander_m ee2137469c fixed code for setting the position of the slit in SlitBladesGeneral 2022-09-07 08:43:13 +02:00
sander_m 678a7de458 aramis_attenuator: added second PV check for photon energy if the main PV is NaN. slits: Changed _apply_on_all_blades method of SlitBladesGeneral class to check for methods if the methhod_name is not in the dict keys 2022-09-07 08:18:47 +02:00
sander_m 2bb347c6e5 fixes in many before commissioning 2022-09-04 18:29:35 +02:00
sander_m b464a7bfc1 as befire 2022-09-01 22:26:52 +02:00
sander_m 255e204df4 upgrades of wago, reflaser, bernina config, xeye, and some more. 2022-09-01 22:25:35 +02:00
sander_m 2fc5fb6947 uncomment slit_mono 2022-08-29 14:53:49 +02:00
sander_m cdac3b734e added pedestal copying to directory 2022-08-19 20:34:27 +02:00
sander_m f09f36f721 shutdown work 2022-08-09 10:53:24 +02:00
sander_m ec565f6cbe fixed att_usd opening the pulse picker before transl2 finished moving 2022-07-12 10:43:40 +02:00
sander_m a471bc6a46 fixes 2022-07-11 13:18:47 +02:00
sander_m 6e7e1e5848 colorbars shown when changing adjustables can now deal with multidimensional values 2022-07-10 07:51:41 +02:00
sander_m 6a5b59df11 working on reciprocal space calculations 2022-07-01 18:38:11 +02:00
sander_m 12b8f33175 first version of UB matrix motion implemented 2022-06-26 16:31:29 +02:00
sander_m adfbbe4e86 upgrade of the adjustable value_property 2022-06-25 10:55:23 +02:00
sander_m e7820ccab5 many addition cleanup, for savoini, started to include pointing monitors 2022-06-22 11:40:45 +02:00
sander_m 6a8763f9e2 sequencer fixed 2022-06-03 22:18:47 +02:00
sander_m 494b4a192b 3..8 features 2022-05-24 20:53:08 +02:00
sander_m 1faacd2bbf fixing recursion in assemblies 2022-05-24 16:11:36 +02:00
sander_m bc364d0034 fixing the readback values 2022-05-24 15:12:30 +02:00
sander_m 36af43f4ff refacturing display and settings of assembly 2022-05-24 11:44:16 +02:00
sander_m 5a5bd3a81d fixes 2022-05-21 11:33:56 +02:00
sander_m 55f56dd716 fix 2022-05-14 20:13:01 +02:00
sander_m ef78c3e9c0 fixes 2022-05-13 22:54:01 +02:00
sander_m 46d699b5e5 fixed samplecam_microscope and added config for kappa 2022-05-13 16:43:55 +02:00
sander_m 227d82b4ac cleanups after shutdown 2022-05-13 08:36:45 +02:00
sander_m e59ca1c947 adding smaract record 2022-05-02 12:26:32 +02:00
sander_m d3c6dd24da remove debugging in bernina.py 2022-03-25 14:20:21 +01:00
sander_m dd94966b3b fixes and changes during trigo 2022-03-25 14:18:26 +01:00
sander_m c7e0c0463e new callback function added to scans, which increments the daq run number. the run table is now saved to a temporary file and then moved to prevent corrupted files when the write process is interrupted 2022-03-04 11:54:29 +01:00
sander_m 45258ca958 fixes during and for skoropata 2022-02-22 20:59:38 +01:00
sander_m b25fba8d3e runtable added evrs 2022-02-14 18:44:10 +01:00
sander_m a2296adad7 runtable added recall functionality 2022-02-14 14:27:46 +01:00
sander_m 8e94e218fc added coldfinger temperature to thc 2022-02-11 14:38:47 +01:00
sander_m 0220817f78 runtable display and usability optimizations 2022-02-09 12:40:11 +01:00
sander_m 8c1b47cd7a runtable fixes 2022-02-04 21:18:02 +01:00
sander_m ff92ebe9a6 changes for LiNb campaign 2022-02-04 10:49:25 +01:00
sander_m 12da0f7d68 added new functionalities to the runtable 2022-02-02 17:33:38 +01:00
sander_m 89ea522a6e wrote new run table class 2022-01-31 18:18:34 +01:00
sander_m fb946294f0 fixes after wintershut down 20212022 2022-01-30 11:10:57 +01:00
sander_m 2fa8b1ef2c updates kb and temp controller 2021-12-06 18:19:45 +01:00
sander_m 47d358e067 added cryostat temperature adjustable to thc 2021-12-06 14:46:48 +01:00
sander_m f11f0740d9 added new att_usd with two stages 2021-12-06 13:09:43 +01:00
sander_m 268965b702 fixes 2021-11-30 14:37:27 +01:00
sander_m b3e70727b3 added ottifant calculations 2021-11-30 09:05:35 +01:00
sander_m 808a7ad570 added ottifant motors to thc 2021-11-24 12:12:56 +01:00
sander_m 7b1b85707d fixes and addons e.g for scans 2021-11-22 20:47:13 +01:00
sander_m 2d43461419 fixes 2021-11-12 12:34:09 +01:00
sander_m d86a597467 moved Assembly back to assembly, memory default set_change_only 2021-11-11 09:29:56 +01:00
sander_m aa8c1f2353 fixes 2021-11-09 15:07:43 +01:00
sander_m 53000a8c3e fix 2021-11-03 12:39:49 +01:00
sander_m 4e61339606 writing into json file 2021-11-03 12:13:02 +01:00
sander_m 2477c0ae0c added new arciver functionality and new ps detector class 2021-11-02 15:52:23 +01:00
sander_m 4a8cfdf834 added automatic creation of google spreadsheets to the run_table class. Added function to change multiple camera cfgs of the CamServer based on conditions. All cameras have now the same naming convention. Added convenience function to clear all bernina cam aliases to config_cs. Moved prof_dsd and mon_dsd out of dsd class. 2021-11-02 11:59:07 +01:00
sander_m 87a24eabb2 Introduced new stype cta working, removed the old one. 2021-11-02 11:29:08 +01:00
sander_m 0adfa32983 hack for stall fue to not broken experimental file system 2021-10-29 16:39:28 +02:00
sander_m f5663a700c detector fixes, atmo 2021-10-16 18:05:36 +02:00
sander_m f36b68a00c detector fix 2021-10-16 18:04:54 +02:00
sander_m 954b5b9102 fix 2021-10-11 20:43:40 +02:00
sander_m 0e0a942eb4 additions to motors, path_alias, bugfox in scan 2021-10-11 20:43:00 +02:00
sander_m 3cd730e616 fixes for fuchs and started new sequencer module 2021-10-10 09:40:17 +02:00
sander_m 7c9eaca8ef fixes and undulator energy changes 2021-10-05 12:25:02 +02:00
sander_m de756a81c4 mono pathlongth compensation, runtable stuff, elog fix to html default 2021-09-20 10:26:39 +02:00
sander_m 5a39189bcd runtable changes 2021-09-13 20:50:11 +02:00
sander_m d9c552fe11 started work on run table 2021-09-13 20:49:37 +02:00
sander_m f8f5d04f79 added new functionality in daq 2021-09-08 10:31:22 +02:00
sander_m 57273891ec added PVs to fel spectrometer 2021-09-07 14:31:33 +02:00
sander_m dc4516dc34 starting upgrading daq, fixes in reflaser 2021-09-04 14:04:17 +02:00
mankowsky_r 5c24a8dd50 After cao situation 2021-07-12 15:55:46 +02:00
mankowsky_r 7a540dee7f fix ofwrong PV channel in kb_mirrors 2021-07-03 09:18:24 +02:00
mankowsky_r 02075c0d75 Added stuff, renamed xrd_you to xrd in eco 2021-06-22 18:27:22 +02:00
mankowsky_r b7bea0ba10 added photonics evr and more 2021-06-02 16:19:08 +02:00
mankowsky_r a56253cbf1 kappa conversion added to gps 2021-05-28 23:05:01 +02:00
mankowsky_r b0c7b240fc daq fix for json files in raw 2021-05-27 13:42:25 +02:00
mankowsky_r eea72f45b4 added tweaker 2021-05-25 21:56:03 +02:00
mankowsky_r cae53d791a added tth virtual motor to RIXS, energy readback of an analyser crystal is now set to None if tth and om do not agree 2021-05-25 12:19:11 +02:00
mankowsky_r bc8a22a556 Bigger refactor again, adding stuff 2021-05-22 15:57:53 +02:00
mankowsky_r 1290e76ce2 Bigger refactor! 2021-05-22 10:34:54 +02:00
mankowsky_r 2612204114 before refactor 2021-05-22 10:09:35 +02:00
mankowsky_r 303a034e2b add status flag to motors 2021-05-21 08:33:44 +02:00
mankowsky_r 5f6d58e913 loads 2021-05-20 23:29:39 +02:00
mankowsky_r b61fb2fbe6 added rixs analyzer, motor config schneider option 2021-05-18 17:38:55 +02:00
mankowsky_r c4ca1f55d8 continue work on general adjustable tweaker 2021-05-12 12:57:42 +02:00
mankowsky_r a62d619ab4 added stuff for rixs, started on general tweak tool, worked on att_usd 2021-05-11 17:47:03 +02:00
mankowsky_r 106a93ba52 added stuff for rixs, started on general tweak tool, worked on att_usd 2021-05-11 17:43:14 +02:00
mankowsky_r e7bfbe3f8a physical diffractometer axes 2021-05-07 18:44:44 +02:00
mankowsky_r 60e3de2a29 implemented slit_und as new dipe device and added it 2021-05-03 16:40:54 +02:00
mankowsky_r e381778bbb fixes to many thing, kb eco based bender comb motions 2021-05-01 15:16:40 +02:00
mankowsky_r 6ee2549634 littel update in documentation. 2021-04-29 12:34:26 +02:00
mankowsky_r bb9c3f7772 linking more general stuff in direction elements subpack. 2021-04-29 11:31:17 +02:00
mankowsky_r dba183d775 Fix GPS bernina 2021-04-29 11:29:37 +02:00
mankowsky_r 3b56841683 fix in xeye 2021-04-29 11:25:52 +02:00
mankowsky_r b5cabd7533 starting namespace more like assembly, continued on NDscan 2021-04-23 17:50:27 +02:00
mankowsky_r b00619e9e1 added archiver methods to all pv dependent objects 2021-04-23 11:18:54 +02:00
mankowsky_r e188009f4c moved epics daq stuff to bernina.py 2021-04-13 19:00:39 +02:00
mankowsky_r 3e86247dc6 memory to presets and targets in benrina tt_kb and prof_kb 2021-03-19 18:57:53 +01:00
mankowsky_r 35b0be080b amend? 2021-03-17 16:40:51 +01:00
mankowsky_r df0c1ff2b0 stuff again, sorry 2021-03-17 16:37:59 +01:00
mankowsky_r b06d6b7a22 fixes fixes fixes 2021-03-16 18:30:21 +01:00
mankowsky_r 6359dc1241 checker uodate 2021-03-02 23:21:05 +01:00
mankowsky_r 1a0d9a2dc8 gui addition to assembly, evr fixes 2021-03-02 18:11:03 +01:00
mankowsky_r c9a05fd6f9 gui addition to assembly, evr fixes 2021-03-02 18:04:26 +01:00
mankowsky_r 3b19638db1 timing system almost finalized 2021-03-02 12:30:58 +01:00
mankowsky_r 0d7f4f98a2 continue work on nd scan 2021-02-24 17:09:50 +01:00
mankowsky_r 5535890d63 fixed message posting in fel, added modes 2021-02-24 15:19:53 +01:00
mankowsky_r fd7e02b6c1 make scan return wait 2021-02-13 12:52:34 +01:00
mankowsky_r 3a956e979b added PV writing to the daq 2021-02-12 10:40:50 +01:00
mankowsky_r 36f5cf9287 new assembly settings collections 2021-02-05 22:42:54 +01:00
mankowsky_r b29772ca27 moved from bsread to bsdata in json files and fixed the kappa you conversion in xrd_you. 2021-02-03 07:37:59 +01:00
mankowsky_r fbc3e37cef added xrd in you convention 2021-02-01 18:58:56 +01:00
mankowsky_r ffeac3d253 run table fix 2021-02-01 14:56:37 +01:00
mankowsky_r ad9753025b gspread keys of run_table now stored together in config folder 2021-02-01 13:52:45 +01:00
mankowsky_r 0ebf5c8863 fixed intenity monitor calibration timeout issues 2021-01-29 12:47:40 +01:00
mankowsky_r 584ad2146e fixed daq and scan in bernina issue with component replacement) 2021-01-28 13:07:56 +01:00
mankowsky_r 91c3e63983 reorganisation in bernina 2021-01-26 14:19:13 +01:00
mankowsky_r 008edcb4ac added 2021-01-25 12:44:08 +01:00
mankowsky_r 780629ca11 added inline microscope bernina 2021-01-25 12:39:59 +01:00
mankowsky_r f2496916f1 fixed alias update issue in motors 2021-01-22 11:05:24 +01:00
mankowsky_r a6f2b9c101 Bug in memory recallstr, remove unlazy obj from bernina 2021-01-20 16:36:12 +01:00
mankowsky_r 10aae5cc3a switched to new way implementing and importing scopes. 2021-01-19 16:51:22 +01:00
mankowsky_r 45e659e14d getattr on module first addition 2021-01-13 16:10:47 +01:00
mankowsky_r 3e1c21ef73 few fixes 2021-01-13 16:06:54 +01:00
mankowsky_r 362328e060 added new namespace stuff 2021-01-13 13:11:22 +01:00
Henrik Lemke 09b3a66c56 forgit import 2021-01-08 21:15:09 +01:00
Henrik Lemke ebddfc1839 added base example, timg cam image terminal plotting. 2021-01-08 21:08:41 +01:00
mankowsky_r 229fc1a632 Merge branch 'bernina-op' of https://github.com/paulscherrerinstitute/eco into bernina-op 2021-01-06 15:49:15 +01:00
mankowsky_r 61b69f6d1b added module for ptz cams 2021-01-06 15:48:23 +01:00
Henrik Lemke 359ea457cb added kappa conversion for bernina 2020-12-17 16:56:40 +01:00
Henrik Lemke 44787676b6 added kappa conversion for bernina 2020-12-17 16:46:06 +01:00
Henrik Lemke d408819dcd added kappa conversion for bernina 2020-12-17 16:03:11 +01:00
Henrik Lemke a5f7137577 Merge branch 'bernina-op' of https://github.com/paulscherrerinstitute/eco into bernina-op 2020-12-17 15:10:22 +01:00
Henrik Lemke 321fe85da5 added kappa conversion for bernina 2020-12-17 15:10:01 +01:00
mankowsky_r 46f2407eab implemented check for valid filename in scans. Checks for space and / 2020-12-17 12:54:12 +01:00
mankowsky_r 746db73fc8 added the kappa conversion file 2020-12-17 11:34:59 +01:00
mankowsky_r 83ec86482b added kappa computing 2020-12-16 15:21:25 +01:00
mankowsky_r 0e5f220850 added FEL energy check of att_usd. The target transmission is recalculated if the x-ray energy is abov 1.5keV 2020-12-16 12:45:10 +01:00
mankowsky_r ad8a2cbf25 added get xy position to dsd 2020-12-05 22:33:26 +01:00
mankowsky_r 2e5dfce7aa fixes 2020-12-05 21:38:05 +01:00
mankowsky_r d3e9eded62 corrected mirror direction of tt_usd outcoupling 2020-12-04 10:19:42 +01:00
mankowsky_r b83fbe8598 added class CameraPCO, aded M5 spectrometer camera to tt_usd instance 2020-12-04 10:05:44 +01:00
mankowsky_r 06b73c5a87 different changes 2020-11-23 14:16:00 +01:00
mankowsky_r ac6774d4a4 pprm and other fixes on the new camera stuff 2020-11-20 12:15:30 +01:00
mankowsky_r 4d8ab90b79 added class to change camera server config for basler cameras 2020-11-19 15:58:46 +01:00
mankowsky_r ad10aa9a56 added name attribute to gps, modified runtable to use pvname attribute instead of old Id attribute 2020-11-19 11:48:29 +01:00
mankowsky_r ebf11f6e7a fix mono 2020-11-17 14:46:22 +01:00
mankowsky_r b45e05031b added a PV condition checker and waiter 2020-11-17 11:41:49 +01:00
mankowsky_r 37e9e75dd2 Added new implementation of smaract 2020-11-17 10:37:36 +01:00
mankowsky_r 6efb6e38c7 added automatic threaded upload of the run_table to gsheet, whenever a position is recorded 2020-11-13 16:16:49 +01:00
mankowsky_r 4107949d9c added det_diff 2020-10-18 21:32:40 +02:00
mankowsky_r 3f0a8560f7 implemented fel in bernina ecol 2020-10-12 23:30:54 +02:00
mankowsky_r 778e34ce8a MAde camera class work 2020-10-07 23:27:49 +02:00
mankowsky_r 5d83c86cd2 More stuff added to assembly, new MotorRecord. 2020-10-06 23:48:58 +02:00
mankowsky_r b8cb68164d Added new package elements, worked on assemblies 2020-10-06 21:07:43 +02:00
mankowsky_r 8030e1a279 added class with motors for single shot spectrometer 2020-09-15 15:06:51 +02:00
mankowsky_r 0b9eb59db1 added scan adjustable to run table metadata 2020-09-15 14:19:48 +02:00
mankowsky_r 2931dacf18 fixed bug in run_table collecting offset values 2020-09-10 12:57:50 +02:00
mankowsky_r e4062daa96 units are now saved with the run_table, also added metadata sending to dimas sf_daq 2020-09-10 09:32:22 +02:00
mankowsky_r b159f272bb pvs in ~/eco/configuration/run_table_channels_CA are now recorded with the run table 2020-09-03 15:05:23 +02:00
mankowsky_r 5bcefe5768 added curvature in KB and utilities_motor 2020-08-30 15:27:39 +02:00
mankowsky_r a3d9e79f9d fixes for Mankowsky experiment 2020-08-30 08:51:47 +02:00
mankowsky_r 9bb3d31393 added run table loading? 2020-07-05 09:55:20 +02:00
mankowsky_r d22cdeded7 xlt 2020-07-03 18:53:47 +02:00
mankowsky_r fbb2baf63b adding lxt based on epics panel 2020-07-03 17:10:49 +02:00
mankowsky_r 434c537011 att_usd can now be called directly and moves to the closest possible transmission value. It closes the xp, waits for the move and opens the xp. 2020-07-02 18:14:16 +02:00
mankowsky_r acb8b47a35 Clean up motor names 2020-07-02 15:59:47 +02:00
mankowsky_r 581a631d7b Beamtime rush 2020-06-30 15:49:59 +02:00
mankowsky_r 929c9f0072 making new daq to daq, fixing a bug for default counter in scans 2020-06-22 16:28:06 +02:00
mankowsky_r a5ec6c71b4 Added in the daq_new the option to add bs channels and camera channels 2020-06-18 17:05:23 +02:00
mankowsky_r 7df6c50504 adjusted the new daq to work with scans 2020-06-18 15:47:00 +02:00
mankowsky_r 89630cf9f8 Changed pulseid pv to bernina EVR 2020-06-17 11:40:39 +02:00
lemke_h 213ba72da5 Acquisition in new daq 2020-06-17 09:14:12 +02:00
mankowsky_r f53bf5a8c3 sampleenvironment,daq_new 2020-06-17 05:50:07 +02:00
mankowsky_r f72e965584 fixed bug in hexapod 2020-06-16 13:18:15 +02:00
mankowsky_r 499f791e44 Added new daq module 2020-06-16 13:14:49 +02:00
mankowsky_r 957dadcc83 refactored implementation of runtables in bernina 2020-06-15 13:40:55 +02:00
mankowsky_r e404d1e0bf Fixed bugs in epics_daq and bs_daq 2020-06-12 15:52:01 +02:00
mankowsky_r c09f970b54 lots of formatting and minor other changes 2020-06-10 17:59:32 +02:00
mankowsky_r 34b4ac4f58 added usd chamber 2020-06-10 17:11:56 +02:00
mankowsky_r 7ce508118a Merge branch 'bernina-op' of http://github.com/paulscherrerinstitute/eco into bernina-op 2020-06-10 17:02:53 +02:00
mankowsky_r dba390e227 added run_table, which is initialized automatically as rt. Added aliases to KBs, DCM, Aramis_attenuator 2020-06-10 17:01:50 +02:00
mankowsky_r 1b1b5a32bc fixed hexapod PI to not move simultaneously in virtual stages 2020-06-07 13:36:52 +02:00
mankowsky_r fb826d5863 added bsen object for bernina, changed usd_table offset back to zero 2020-06-07 06:49:45 +02:00
mankowsky_r b851b76452 changes bernina config slightly 2020-06-05 16:57:54 +02:00
mankowsky_r 8f6dcd52e2 Hexapod 2020-06-05 10:34:56 +02:00
mankowsky_r 10d57fa106 finalized kb_steering for the hexapod table in bernina 2020-05-27 18:47:45 +02:00
mankowsky_r a9805a630f implemented kb_steering in bernina 2020-05-26 23:27:54 +02:00
mankowsky_r e438dd736d added kb calculations start 2020-05-26 22:58:48 +02:00
mankowsky_r 0780421285 added symmetrie hexapod start 2020-05-19 17:55:24 +02:00
lemke_h 73b9afc05e added archiver module 2020-05-15 12:39:35 +02:00
lemke_h 7828ce7fe4 added archiver module 2020-05-15 12:39:03 +02:00
lemke_h 034be1f852 added tweak in motor 2020-05-14 22:16:54 +02:00
lemke_h f06b041baa added tweak in motor 2020-05-14 22:14:17 +02:00
lemke_h dc1a5c5afc Merge branch 'bernina-op' of https://github.com/paulscherrerinstitute/eco into bernina-op 2020-05-06 18:04:39 +02:00
lemke_h 28994476e5 make spec convention wait for Changers to finish/ raise errors always, started work on elog functionality at the same time 2020-05-06 18:03:47 +02:00
mankowsky_r 1658503716 fixed elog 2020-05-05 17:40:27 +02:00
lemke_h 6b76880a22 removed jedi autocompletion in startup_inline fr ipython 2020-05-05 17:26:51 +02:00
lemke_h 6f8483a672 waited for adjustables raise error now 2020-05-05 12:53:37 +02:00
mankowsky_r 0417e21623 first implementation of smaract slits 2020-05-05 12:00:03 +02:00
mankowsky_r d120169400 first implementation of smaract slits 2020-05-05 11:58:40 +02:00
mankowsky_r 87e80bf2a5 added a new slit from blades 2020-04-29 06:40:30 +02:00
mankowsky_r eb36cdb948 changes during STO inhouse 2020-04-08 12:14:51 +02:00
mankowsky_r 7bdd19185f Added th chamber and some sample env 2020-02-12 09:35:52 +01:00
mankowsky_r 16abb7a9e7 added restart dia client option 2020-01-30 18:55:35 +01:00
mankowsky_r ab2cffaa47 attenuator adjustable, dummy adjustable, scans can now acquire 2020-01-29 19:20:47 +01:00
mankowsky_r 7309897d5d attenuator adjustable, dummy adjustable, scans can now acquire 2020-01-29 19:20:29 +01:00
mankowsky_r 69ed54f7aa Changes for stages for December 2019 action inside Bernina 2020-01-21 12:35:03 +01:00
mankowsky_r 1479efcd59 added kappa to xrd in bernina 2019-11-11 18:18:47 +01:00
mankowsky_r 2a80fa11b2 starting to get rid of pyepics part in eco as it updated. 2019-10-30 15:40:27 +01:00
mankowsky_r 6c9cf4799c long missing commit 2019-10-28 13:12:07 +01:00
mankowsky_r 3f4ce1c2d4 new way to interact with timetool pipeline 2019-09-28 06:29:17 +02:00
mankowsky_r eb5408fb2d fixes during detector hell and for refactoring adjustables. 2019-09-24 16:01:55 +02:00
mankowsky_r 86dba672cd changed default freuency of pedestal to 25Hz to be compatible with 16M. added try except in loading virtual delay stage in bernina_experiment 2019-09-20 10:31:23 +02:00
mankowsky_r ed63e9860d Merge branch 'bernina-op' of https://github.com/paulscherrerinstitute/eco into bernina-op 2019-09-18 12:02:16 +02:00
mankowsky_r 60cf87fca0 hardcoded file adjustment in dia, added spectral and spatial encoders, added autocalibrate for monOpt in Bernina 2019-09-18 11:55:40 +02:00
Henrik Lemke 2156da1fc5 Refactored again the adjustable conventions 2019-09-16 11:03:17 +02:00
mankowsky_r c575f4e213 added spatial encoding 2019-09-16 08:47:31 +02:00
mankowsky_r 48a6047916 added spectral encoding 2019-09-16 08:23:55 +02:00
mankowsky_r ed9fd67504 added spectral encoding 2019-09-16 08:23:24 +02:00
mankowsky_r fa108f663a added spectral encoder for bernina 2019-09-15 16:59:30 +02:00
mankowsky_r c63f349e94 refactored and new startup again. 2019-09-05 18:14:17 +02:00
mankowsky_r 67e23a41f9 added startup script to be run inside ipython 2019-09-04 22:16:44 +02:00
mankowsky_r c16ac726cf fixes and epics string object 2019-09-04 19:00:46 +02:00
mankowsky_r 08fb028bd7 fixed ascanList, and added compact times for time stage repr 2019-07-21 10:30:57 +02:00
mankowsky_r f9bcfd0a24 Merge branch 'bernina-op' of https://github.com/paulscherrerinstitute/eco into bernina-op 2019-07-18 21:31:46 +02:00
mankowsky_r bf31e5c118 no writing of files any more when no filename defined in daq.acquire 2019-07-18 21:31:37 +02:00
mankowsky_r d9f3b201dd better print for checker in scans 2019-07-18 21:23:00 +02:00
mankowsky_r 6a16287bf4 added hex and some scans 2019-07-17 23:59:31 +02:00
mankowsky_r 222ea032ff added hexapod adjustables 2019-07-17 06:17:51 +02:00
lemke_h 8f99c1c9ca acquire for Pv Streams 2019-07-17 03:07:55 +02:00
lemke_h d936524c8a startup tweaks 2019-07-16 11:12:25 +02:00
mankowsky_r 0519848be7 event master and others 2019-07-16 11:11:27 +02:00
mankowsky_r 152debee61 Merge branch 'bernina-op' of https://github.com/paulscherrerinstitute/eco into bernina-op 2019-07-15 09:34:33 +02:00
lemke_h c953774c07 slits and bl 2019-07-15 09:34:08 +02:00
Dmitry Ozerov 5ae5df377f quick fix to have run_name (as filename) for statistics tab in visualisation 2019-07-12 15:32:49 +02:00
mankowsky_r 54ddb44003 fixes in startup and adjustables and bernina 2019-07-10 18:32:42 +02:00
lemke_h db4d04de07 added slit posgap 2019-07-09 12:32:09 +02:00
mankowsky_r 2fc0ce1c8d corrected delay stage directions in bernina 2019-07-04 21:51:42 +02:00
mankowsky_r d7313a0380 bugfix for the attenuator adjustable 2019-06-30 07:29:13 +02:00
mankowsky_r 727f4f338b first attempt to make attenuator adjustable, some name parameter in monochromator, not clear why this was needed. 2019-06-29 07:11:05 +02:00
mankowsky_r 071b119da7 Merge branch 'bernina-op' of https://github.com/paulscherrerinstitute/eco into bernina-op 2019-06-26 14:25:40 +02:00
lemke_h 89b3a3743d thread alive check fix in Changer 2019-06-26 14:24:37 +02:00
Henrik Lemke 8dd2ae357b adjusting requirements, more to be done 2019-06-26 08:19:28 +02:00
mankowsky_r 5cb941c775 upstream propagation in aliases included 2019-06-25 14:21:33 +02:00
mankowsky_r f78c661716 started camera object 2019-06-24 15:55:15 +02:00
mankowsky_r 46b16b5f3e Merge branch 'bernina-op' of https://github.com/paulscherrerinstitute/eco into bernina-op 2019-06-21 23:40:12 +02:00
mankowsky_r 6e2aa3acc5 hack for simultaneous collection of dia and bs dump 2019-06-21 23:39:48 +02:00
mankowsky_r c3d33118fc bug in motor set limits 2019-06-21 23:37:56 +02:00
mankowsky_r 86968da6da bugfixes 2019-06-21 22:10:16 +02:00
mankowsky_r 6d195db2cb reworking event timing, start 2019-06-21 14:59:38 +02:00
lemke_h 96eac615da typo fix 2019-06-07 12:32:37 +02:00
lemke_h ef9c47d3d4 fixed call method in spec convenience 2019-06-07 11:55:52 +02:00
mankowsky_r 68595906d4 reformatting 2019-06-07 10:46:30 +02:00
lemke_h a5c5f0b227 started working on better slits using adjustables 2019-06-07 10:44:54 +02:00
lemke_h 4e5d853930 added update moves, to MotorRecord so far. also added value callbacks there 2019-06-07 09:04:58 +02:00
lemke_h 0e089b6796 added range repr to MotorRecord 2019-06-06 23:05:04 +02:00
lemke_h af96e52685 started working on update change 2019-06-06 20:01:10 +02:00
mankowsky_r ca54d9d983 reformatted 2019-06-04 15:10:20 +02:00
mankowsky_r 140eeed9a5 added counters keyword to scan.Scans scans 2019-06-04 14:55:30 +02:00
lemke_h a4d18879ab fixed bsdaq 2019-06-04 14:43:08 +02:00
mankowsky_r 8d29532d57 fixed file naming in bs_daq 2019-06-04 14:39:36 +02:00
lemke_h 915ba4e837 Merge branch 'master' into bernina-op 2019-06-04 14:14:42 +02:00
lemke_h a38f6641a9 fixed bsdaq, finally added the screenpanel module 2019-06-04 14:12:26 +02:00
mankowsky_r efe64ae362 changing the lxtt directions 2019-06-04 10:26:17 +02:00
mankowsky_r c9c77338a9 added a2scan of 2 variables required for this experiment and added Id to delay stages, which is required for scans 2019-06-01 18:29:17 +02:00
lemke_h 344966ed1b Merge branch 'bernina-op' of https://github.com/paulscherrerinstitute/eco into bernina-op 2019-05-31 21:23:19 +02:00
lemke_h c1c30c15a2 fixed the bs_daq module, adjusted to way the new bsread is doing stuff, channellist in bernina was changed to default and default_bs 2019-05-31 21:22:26 +02:00
mankowsky_r ac717411af fix at bernina xrd 2019-05-31 21:16:38 +02:00
lemke_h ec6c29f496 adjusted implementation of cameras 2019-05-27 18:55:52 +02:00
lemke_h 2a83c49307 Merge branch 'bernina-op' 2019-05-27 18:20:45 +02:00
lemke_h cdc01437ff adjusted implementation of cameras, added an alias safe append_object_to_object method in aliases 2019-05-27 18:17:49 +02:00
lemke_h 3cb525c090 Merge branch 'bernina-op' 2019-05-26 21:12:58 +02:00
lemke_h dad5883cb2 Merge branch 'dev' 2019-05-26 21:12:28 +02:00
lemke_h 8c8cf3c0af some adjustments to new tools on profile monitors and intensity monitors 2019-05-26 11:33:50 +02:00
lemke_h 7c649a49e3 implemented compensated delay stage in bernina 2019-05-26 10:16:39 +02:00
lemke_h 0e58d93e0b added delay stages, fixed spec convenience bugs 2019-05-25 22:22:37 +02:00
lemke_h db2d083ab3 added new virtual adjustable and according new way to do delay stages. 2019-05-24 16:35:24 +02:00
“Roman 950bdc3381 added smar_config to config file. smaract stages are added to las by looping through the smar_config dict (smar_name,smar_address),e.g. {'thz_z':'-ESB1'} 2019-05-24 09:53:52 +02:00
“Roman caee42e77d XRD setup can now be configured in the config file and the components are loaded accordingly 2019-05-23 09:26:47 +02:00
“Roman 7d58422551 added default_file_path variable to dia, which is set to None (has no effetc but was missing and causing issues with file paths of the other DAQs). renamed ioxos_daq to epics_daq. changed epics_daq h5 file format to match bsread format for compatibility with escape. changed default folder for data and scan data of epics_daq to: epics_daq/ + scan_info/ and scan_data/ for scans. minor fixes of epics DAQ 2019-05-11 21:08:27 +02:00
lemke_h 0622849cd7 got bsdaq to work again and added to bernina config (with two channellists) 2019-05-10 15:27:41 +02:00
lemke_h 83ee606628 added fix to attenuator 2019-05-10 14:09:19 +02:00
lemke_h 52f4dc4b16 pulse picker extension 2019-05-08 15:43:58 +02:00
lemke_h f86494580b Merge branch 'dev' into bernina-op 2019-05-08 13:07:52 +02:00
lemke_h d69f6fcd20 fix bug in sequencer 2019-05-08 13:06:47 +02:00
“Roman fea189a551 fixed the directory permission assignment after creation of scan_info (chmod(775) -> chmod(0o775)) in scan.py 2019-05-07 19:14:34 +02:00
lemke_h 3e41e67387 Merge branch 'dev' into bernina-op 2019-05-06 21:11:24 +02:00
lemke_h 57c85922ca Merge branch 'dev' 2019-05-06 21:08:47 +02:00
lemke_h 0f7d8d6f05 bug in size determination PV stream 2019-05-06 21:06:55 +02:00
lemke_h 0756b28ac5 Added a Pv stream detector prototype, some cleanup 2019-05-06 09:03:58 +02:00
lemke_h 4009fd2126 added a enum PV adjustable 2019-05-03 16:35:39 +02:00
lemke_h ab4f5bcbd4 Merge branch 'dev' 2019-05-03 11:52:17 +02:00
lemke_h 5c9d57bc2b fixed merge conflicts with dev 2019-05-03 11:46:15 +02:00
lemke_h 86bfaf16dc merge to dev 2019-05-03 11:21:54 +02:00
lemke_h 90c1e7e0fb added timing master and cta to bernina 2019-05-03 09:35:58 +02:00
lemke_h c082d5c545 moved Pv adjustable to adjustable 2019-05-01 15:01:17 +02:00
lemke_h 78b4d2e45e fixed dia, update to bernins-op and fix in latest pedestal determination 2019-05-01 14:43:25 +02:00
lemke_h 9a2e5b75de fixed config bernina to new configuratoin object 2019-05-01 14:21:17 +02:00
lemke_h 691cbf8e86 little fixes, attenuator 2019-04-30 21:02:32 +02:00
lemke_h 6302c1d49e started on cta implementation 2019-04-30 15:53:19 +02:00
lemke_h 35c8bfa3b5 started on cta implementation 2019-04-30 10:40:39 +02:00
mankowsky_r e790ecc550 changed attenuator minimum energy 2019-03-19 16:13:10 +01:00
lemke_h a56e2f74d6 ipython config tab completion instead dummy class 2019-03-18 16:05:11 +01:00
mankowsky_r f46cfc2e06 fixes in automatic pedestal lookup 2019-03-18 15:57:24 +01:00
lemke_h 5bc60a0e3c test 2019-02-21 18:44:32 +01:00
lemke_h 0805bf8051 test 2019-02-21 18:42:09 +01:00
mankowsky_r 1bb61cc74a fixes and autopedestal find 2019-02-21 18:28:22 +01:00
mankowsky_r 91a44729ca dia address change 2019-02-14 23:13:15 +01:00
mankowsky_r 802fab812b fixes during pathak exp 2019-01-30 13:03:41 +01:00
mankowsky_r f27af35cff added devices_general.pv_adjustable, sigma class in endstations.bernina_cameras as well as cams142_C1 to the aliases 2019-01-25 18:05:21 +01:00
lemke_h 6797206dde Merge branch 'master' of https://github.com/paulscherrerinstitute/eco 2018-12-27 21:41:00 +01:00
lemke_h b8d4a0292b startup and more 2018-12-27 21:40:56 +01:00
mankowsky_r 7ffde547e5 Merge branch 'master' of https://github.com/paulscherrerinstitute/eco 2018-12-27 21:34:08 +01:00
mankowsky_r d0e6ac1034 added pulse picker 2018-12-27 21:29:30 +01:00
mankowsky_r 9ed1e522b6 bugfixes 2018-12-18 00:24:24 +01:00
lemke_h 2691e5feff evr little advances 2018-12-16 22:48:20 +01:00
lemke_h ddd749011c evr little advances 2018-12-16 22:45:57 +01:00
mankowsky_r 643170b6a5 fixed some stuff in dia, which was apparently cleaned from some older version. 2018-12-16 20:52:34 +01:00
mankowsky_r b7f101fff5 fixes in dia 2018-12-16 08:07:20 +01:00
mankowsky_r ad6a4c02ed DIA has moved to acquisition and been cleaned up a bit. 2018-12-13 20:05:08 +01:00
mankowsky_r 84ba4a0ff7 added checker 2018-12-13 07:52:52 +01:00
mankowsky_r ea92c1ae80 evr conditions 2018-12-12 18:12:14 +01:00
mankowsky_r 52699918e0 event timing first add 2018-12-12 17:49:10 +01:00
mankowsky_r 4b37b0687c event timing first add 2018-12-12 17:47:42 +01:00
mankowsky_r 4784acb462 scan upgrades 2018-12-11 07:05:51 +01:00
htlemke a08aff3707 Update Readme.md 2018-12-01 12:49:10 +01:00
htlemke 49bb77a8a9 Update Readme.md 2018-12-01 09:31:02 +01:00
mankowsky_r 9e7d92b928 started addind swissmx 2018-11-22 17:20:27 +01:00
mankowsky_r 1888676f73 added daq , but it is not complete 2018-11-15 11:00:28 +01:00
218 changed files with 57939 additions and 2095 deletions
+123
View File
@@ -0,0 +1,123 @@
# The default ``config.py``
# flake8: noqa
def set_prefs(prefs):
"""This function is called before opening the project"""
# Specify which files and folders to ignore in the project.
# Changes to ignored resources are not added to the history and
# VCSs. Also they are not returned in `Project.get_files()`.
# Note that ``?`` and ``*`` match all characters but slashes.
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
# '.svn': matches 'pkg/.svn' and all of its children
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
prefs["ignored_resources"] = [
"*.pyc",
"*~",
".ropeproject",
".hg",
".svn",
"_svn",
".git",
".tox",
]
# Specifies which files should be considered python files. It is
# useful when you have scripts inside your project. Only files
# ending with ``.py`` are considered to be python files by
# default.
# prefs['python_files'] = ['*.py']
# Custom source folders: By default rope searches the project
# for finding source folders (folders that should be searched
# for finding modules). You can add paths to that list. Note
# that rope guesses project source folders correctly most of the
# time; use this if you have any problems.
# The folders should be relative to project root and use '/' for
# separating folders regardless of the platform rope is running on.
# 'src/my_source_folder' for instance.
# prefs.add('source_folders', 'src')
# You can extend python path for looking up modules
# prefs.add('python_path', '~/python/')
# Should rope save object information or not.
prefs["save_objectdb"] = True
prefs["compress_objectdb"] = False
# If `True`, rope analyzes each module when it is being saved.
prefs["automatic_soa"] = True
# The depth of calls to follow in static object analysis
prefs["soa_followed_calls"] = 0
# If `False` when running modules or unit tests "dynamic object
# analysis" is turned off. This makes them much faster.
prefs["perform_doa"] = True
# Rope can check the validity of its object DB when running.
prefs["validate_objectdb"] = True
# How many undos to hold?
prefs["max_history_items"] = 32
# Shows whether to save history across sessions.
prefs["save_history"] = True
prefs["compress_history"] = False
# Set the number spaces used for indenting. According to
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's
# unit-tests use 4 spaces it is more reliable, too.
prefs["indent_size"] = 4
# Builtin and c-extension modules that are allowed to be imported
# and inspected by rope.
prefs["extension_modules"] = []
# Add all standard c-extensions to extension_modules list.
prefs["import_dynload_stdmods"] = True
# If `True` modules with syntax errors are considered to be empty.
# The default value is `False`; When `False` syntax errors raise
# `rope.base.exceptions.ModuleSyntaxError` exception.
prefs["ignore_syntax_errors"] = False
# If `True`, rope ignores unresolvable imports. Otherwise, they
# appear in the importing namespace.
prefs["ignore_bad_imports"] = False
# If `True`, rope will insert new module imports as
# `from <package> import <module>` by default.
prefs["prefer_module_from_imports"] = False
# If `True`, rope will transform a comma list of imports into
# multiple separate import statements when organizing
# imports.
prefs["split_imports"] = False
# If `True`, rope will remove all top-level import statements and
# reinsert them at the top of the module when making changes.
prefs["pull_imports_to_top"] = True
# If `True`, rope will sort imports alphabetically by module name instead
# of alphabetically by import statement, with from imports after normal
# imports.
prefs["sort_imports_alphabetically"] = False
# Location of implementation of
# rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
# case, you don't have to change this value, unless you're an rope expert.
# Change this value to inject you own implementations of interfaces
# listed in module rope.base.oi.type_hinting.providers.interfaces
# For example, you can add you own providers for Django Models, or disable
# the search type-hinting in a class hierarchy, etc.
prefs[
"type_hinting_factory"
] = "rope.base.oi.type_hinting.factory.default_type_hinting_factory"
def project_opened(project):
"""This function is called after opening the project"""
# Do whatever you like here!
BIN
View File
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
{
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.pythonPath": "/sf/bernina/applications/bm/envs/bernina38/bin/python",
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
},
"python.formatting.provider": "none"
}
+51 -4
View File
@@ -5,17 +5,64 @@
Experiment Control \__/\__/\___/
# Experiment Control
Python based control environment for experiments, developed at SwissFEL.
eco is a python based control environment for experiments, developed and used at SwissFEL, PSI.
It is supposed to be used as
- library of experimental devices for higher level python applications or GUIs
- interactive command line interface from e.g. ipython/jupyter shell or notebook.
## Structure
eco consists of mutiple python modules strucrured in main classes
Eco follows an object oriented approach to represent devices which can be passed around as a compatibility layer in python, This should facilitate to combine devices in general control and acquisition routines as well as to develop experimental routines which take advantage of the constantly growing landscape of scientific python libraries.
Examples for such object representation will follow in the documantation, for object-oriented programing in python also checkout online documentation like this [short introduction]{https://realpython.com/python3-object-oriented-programming/}.
## eco Elements
Eco consists in general terms of
1. conventions and examples for the behavior of general objects that allow to use them for different purposes.
2. library modules for broadly used devices using protocols (_e.g._ epics).
3. library modules for more specific, facility-dependent devices or logical assemblies of devices.
4. scopes of specific configurations of devices and scope-specific code, usable e.g. in interactive mode.
## Package Structure
eco consists of a hierachy of mutiple python modules.
At top level should be found:
- utilities (basic and convention helpers)
- basic devices
- examples
-- convention checkers
-- utilities
- specific types of devices
-- general definition of potentially recurring devices
-- separated into different groups
- configurations of multiple devices into instruments
[Device representation.pdf](https://github.com/paulscherrerinstitute/eco/files/2453401/Device.representation.pdf)
# Installation
## Anaconda
The eco package is available on [anaconda.org](https://anaconda.org/paulscherrerinstitute/eco) and can be installed as follows:
```bash
conda install -c paulscherrerinstitute eco
```
# HowTos
Please find in the following some general procedures when adding components in eco according to present conventions. This section can and should be dynamic, and may include outdated hint if not updated for longer.
## create new object ind eco
In order to help with naming, aliases, shell representation, new objects should be implemented as derived from `elements.Assembly` and call the parent init function with the name variable.
```python
from elements.assembly import Assembly
class Myobject(Assembly):
def __init__(self,name=None):
super().__init__(name=name)
```
The `Assembly` object has different methods that help to assemble different other eco objects together.
```python
# in
self._append(MySubObject,*args, **kwargs, name='mysubobjname', is_setting=True, is_display=True)
```
The `is_setting` flag requires that the appended object is an adjustable (can be set afterwards) or has adjustable settings in case it is itself an assembly.
The `is_status` flag independently determines if the subobject should be used to describe the status of the new assembly, e.g. show up in its representation. In case the subobject is no adjustable itself but has adjustable settings that should be shown in the object status, please use `is_display='recursive'`.
+3
View File
@@ -0,0 +1,3 @@
{
"python.formatting.provider": "black"
}
+9
View File
@@ -0,0 +1,9 @@
try:
from eco.elements.protocols import Adjustable, Detector, MonitorableValueUpdate
except:
print("cannot import Prototypic protocol classes")
from eco.elements.assembly import Assembly
from eco import defaults
+57 -14
View File
@@ -1,22 +1,28 @@
from bsread import Source
from bsread.h5 import receive
from bsread.h5 import receive, process_message_compact
from bsread.avail import dispatcher
import zmq
import mflow
import os
import data_api as api
import datetime
from threading import Thread
from pathlib import Path
from .utilities import Acquisition
class BStools:
def __init__(
self, default_channel_list={"listname": []}, default_file_path="%s", elog=None
self,
default_channel_list={"listname": []},
default_file_path="%s",
elog=None,
name=None,
):
self._default_file_path = default_file_path
self._default_channel_list = default_channel_list
self._elog = elog
self.name = name
def avail(self, *args, **kwargs):
return dispatcher.get_current_channels(*args, **kwargs)
@@ -60,10 +66,9 @@ class BStools:
N_pulses=None,
default_path=True,
queue_size=100,
compact_format=False,
):
if default_path:
fina = self._default_file_path % fina
N_pulses *= 1
if os.path.isfile(fina):
print("!!! File %s already exists, would you like to delete it?" % fina)
if input("(y/n)") == "y":
@@ -71,18 +76,42 @@ class BStools:
os.remove(fina)
else:
return
path_as_path = Path(fina)
if not path_as_path.parent.exists():
path_as_path.parent.mkdir()
if not channel_list:
print(
"No channels specified, using default list '%s' instead."
% list(self._default_channel_list.keys())[0]
)
channel_list = self._default_channel_list[
list(self._default_channel_list.keys())[0]
]
print("No channels specified, using all lists instead.")
channel_list = []
for tlist in self._default_channel_list.values():
channel_list.extend(tlist)
print(channel_list)
if compact_format:
message_processor = process_message_compact
else:
message_processor = None
source = dispatcher.request_stream(channel_list)
mode = zmq.SUB
receive(source, fina, queue_size=queue_size, mode=mode, n_messages=N_pulses)
try:
print(f"message proc is {message_processor}")
receive(
source,
fina,
queue_size=queue_size,
mode=mode,
n_messages=N_pulses,
message_processor=message_processor,
)
except KeyboardInterrupt:
# KeyboardInterrupt is thrown if the receiving is terminated via ctrl+c
# As we don't want to see a stacktrace then catch this exception
pass
finally:
print("Closing stream")
dispatcher.remove_stream(source)
def db(
self,
@@ -134,6 +163,20 @@ class BStools:
def acquire(self, file_name=None, Npulses=100):
file_name += ".h5"
# Npulses += 100
if self._default_file_path:
file_name = self._default_file_path % file_name
data_dir = Path(os.path.dirname(file_name))
if not data_dir.exists():
print(
f"Path {data_dir.absolute().as_posix()} does not exist, will try to create it..."
)
data_dir.mkdir(parents=True)
print(f"Tried to create {data_dir.absolute().as_posix()}")
data_dir.chmod(0o775)
print(f"Tried to change permissions to 775")
def acquire():
self.h5(fina=file_name, N_pulses=Npulses)
+148
View File
@@ -0,0 +1,148 @@
import time
from epics import PV
import numpy as np
from ..elements.adjustable import AdjustableFS
from ..epics.adjustable import AdjustablePv
from ..epics.detector import DetectorPvDataStream
from ..detector.detectors_psi import DetectorBsStream
from ..elements.assembly import Assembly
class CheckerCA(Assembly):
def __init__(
self,
pvname=None,
thresholds=None,
required_fraction=None,
filepath_thresholds="/sf/bernina/code/gac-bernina/eco_cnf_bernina/configuration/checker_thresholds.json",
filepath_fraction="/sf/bernina/code/gac-bernina/eco_cnf_bernina/configuration/checker_required_fraction.json",
name=None,
):
super().__init__(name=name)
self._append(DetectorPvDataStream, pvname, name="monitor")
self._append(
AdjustableFS,
filepath_thresholds,
default_value=sorted(thresholds),
name="thresholds",
)
self._append(
AdjustableFS,
filepath_fraction,
default_value=required_fraction,
name="required_fraction",
)
def check_now(self):
cv = self.monitor.get_current_value()
thresholds = self.thresholds()
if cv > thresholds[0] and cv < thresholds[1]:
return True
else:
return False
# def append_to_data(self, **kwargs):
# self.data.append(kwargs["value"])
def clear_and_start_counting(self):
self.monitor.accumulate_start()
# def stopcounting(self):
# self.PV.clear_callbacks()
def stop_and_analyze(self):
data = np.asarray(self.monitor.accumulate_stop())
thresholds = self.thresholds()
good = np.logical_and(data > thresholds[0], data < thresholds[1])
fraction = good.sum() / len(good)
isgood = fraction >= self.required_fraction()
if not isgood:
print(f"Checker: {fraction*100}% inside limits {self.thresholds()},")
print(f" given limit was {self.required_fraction()*100}%.")
return fraction >= self.required_fraction()
class CheckerBS(Assembly):
def __init__(
self,
bs_channel=None,
thresholds=None,
required_fraction=None,
filepath_thresholds="/sf/bernina/code/gac-bernina/eco_cnf_bernina/configuration/checker_thresholds.json",
filepath_fraction="/sf/bernina/code/gac-bernina/eco_cnf_bernina/configuration/checker_required_fraction.json",
name=None,
):
super().__init__(name=name)
self._append(DetectorBsStream, bs_channel, name="monitor")
self._append(
AdjustableFS,
filepath_thresholds,
default_value=sorted(thresholds),
name="thresholds",
)
self._append(
AdjustableFS,
filepath_fraction,
default_value=required_fraction,
name="required_fraction",
)
def check_now(self):
cv = None
while cv is None:
cv = self.monitor.get_current_value()
time.sleep(0.02)
thresholds = self.thresholds()
if cv > thresholds[0] and cv < thresholds[1]:
return True
else:
return False
# def append_to_data(self, **kwargs):
# self.data.append(kwargs["value"])
def clear_and_start_counting(self):
self.monitor.accumulate_start()
# def stopcounting(self):
# self.PV.clear_callbacks()
def stop_and_analyze(self):
try:
data = np.asarray(self.monitor.accumulate_stop())
thresholds = self.thresholds()
good = np.logical_and(data > thresholds[0], data < thresholds[1])
fraction = np.nansum(good) / len(good)
isgood = fraction >= self.required_fraction()
if not isgood:
print(f"Checker: {fraction*100}% inside limits {self.thresholds()},")
print(f" given limit was {self.required_fraction()*100}%.")
except:
return False
return fraction >= self.required_fraction()
# checker_obj = Checker_obj(checkerPV)
# checker_ready = {}
# checker_ready["checker_call"] = checker_function
# checker_ready["args"] = [[60, 700]]
# checker_ready["kwargs"] = {}
# checker_ready["wait_time"] = 3
# checker_init = {}
# checker_init["checker_call"] = checker_obj.clear_and_start_counting
# checker_init["args"] = []
# checker_init["kwargs"] = {}
# checker_init["wait_time"] = None
# checker_end = {}
# checker_end["checker_call"] = checker_obj.stop_and_analyze
# checker_end["args"] = [[60, 700], .7]
# checker_end["kwargs"] = {}
# checker_end["wait_time"] = None
+315
View File
@@ -0,0 +1,315 @@
import time
import weakref
from eco.acquisition.utilities import Acquisition
from eco.elements.protocols import Detector, MonitorableValueUpdate
from collections import namedtuple
from escape import ArrayTimestamps
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
from pathlib import Path
from datetime import datetime
from escape import DataSet
DEFAULT_STORAGE_DIR = Path("./")
StepTime = namedtuple("StepTime", "start stop")
class CounterValue:
def __init__(self, *detectors, name="value_counter"):
self.detectors = []
self.detector_values = []
self.monitorables = []
self.append_detectors(*detectors)
self.callbacks_start_scan = [self.start_scan]
self.callbacks_start_step = []
self.callbacks_step_counting = []
self.callbacks_end_step = [self.create_arrays, self.plot_arrays]
def stopani(scan, **kwargs):
scan.animation.event_source.stop()
self.callbacks_end_scan = [
self.create_arrays,
self.stop_monitoring,
self.clear_detectors,
stopani,
self.store_arrays,
]
self.name = name
def append_detectors(self, *detectors):
for detector in detectors:
if not isinstance(detector, MonitorableValueUpdate) and not isinstance(
detector, Detector
):
raise TypeError(
f"Expected Detector or MonitorableValueUpdate, got {type(detector)}"
)
if (
isinstance(detector, MonitorableValueUpdate)
and detector not in self.monitorables
):
self.monitorables.append(detector)
elif isinstance(detector, Detector) and detector not in self.detectors:
self.detectors.append(detector)
# self.detectors = detectors
def start_scan(self, scan=None, detectors=[], **kwargs):
self.append_detectors(*detectors)
scan.detector_values = []
scan.detector_names = self.get_detector_names()
self.start_monitoring(scan=scan)
scan.timestamp_intervals = []
# scan.moniitorable_names = self.get_monitorable_names()
def start_monitoring(self, scan=None, **kwargs):
monitors = [tm.set_current_value_callback() for tm in self.monitorables]
for tm in monitors:
tm.start()
if scan is not None:
scan.monitors = {
tn: tm for (tn, tm) in zip(self.get_monitorable_names(), monitors)
}
else:
self.monitors = monitors
def stop_monitoring(self, scan=None, **kwargs):
if scan is not None:
for tm in scan.monitors.values():
tm.stop()
del scan.monitors
else:
for tm in self.monitors:
tm.stop()
del self.monitors
def clear_detectors(self, scan, **kwargs):
for det in self.detectors:
tmpref = weakref.ref(det)
del det
if tmpref() is not None:
print(
f"Warning: Detector {tmpref().name} could not be deleted properly!"
)
def get_monitorable_names(self):
names = []
for tm in self.monitorables:
try:
names.append(tm.alias.get_full_name())
except:
names.append(tm.name)
return names
def get_detector_names(self):
names = []
for detector in self.detectors:
try:
names.append(detector.alias.get_full_name())
except:
names.append(detector.name)
return names
def get_detector_values(self):
detector_values = []
for detector in self.detectors:
try:
detector_values.append(detector.get_current_value())
except Exception as e:
print(f"Error getting value from {detector.name}: {e}")
detector_values.append(None)
return detector_values
def acquire(self, scan=None, collection_time=1.0, Npulses=None, **kwargs):
if Npulses is not None:
collection_time = Npulses
t_start = time.time()
acq_pars = {}
if scan:
scan_wr = weakref.ref(scan)
acq_pars = {
"scan_info": {
"scan_name": scan.description(),
"scan_values": scan.values_current_step,
"scan_readbacks": scan.readbacks_current_step,
"name": [adj.name for adj in scan.adjustables],
"expected_total_number_of_steps": scan.number_of_steps(),
"scan_step_info": {
"step_number": scan.next_step + 1,
},
},
}
acquisition = Acquisition(
acquire=None,
acquisition_kwargs={"Npulses": Npulses},
)
def acquire():
t_tmp = time.time()
det_val = self.get_detector_values()
scan_wr().detector_values.append(det_val)
time.sleep(collection_time - (time.time() - t_tmp))
t_stop = time.time()
scan_wr().timestamp_intervals.append(StepTime(t_tmp, t_stop))
acquisition.set_acquire_foo(acquire, hold=False)
return acquisition
def create_arrays(self, scan, **kwargs):
scan.monitor_scan_arrays = {}
for monname, mon in scan.monitors.items():
scan.monitor_scan_arrays[monname] = ArrayTimestamps(
data=mon.data["values"],
timestamps=mon.data["timestamps"],
timestamp_intervals=scan.timestamp_intervals,
parameter=parameter_from_scan(scan),
name=monname,
)
def plot_arrays(self, scan, **kwargs):
if not hasattr(scan, "animation"):
plt.close("CounterValue")
f, axs = plt.subplots(
len(scan.monitor_scan_arrays), 1, sharex=True, num="CounterValue"
)
scan.fig = f
if isinstance(axs, plt.Axes):
axs = [axs]
def plotdat(n, *args):
for ma, ax in zip(scan.monitor_scan_arrays.values(), axs):
ax.cla()
ma.scan.plot(axis=ax, fmt="o-")
scan.animation = FuncAnimation(
fig=f, func=plotdat, cache_frame_data=False, interval=500
)
plt.show(block=False)
else:
scan.fig.tight_layout()
scan.fig.canvas.draw()
scan.fig.canvas.flush_events()
def store_arrays(
self, scan, filename="auto", directory="auto", elog=None, **kwargs
):
if directory == "auto":
directory = DEFAULT_STORAGE_DIR
if callable(directory):
directory = directory()
directory = Path(directory)
if not directory.exists():
try:
directory.mkdir(parents=True)
except:
print(f"Warning: Could not create directory {directory.resolve()} !")
if filename == "auto":
filename = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") + ".esc.h5"
try:
d = DataSet.create_with_new_result_file(
Path(directory) / Path(filename), force_overwrite=False
)
names = []
for k, v in scan.monitor_scan_arrays.items():
names.append(k)
d.append(v, name=k)
v.store()
d.results_file.close()
scan.stored_filename = (
(Path(directory) / Path(filename)).resolve().as_posix()
)
print(
f"Stored filename {(Path(directory) / Path(filename)).resolve().as_posix()}"
)
d = DataSet.load_from_result_file(Path(directory) / Path(filename))
for name in names:
scan.monitor_scan_arrays[name] = d.datasets[name]
d.results_file.close()
except:
print("Could not create dataset file!")
files = []
try:
# import mpld3
plotfilename = Path(directory) / Path(
Path(filename).stem.split(".")[0] + ".png"
)
scan.fig.savefig(
plotfilename.as_posix(),
)
files.append(plotfilename)
# print(plotfilename, plotfilename.as_posix())
except Exception:
pass
files.append(Path(directory) / Path(filename))
if elog:
if elog == True:
elog = None
scan.status_to_elog(
text=f"### Quick scan: {scan.description()}\nData stored in {filename}.",
auto_title=False,
elog=elog,
files=files,
)
# TODO
def start(self):
pass
def stop(self):
pass
def parameter_from_scan(scan):
parameter = {
parname: {"values": [tvs[n] for tvs in scan.scan_info["scan_values"]]}
for n, parname in enumerate(
scan.scan_info["scan_parameters"]["name"],
)
}
return parameter
# class Monitor:
# def __init__(self, pvname, start_immediately=True):
# self.data = {}
# self.print = False
# self.pv = PV(pvname)
# self.cb_index = None
# if start_immediately:
# self.start_callback()
# def start_callback(self):
# self.cb_index = self.pv.add_callback(self.append)
# def stop_callback(self):
# self.pv.remove_callback(self.cb_index)
# def append(self, pvname=None, value=None, timestamp=None, **kwargs):
# if not (pvname in self.data):
# self.data[pvname] = []
# ts_local = time()
# self.data[pvname].append(
# {"value": value, "timestamp": timestamp, "timestamp_local": ts_local}
# )
# if self.print:
# print(
# f"{pvname}: {value}; time: {timestamp}; time_local: {ts_local}; diff: {ts_local-timestamp}"
# )
+601
View File
@@ -0,0 +1,601 @@
# likely never worked ...
# def _wait_for_tasks(scan, **kwargs):
# print("checking remaining tasks from previous scan ...")
# for task in scan.remaining_tasks:
# task.join()
# print("... done.")
import json
from pathlib import Path
import shutil
from threading import Thread
class CounterChecker:
def __init__(self):
# self.
if self.checker:
first_check = time()
checker_unhappy = False
while not self.checker.check_now():
print(
colorama.Fore.RED
+ f"Condition checker is not happy, waiting for OK conditions since {time()-first_check:5.1f} seconds."
+ colorama.Fore.RESET,
# end="\r",
)
sleep(self._checker_sleep_time)
checker_unhappy = True
if checker_unhappy:
print(
colorama.Fore.RED
+ f"Condition checker was not happy and waiting for {time()-first_check:5.1f} seconds."
+ colorama.Fore.RESET
)
self.checker.clear_and_start_counting()
class CounterStatusInitNamespaceToDAQ:
def __init__(self, namespace=None, daq=None):
self.namespace = namespace
self.daq = daq
self.callbacks_start_scan = []
self.callbacks_end_scan = []
self.callbacks_start_step = []
self.callbacks_end_step = []
def append_start_status_to_scan(self,scan=None, append_status_info=True):
if not append_status_info:
return
namespace_status = self.namespace.get_status(base=None)
stat = {"status_run_start": namespace_status}
scan.namespace_status = stat
def callback_start_step(self, scan=None, append_status_info=True):
pass
def callback_end_step(self, scan=None, append_status_info=True):
pass
def append_status_to_scan_and(self,
scan, append_status_info=True, end_scan=True, **kwargs
):
if not append_status_info:
return
if not len(scan.values_done)>0:
return
namespace_status = self.namespace.get_status(base=None)
scan.namespace_status["status_run_end"] = namespace_status
if hasattr(scan, "daq_run_number"):
runno = scan.daq_run_number
else:
runno = self.daq.get_last_run_number()
pgroup = self.daq.pgroup
tmpdir = Path(f"/sf/bernina/data/{pgroup}/res/run_data/daq/run{runno:04d}/aux")
tmpdir.mkdir(exist_ok=True, parents=True)
try:
tmpdir.chmod(0o775)
except:
pass
statusfile = tmpdir / Path("status.json")
if not statusfile.exists():
with open(statusfile, "w") as f:
json.dump(scan.namespace_status, f, sort_keys=True, cls=NumpyEncoder, indent=4)
else:
with open(statusfile, "r+") as f:
f.seek(0)
json.dump(scan.namespace_status, f, sort_keys=True, cls=NumpyEncoder, indent=4)
f.truncate()
print("Wrote status with seek truncate!")
if not statusfile.group() == statusfile.parent.group():
shutil.chown(statusfile, group=statusfile.parent.group())
response = self.daq.append_aux(
statusfile.resolve().as_posix(),
pgroup=pgroup,
run_number=runno,
)
print("####### transfer status #######")
print(response.json())
print("###############################")
scan.scan_info["scan_parameters"]["status"] = "aux/status.json"
class CounterAliasesToDAQ:
def __init__(self, namespace=None, daq=None):
self.namespace = namespace
self.daq = daq
def callback_start_scan(self, scan=None, append_status_info=True):
pass
def callback_end_step(self, scan=None, append_status_info=True):
pass
def callback_start_step(self, scan, force=False, **kwargs):
if force or (len(scan.values_done) == 1):
namespace_aliases = self.namespace.alias.get_all()
if hasattr(scan, "daq_run_number"):
runno = scan.daq_run_number
else:
runno = self.daq.get_last_run_number()
pgroup = self.daq.pgroup
tmpdir = Path(f"/sf/bernina/data/{pgroup}/res/tmp/aliases_run{runno:04d}")
tmpdir.mkdir(exist_ok=True, parents=True)
try:
tmpdir.chmod(0o775)
except:
pass
aliasfile = tmpdir / Path("aliases.json")
if not Path(aliasfile).exists():
with open(aliasfile, "w") as f:
json.dump(
namespace_aliases, f, sort_keys=True, cls=NumpyEncoder, indent=4
)
else:
with open(aliasfile, "r+") as f:
f.seek(0)
json.dump(
namespace_aliases, f, sort_keys=True, cls=NumpyEncoder, indent=4
)
f.truncate()
if not aliasfile.group() == aliasfile.parent.group():
shutil.chown(aliasfile, group=aliasfile.parent.group())
scan.remaining_tasks.append(
Thread(
target=daq.append_aux,
args=[aliasfile.resolve().as_posix()],
kwargs=dict(pgroup=pgroup, run_number=runno),
)
)
# DEBUG
print(
f"Sending scan_info_rel.json in {Path(aliasfile).parent.stem} to run number {runno}."
)
scan.remaining_tasks[-1].start()
# response = daq.append_aux(
# aliasfile.resolve().as_posix(),
# pgroup=pgroup,
# run_number=runno,
# )
print("####### transfer aliases started #######")
# print(response.json())
# print("################################")
scan.scan_info["scan_parameters"]["aliases"] = "aux/aliases.json"
def _message_end_scan(scan, **kwargs):
print(f"Finished run {scan.run_number}.")
if hasattr(scan, "daq_run_number"):
runno_daq_saved = scan.daq_run_number
print(f"daq_run_number is run {runno_daq_saved}.")
try:
runno = daq.get_last_run_number()
print(f"daq last run number is run {runno}.")
except:
pass
try:
e = pyttsx3.init()
e.say(f"Finished run {scan.run_number}.")
e.runAndWait()
e.stop()
except:
print("Audio output failed.")
# def _copy_scan_info_to_raw(scan, daq=daq):
# run_number = daq.get_last_run_number()
# pgroup = daq.pgroup
# print(f"Copying info file to run {run_number} to the raw directory of {pgroup}.")
# response = daq.append_aux(
# scan.scan_info_filename, pgroup=pgroup, run_number=run_number
# )
# print(f"Status: {response.json()['status']} Message: {response.json()['message']}")
def _create_general_run_info(scan, daq=daq, **kwargs):
with open(scan.scan_info_filename, "r") as f:
si = json.load(f)
info = {}
# general info, potentially automatically filled
info["general"] = {}
# individual data filled by daq/writers/user through api
info["start"] = {}
info["end"] = {}
info["steps"] = []
def _copy_scan_info_to_raw(scan, daq=daq, **kwargs):
t_start = time.time()
scan.writeScanInfo()
# get data that should come later from api or similar.
run_directory = list(
Path(f"/sf/bernina/data/{daq.pgroup}/raw").glob(f"run{scan.run_number:04d}*")
)[0].as_posix()
# with open(scan.scan_info_filename, "r") as f:
# si = json.load(f)
# Get scan info from scan
si = scan.scan_info
# correct some data in there (relative paths for now)
from os.path import relpath
newfiles = []
for files in si["scan_files"]:
newfiles.append([relpath(file, run_directory) for file in files])
si["scan_files"] = newfiles
# save temprary file and send then to raw
if hasattr(scan, "daq_run_number"):
runno = scan.daq_run_number
else:
runno = daq.get_last_run_number()
pgroup = daq.pgroup
tmpdir = Path(f"/sf/bernina/data/{pgroup}/res/run_data/daq/run{runno:04d}/aux")
tmpdir.mkdir(exist_ok=True, parents=True)
try:
tmpdir.chmod(0o775)
except:
pass
scaninfofile = tmpdir / Path("scan_info_rel.json")
if not Path(scaninfofile).exists():
with open(scaninfofile, "w") as f:
json.dump(si, f, sort_keys=True, cls=NumpyEncoder, indent=4)
else:
with open(scaninfofile, "r+") as f:
f.seek(0)
json.dump(si, f, sort_keys=True, cls=NumpyEncoder, indent=4)
f.truncate()
if not scaninfofile.group() == scaninfofile.parent.group():
shutil.chown(scaninfofile, group=scaninfofile.parent.group())
# print(f"Copying info file to run {runno} to the raw directory of {pgroup}.")
scan.remaining_tasks.append(
Thread(
target=daq.append_aux,
args=[scaninfofile.as_posix()],
kwargs=dict(pgroup=pgroup, run_number=runno),
)
)
# DEBUG
print(
f"Sending scan_info_rel.json in {Path(scaninfofile).parent.stem} to run number {runno}."
)
scan.remaining_tasks[-1].start()
# response = daq.append_aux(scaninfofile.as_posix(), pgroup=pgroup, run_number=runno)
# print(f"Status: {response.json()['status']} Message: {response.json()['message']}")
# print(
# f"--> creating and copying file took{time.time()-t_start} s, presently adding to deadtime."
# )
from eco.detector import Jungfrau
def _copy_selected_JF_pedestals_to_raw(
scan, daq=daq, copy_selected_JF_pedestals_to_raw=True, **kwargs
):
def copy_to_aux(daq, scan):
if hasattr(scan, "daq_run_number"):
runno = scan.daq_run_number
else:
runno = daq.get_last_run_number()
pgroup = daq.pgroup
for jf_id in daq.channels["channels_JF"]():
jf = Jungfrau(jf_id, name="noname", pgroup_adj=config_bernina.pgroup)
print(
f"Copying {jf_id} pedestal to run {runno} in the raw directory of {pgroup}."
)
response = daq.append_aux(
jf.get_present_pedestal_filename_in_run(intempdir=True),
pgroup=pgroup,
run_number=runno,
)
print(
f"Status: {response.json()['status']} Message: {response.json()['message']}"
)
print(
f"Copying {jf_id} gainmap to run {runno} in the raw directory of {pgroup}."
)
response = daq.append_aux(
jf.get_present_gain_filename_in_run(intempdir=True),
pgroup=pgroup,
run_number=runno,
)
print(
f"Status: {response.json()['status']} Message: {response.json()['message']}"
)
if copy_selected_JF_pedestals_to_raw:
scan.remaining_tasks.append(Thread(target=copy_to_aux, args=[daq, scan]))
scan.remaining_tasks[-1].start()
def _increment_daq_run_number(scan, daq=daq, **kwargs):
try:
daq_last_run_number = daq.get_last_run_number()
if int(scan.run_number) is int(daq_last_run_number) + 1:
print("############ incremented ##########")
daq_run_number = daq.get_next_run_number()
else:
daq_run_number = daq_last_run_number
if int(scan.run_number) is not int(daq_run_number):
print(
f"Difference in run number between eco {int(scan.run_number)} and daq {int(daq_run_number)}: using run number {int(scan.run_number)}"
)
if int(scan.run_number) > int(daq_run_number):
n = int(scan.run_number) - int(daq_run_number)
print("Increasing daq run_number")
for i in range(n):
rn = daq.get_next_run_number()
print(rn)
scan.daq_run_number = rn
else:
scan.daq_run_number = daq_run_number
except Exception as e:
print(e)
class Monitor:
def __init__(self, pvname, start_immediately=True):
self.data = {}
self.print = False
self.pv = PV(pvname)
self.cb_index = None
if start_immediately:
self.start_callback()
def start_callback(self):
self.cb_index = self.pv.add_callback(self.append)
def stop_callback(self):
self.pv.remove_callback(self.cb_index)
def append(self, pvname=None, value=None, timestamp=None, **kwargs):
if not (pvname in self.data):
self.data[pvname] = []
ts_local = time.time()
self.data[pvname].append(
{"value": value, "timestamp": timestamp, "timestamp_local": ts_local}
)
if self.print:
print(
f"{pvname}: {value}; time: {timestamp}; time_local: {ts_local}; diff: {ts_local-timestamp}"
)
import traceback
def append_scan_monitors(
scan,
daq=daq,
custom_monitors={},
**kwargs,
):
scan.monitors = {}
for adj in scan.adjustables:
try:
tname = adj.alias.get_full_name()
except Exception:
tname = adj.name
traceback.print_exc()
try:
scan.monitors[tname] = Monitor(adj.pvname)
except Exception:
print(f"Could not add CA monitor for {tname}")
traceback.print_exc()
try:
rname = adj.readback.alias.get_full_name()
except Exception:
print("no readback configured")
traceback.print_exc()
try:
scan.monitors[rname] = Monitor(adj.readback.pvname)
except Exception:
print(f"Could not add CA readback monitor for {tname}")
traceback.print_exc()
for tname, tobj in custom_monitors.items():
try:
if type(tobj) is str:
tmonpv = tobj
scan.monitors[tname] = Monitor(tmonpv)
print(f"Added custom monitor for {tname}")
except Exception:
print(f"Could not add custom monitor for {tname}")
traceback.print_exc()
try:
tname = daq.pulse_id.alias.get_full_name()
scan.monitors[tname] = Monitor(daq.pulse_id.pvname)
except Exception:
print(f"Could not add daq.pulse_id monitor")
traceback.print_exc()
def end_scan_monitors(scan, daq=daq, **kwargs):
for tmon in scan.monitors:
scan.monitors[tmon].stop_callback()
monitor_result = {tmon: scan.monitors[tmon].data for tmon in scan.monitors}
#######
# get data that should come later from api or similar.
run_directory = list(
Path(f"/sf/bernina/data/{daq.pgroup}/raw").glob(f"run{scan.run_number:04d}*")
)[0].as_posix()
# correct some data in there (relative paths for now)
from os.path import relpath
# save temprary file and send then to raw
if hasattr(scan, "daq_run_number"):
runno = scan.daq_run_number
else:
runno = daq.get_last_run_number()
pgroup = daq.pgroup
tmpdir = Path(f"/sf/bernina/data/{pgroup}/res/tmp/info_run{runno:04d}")
tmpdir.mkdir(exist_ok=True, parents=True)
try:
tmpdir.chmod(0o775)
except:
pass
scanmonitorfile = tmpdir / Path("scan_monitor.pkl")
if not Path(scanmonitorfile).exists():
with open(scanmonitorfile, "wb") as f:
pickle.dump(monitor_result, f)
print(f"Copying monitor file to run {runno} to the raw directory of {pgroup}.")
response = daq.append_aux(
scanmonitorfile.as_posix(), pgroup=pgroup, run_number=runno
)
print(f"Status: {response.json()['status']} Message: {response.json()['message']}")
# scan.monitors = None
def _init_all(scan, append_status_info=True, **kwargs):
if not append_status_info:
return
namespace.init_all(silent=False)
callbacks_start_scan = []
callbacks_start_scan.append(_init_all)
callbacks_start_scan.append(_wait_for_tasks)
callbacks_start_scan.append(_append_namesace_status_to_scan)
callbacks_start_scan.append(_increment_daq_run_number)
callbacks_start_scan.append(append_scan_monitors)
callbacks_end_step = []
callbacks_end_step.append(_copy_scan_info_to_raw)
callbacks_end_step.append(_write_namespace_aliases_to_scan)
callbacks_end_step.append(
lambda scan, daq=daq, namespace=namespace, append_status_info=True, end_scan=True, **kwargs: _write_namespace_status_to_scan(
scan,
daq=daq,
namespace=namespace,
append_status_info=append_status_info,
end_scan=False,
**kwargs,
)
)
callbacks_end_scan = []
callbacks_end_scan.append(_write_namespace_status_to_scan)
callbacks_end_scan.append(_copy_scan_info_to_raw)
callbacks_end_scan.append(
lambda scan, daq=daq, force=True, **kwargs: _write_namespace_aliases_to_scan(
scan, daq=daq, force=force, **kwargs
)
)
callbacks_end_scan.append(_copy_selected_JF_pedestals_to_raw)
callbacks_end_scan.append(end_scan_monitors)
callbacks_end_scan.append(_message_end_scan)
# >>>> Extract for run_table and elog
# if self._run_table or self._elog:
def _create_metadata_structure_start_scan(
scan, run_table=run_table, elog=elog, append_status_info=True, **kwargs
):
runname = os.path.basename(scan.fina).split(".")[0]
runno = int(runname.split("run")[1].split("_")[0])
metadata = {
"type": "scan",
"name": runname.split("_", 1)[1],
"scan_info_file": scan.scan_info_filename,
}
for n, adj in enumerate(scan.adjustables):
nname = None
nId = None
if hasattr(adj, "Id"):
nId = adj.Id
if hasattr(adj, "name"):
nname = adj.name
metadata.update(
{
f"scan_motor_{n}": nname,
f"from_motor_{n}": scan.values_todo[0][n],
f"to_motor_{n}": scan.values_todo[-1][n],
f"id_motor_{n}": nId,
}
)
if np.mean(np.diff(scan.pulses_per_step)) < 1:
pulses_per_step = scan.pulses_per_step[0]
else:
pulses_per_step = scan.pulses_per_step
metadata.update(
{
"steps": len(scan.values_todo),
"pulses_per_step": pulses_per_step,
"counters": [daq.name for daq in scan.counterCallers],
}
)
try:
try:
metadata.update({"scan_command": get_ipython().user_ns["In"][-1]})
except:
print("Count not retrieve ipython scan command!")
message_string = f"#### Run {runno}"
if metadata["name"]:
message_string += f': {metadata["name"]}\n'
else:
message_string += "\n"
if "scan_command" in metadata.keys():
message_string += "`" + metadata["scan_command"] + "`\n"
message_string += "`" + metadata["scan_info_file"] + "`\n"
elog_ids = elog.post(
message_string,
Title=f'Run {runno}: {metadata["name"]}',
text_encoding="markdown",
)
scan._elog_id = elog_ids[1]
metadata.update({"elog_message_id": scan._elog_id})
metadata.update(
{"elog_post_link": scan._elog.elogs[1]._log._url + str(scan._elog_id)}
)
except:
print("Elog posting failed with:")
traceback.print_exc()
if not append_status_info:
return
d = {}
## use values from status for run_table
try:
d = scan.status["status_run_start"]["status"]
except:
print("Tranferring values from status to run_table did not work")
t_start_rt = time.time()
try:
run_table.append_run(runno, metadata=metadata, d=d)
except:
print("WARNING: issue adding data to run table")
print(f"RT appending: {time.time()-t_start_rt:.3f} s")
# <<<< Extract for run table and elog
callbacks_start_scan.append(_create_metadata_structure_start_scan)
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
import weakref
from eco.acquisition.counters import CounterValue
# from eco.acquisition import scan
# from lazy_object_proxy import Proxy as LazyProxy
def scannable(Obj):
@property
def scans(self):
from eco.acquisition import scan # moved import here to avoid circular import
if hasattr(self, "_counter"):
if not hasattr(self, "_old_counters"):
self._old_counters = []
self._old_counters.append(weakref.ref(self._counter))
del self._counter
self._counter = CounterValue(self, name=self.alias.get_full_name())
if not hasattr(self, "_scans"):
self._scans = scan.Scans(default_counters=[self._counter])
else:
self._scans.default_counters = [self._counter]
return self._scans
Obj.scans = scans
return Obj
+372
View File
@@ -0,0 +1,372 @@
from time import sleep
from datetime import datetime
from ..acquisition.utilities import Acquisition
from detector_integration_api import DetectorIntegrationClient
import os
from pathlib import Path
import logging
logger = logging.getLogger(__name__)
class DIAClient:
def __init__(
self,
name=None,
instrument=None,
pgroup=None,
gain_path="",
pedestal_filename="",
pedestal_directory="",
api_address="http://sf-daq-2:10000",
jf_channels=[],
n_frames_default=100,
config_default=None,
default_file_path=None,
):
if config_default:
for cnf, cnfdict in config_default.items():
self.__dict__[cnf + "_config"] = cnfdict
else:
self.writer_config = {}
self.backend_config = {}
self.detector_config = {}
self.bsread_config = {}
self.name = name
self._default_file_path = default_file_path
self._api_address = api_address
self.client = DetectorIntegrationClient(api_address)
print("\nDetector Integration API on %s" % api_address)
if pgroup:
self.pgroup = int("".join([s for s in pgroup if s.isdigit()]))
else:
self.pgroup = None
self.n_frames = n_frames_default
self.jf_channels = jf_channels
self.pede_file = pedestal_filename
self.pedestal_directory = pedestal_directory
self.gain_path = gain_path
self.instrument = instrument
if instrument is None:
print("ERROR: please configure the instrument parameter in DIAClient")
self.update_config()
self.active_clients = list(self.get_active_clients()["clients_enabled"].keys())
self.jf_channels = list(x for x in self.active_clients if x != "bsread")
def update_config(
self,
):
# try:
self.get_last_pedestal()
# except:
# print('Did not find a pedestal file in %s'%(self.pedestal_directory))
self.writer_config.update(
{
"output_file": "/sf/%s/data/p%d/raw/test_data"
% (self.instrument, self.pgroup),
"user_id": self.pgroup,
"n_frames": self.n_frames,
"general/user": str(self.pgroup),
"general/process": __name__,
"general/created": str(datetime.now()),
"general/instrument": self.instrument,
}
)
self.backend_config.update(
{
"n_frames": self.n_frames,
"bit_depth": 16,
"gain_corrections_filename": self.gain_path,
# FIXME: HARDCODED!!!
"is_HG0": False,
}
)
if self.pede_file != "":
self.backend_config["gain_corrections_filename"] = self.gain_path
self.backend_config["gain_corrections_dataset"] = "gains"
self.backend_config["pede_corrections_filename"] = self.pede_file
self.backend_config["pede_corrections_dataset"] = "gains"
self.backend_config["pede_mask_dataset"] = "pixel_mask"
self.backend_config["activate_corrections_preview"] = True
else:
self.backend_config["pede_corrections_dataset"] = "gains"
self.backend_config["pede_mask_dataset"] = "pixel_mask"
self.backend_config["gain_corrections_filename"] = ""
self.backend_config["pede_corrections_filename"] = ""
self.backend_config["activate_corrections_preview"] = False
self.detector_config.update(
{
"timing": "trigger",
# FIXME: HARDCODED
"exptime": 0.000_005,
"cycles": self.n_frames,
# "delay" : 0.001992,
"frames": 1,
"dr": 16,
}
)
self.bsread_config.update(
{
"output_file": "/sf/%s/data/p%d/raw/test_bsread"
% (self.instrument, self.pgroup),
"user_id": self.pgroup,
"general/user": str(self.pgroup),
"general/process": __name__,
"general/created": str(datetime.now()),
"general/instrument": self.instrument,
}
)
# self.default_channels_list = jungfrau_utils.load_default_channel_list()
def reset(self):
self.client.reset()
# pass
def get_status(self):
return self.client.get_status()
def get_config(self):
config = self.client.get_config()
return config
def set_pgroup(self, pgroup):
self.pgroup = pgroup
self.update_config()
def set_bs_channels(
self,
):
print(
"Please update /sf/%s/config/com/channel_lists/default_channel_list and restart all services on the DAQ server"
% self.instrument
)
def set_config(self):
self.reset()
self.client.set_config(
{
"writer": self.writer_config,
"backend": self.backend_config,
"detector": self.detector_config,
"bsread": self.bsread_config,
}
)
def check_still_running(self, time_interval=0.5):
cfg = self.get_config()
running = True
while running:
if not self.get_status()["status"][-7:] == "RUNNING":
running = False
break
# elif not self.get_status()['status'][-20:]=='BSREAD_STILL_RUNNING':
# running = False
# break
else:
sleep(time_interval)
def take_pedestal(
self,
n_frames=1000,
analyze=True,
analyze_locally=False,
n_bad_modules=0,
freq=25,
):
from jungfrau_utils.scripts.jungfrau_run_pedestals import (
run as jungfrau_utils_run,
)
directory = "/sf/%s/data/p%d/raw/JF_pedestals/" % (self.instrument, self.pgroup)
res_dir = directory.replace("/raw/", "/res/")
if not os.path.exists(res_dir):
print("Directory %s not existing, creating it" % res_dir)
os.makedirs(res_dir)
os.chmod(res_dir, 0o775)
filename = "pedestal_%s" % datetime.now().strftime("%Y%m%d_%H%M")
period = 1 / freq
jungfrau_utils_run(
self._api_address,
filename,
directory,
self.pgroup,
period,
self.detector_config["exptime"],
n_frames,
1,
analyze_locally,
n_bad_modules,
self.instrument,
)
if analyze:
pedestals_taken = Path(directory).glob(filename + "*")
print(
"Analysis of pedestal data is outsourced to batch farm, user credentials required."
)
user = input("enter user name for analysis on sf batch farm: ")
commandstr = [
f"ssh {user}@sf-cn-1 source /sf/{self.instrument}/bin/anaconda_env"
]
for ped in pedestals_taken:
commandstr.append(
f"sbatch jungfrau_create_pedestals --filename {ped.as_posix()} --directory {res_dir}"
)
os.system("\;".join(commandstr))
def get_last_pedestal(self):
self.active_clients = list(self.get_active_clients()["clients_enabled"].keys())
self.jf_channels = list(x for x in self.active_clients if x != "bsread")
p = Path(self.pedestal_directory)
allpedestals = [
(
datetime.strptime(
f.stem.split("pedestal_")[1].split(".")[0], "%Y%m%d_%H%M"
),
f,
)
for f in p.glob("*.h5")
]
completepedestals = []
for pedtime in set([tt for tt, tf in allpedestals]):
tpedset = [pedtime, []]
try:
for channel in self.jf_channels:
tpedset[1].append(
[
tf
for tt, tf in allpedestals
if (tt == pedtime and channel in tf.as_posix())
][0]
)
if len(tpedset[1]) == len(self.jf_channels):
completepedestals.append(tpedset)
else:
print(
"Number of pedestal files %4f not number of JFs %4f"
% (len(tpedset[1]), len(self.jf_channels))
)
return
except:
pass
if len(completepedestals) > 0:
f = max(completepedestals)[1][0]
# dtim,f = max((datetime.strptime(f.stem.split('pedestal_')[1].split('.')[0],"%Y%m%d_%H%M"),f) for f in p.glob('*.h5'))
self.pede_file = (f.parent / Path(f.stem.split(".")[0])).as_posix()
def start(self):
self.client.start()
print("start acquisition")
pass
def stop(self):
self.client.stop()
print("stop acquisition")
pass
def config_and_start_test(self):
self.reset()
self.set_config()
self.start()
pass
def wait_for_status(self, *args, **kwargs):
return self.client.wait_for_status(*args, **kwargs)
def get_active_clients(self):
return self.client.get_clients_enabled()
def acquire(self, file_name=None, Npulses=100, JF_factor=1, bsread_padding=0):
"""
JF_factor?
bsread_padding?
"""
file_rootdir = "/sf/%s/data/p%d/raw/" % (self.instrument, self.pgroup)
if file_name is None:
# FIXME /dev/null crashes the data taking (h5py can't close /dev/null and crashes)
print("Not saving any data, as file_name is not set")
# file_name_JF = file_rootdir + "DelMe"
# file_name_bsread = file_rootdir + "DelMe"
file_name_JF = "/dev/null"
file_name_bsread = "/dev/null"
else:
# FIXME hardcoded
file_name_JF = file_rootdir + file_name
file_name_bsread = file_rootdir + file_name
if self.pgroup == 0:
raise ValueError("Please use set_pgroup() to set a pgroup value.")
def acquire():
self.n_frames = Npulses * JF_factor
self.update_config()
# self.detector_config.update({
# 'cycles': n_frames})
self.writer_config.update(
{
"output_file": file_name_JF,
# 'n_messages': n_frames
}
)
self.backend_config.update({"run_name": file_name_JF})
# 'n_frames': n_frames})
self.bsread_config.update(
{
"output_file": file_name_bsread,
# 'Npulses': Npulses + bsread_padding
}
)
self.reset()
self.set_config()
# print(self.get_config())
self.wait_for_status("IntegrationStatus.CONFIGURED")
self.client.start()
done = False
while not done:
stat = self.get_status()
if stat["status"] == "IntegrationStatus.FINISHED":
done = True
# if stat["status"] == "IntegrationStatus.BSREAD_STILL_RUNNING":
# done = True
# if stat["status"] == "IntegrationStatus.INITIALIZED":
# done = True
# if stat["status"] == "IntegrationStatus.DETECTOR_STOPPED":
# done = True
sleep(0.1)
self.client.stop()
outputfilenames = [
f"{file_name_JF}.{tcli.upper()}.h5"
for tcli in self.active_clients
+ [
"BSREAD.IMAGES"
] # ['BSREAD.h5_SARES20-CAMS142-M4','BSREAD.h5_SARES20-CAMS142-M5'] # DIRTY HACK
]
return Acquisition(
acquire=acquire,
acquisition_kwargs={"file_names": outputfilenames, "Npulses": Npulses},
hold=False,
)
def wait_done(self):
self.check_running()
self.check_still_running()
def reset_server(self, *args):
if not args:
args = ["all"]
os.system(
"ssh jf@sf-daq-3 -t -i ~/.ssh/daq3.key ./services.sh restart "
+ " ".join(args)
)
+463
View File
@@ -0,0 +1,463 @@
import numpy as np
import h5py
from epics import PV
import os
import datetime
from threading import Thread
from time import sleep
from pathlib import Path
from .utilities import Acquisition
import time
from ..elements.adjustable import AdjustableFS
from escape import ArrayTimestamps
class RunFilenameGenerator:
def __init__(self, path, prefix="run", Ndigits=4, separator="_", suffix="json"):
self.separator = separator
self.prefix = prefix
self.Ndigits = Ndigits
self.path = Path(path)
self.suffix = suffix
def get_existing_runnumbers(self):
fl = self.path.glob(
self.prefix + self.Ndigits * "[0-9]" + self.separator + "*." + self.suffix
)
fl = [tf for tf in fl if tf.is_file()]
runnos = [
int(tf.name.split(self.prefix)[1].split(self.separator)[0]) for tf in fl
]
return runnos
def get_run_info_file(self, runno):
fl = self.path.glob(
self.prefix
+ f"{runno:0{self.Ndigits}d}"
+ self.separator
+ "*."
+ self.suffix
)
fl = [tf for tf in fl if tf.is_file()]
if len(fl) > 1:
raise Exception(
f"Found multiple files in {self.path} with run number {runno}"
)
return fl[0]
def get_nextrun_number(self):
runnos = self.get_existing_runnumbers()
if runnos:
return max(runnos) + 1
else:
return 0
def get_nextrun_filename(self, name):
runnos = self.get_existing_runnumbers()
if runnos:
runno = max(runnos) + 1
else:
runno = 0
return (
self.prefix
+ "{{:0{:d}d}}".format(self.Ndigits).format(runno)
+ self.separator
+ name
+ "."
+ self.suffix
)
class EpicsDaq:
def __init__(
self,
elog=None,
name=None,
pgroup=None,
channel_list=None,
default_filepath=None,
):
self.name = name
self.pgroup = pgroup
self.alternative_file_path = AdjustableFS(
f"/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/{name}_alternative_file_path.json",
default_value=False,
name="alternative_file_path",
)
self._elog = elog
self.data = []
self.channels = {}
self.pulse_id = PV("SLAAR11-LTIM01-EVR0:RX-PULSEID")
self.channel_list = channel_list
self.update_channels()
self.callbacks_start_scan = [self.generate_run_number]
self.callbacks_start_step = []
self.callbacks_step_counting = []
self.callbacks_end_step = [self.write_scan_info]
self.callbacks_end_scan = [
self.create_arrays,
self.store_arrays,
]
@property
def _default_file_path(self):
if self.alternative_file_path() == False:
file_path = f"/sf/bernina/data/{self.pgroup()}/res/run_data/epics_daq/data/"
else:
file_path = self.alternative_file_path()
return file_path
@_default_file_path.setter
def _default_file_path(self, val):
self.alternative_file_path(val)
def update_channels(self):
channels = self.channel_list.get_current_value()
for channel in channels:
if not (channel in self.channels.keys()):
self.channels[channel] = PV(channel, auto_monitor=True)
def generate_run_number(self, scan, **kwargs):
os.glob(self._default_file_path)
def get_existing_runnumbers(self):
fl = self.path.glob(
self.prefix + self.Ndigits * "[0-9]" + self.separator + "*." + self.suffix
)
fl = [tf for tf in fl if tf.is_file()]
runnos = [
int(tf.name.split(self.prefix)[1].split(self.separator)[0]) for tf in fl
]
return runnos
channels = self.channel_list.get_current_value()
for channel in channels:
if not (channel in self.channels.keys()):
self.channels[channel] = PV(channel, auto_monitor=True)
def write_scan_info(self, scan, **kwargs):
if not Path(scan.scan_info_filename).exists():
with open(scan.scan_info_filename, "w") as f:
json.dump(scan.scan_info, f, sort_keys=True, cls=NumpyEncoder)
else:
with open(self.scan_info_filename, "r+") as f:
f.seek(0)
json.dump(self.scan_info, f, sort_keys=True, cls=NumpyEncoder)
f.truncate()
def h5(self, fina=None, channel_list=None, Npulses=None, queue_size=100):
if channel_list is None:
channel_list = self.channel_list
if not channel_list.get_current_value() == list(self.channels.keys()):
self.update_channels()
if os.path.isfile(fina):
print("!!! File %s already exists, would you like to delete it?" % fina)
if input("(y/n)") == "y":
print("Deleting %s ." % fina)
os.remove(fina)
else:
return
data = self.get_data(channel_list=None, Npulses=None, queue_size=100)
f = h5py.File(name=fina, mode="w")
for k in data.keys():
dat = f.create_group(name=k)
dat.create_dataset(name="data", data=data[k]["values"])
dat.create_dataset(name="timestamps", data=data[k]["timestamps"])
dat.create_dataset(
name="pulse_id", data=np.arange(Npulses) + round(time.time() * 100)
)
return data
def get_data(self, channel_list=None, Npulses=None, queue_size=100, **kwargs):
if channel_list is None:
channel_list = self.channel_list
if not channel_list.get_current_value() == list(self.channels.keys()):
self.update_channels()
data = {}
counters = {}
channels = self.channels
for k, channel in channels.items():
channelval = channel.value
if type(channelval) == np.ndarray:
shape = (Npulses,) + channelval.shape
dtype = channelval.dtype
else:
shape = (Npulses,)
dtype = type(channelval)
data[k] = {
"values": np.ndarray(
shape,
dtype=dtype,
),
"timestamps": np.ndarray(
(Npulses,),
dtype=float,
),
}
counters[k] = 0
def cb_getdata(ch=None, k="", *args, **kwargs):
data[k]["values"][counters[k]] = kwargs["value"]
data[k]["timestamps"][counters[k]] = kwargs["timestamp"]
counters[k] = counters[k] + 1
if counters[k] == Npulses:
ch.clear_callbacks()
for k, channel in channels.items():
channel.add_callback(callback=cb_getdata, ch=channel, k=k)
while True:
sleep(0.005)
if np.mean(list(counters.values())) == Npulses:
break
return data
# def acquire(self, Npulses=100, default_path=True, scan=None):
# file_name = scan._description
# file_name += ".h5"
# if default_path:
# file_name = self._default_file_path + file_name
# data_dir = Path(os.path.dirname(file_name))
# if not data_dir.exists():
# print(
# f"Path {data_dir.absolute().as_posix()} does not exist, will try to create it..."
# )
# data_dir.mkdir(parents=True)
# print(f"Tried to create {data_dir.absolute().as_posix()}")
# data_dir.chmod(0o775)
# print(f"Tried to change permissions to 775")
#
# def acquire():
# self.h5(fina=file_name, Npulses=Npulses)
# return Acquisition(
# acquire=acquire,
# acquisition_kwargs={"file_names": [file_name], "Npulses": Npulses},
# hold=False,
# )
def acquire(self, scan=None, Npulses=None, **kwargs):
acq_pars = {}
if scan:
scan_wr = weakref.ref(scan)
acq_pars = {
"scan_info": {
"scan_name": scan.description(),
"scan_values": scan.values_current_step,
"scan_readbacks": scan.readbacks_current_step,
"name": [adj.name for adj in scan.adjustables],
"expected_total_number_of_steps": scan.number_of_steps(),
"scan_step_info": {
"step_number": scan.next_step + 1,
},
},
}
acquisition = Acquisition(
acquire=None,
acquisition_kwargs={"Npulses": Npulses},
)
def acquire():
t_tmp = time.time()
data_step = self.get_data(Npulses)
scan_wr().data.append(data_step)
t_stop = time.time()
scan_wr().timestamp_intervals.append(StepTime(t_tmp, t_stop))
acquisition.set_acquire_foo(acquire, hold=False)
return acquisition
def create_arrays(self, scan, **kwargs):
scan.monitor_scan_arrays = {}
nsteps = len(scan.data)
for monname, mon in scan.monitors.items():
scan.monitor_scan_arrays[monname] = ArrayTimestamps(
data=mon.data["values"],
timestamps=mon.data["timestamps"],
timestamp_intervals=scan.timestamp_intervals,
parameter=parameter_from_scan(scan),
name=monname,
)
def store_arrays(
self, scan, filename="auto", directory="auto", elog=None, **kwargs
):
if directory == "auto":
directory = self._default_file_path
if not directory.exists():
try:
directory.mkdir(parents=True)
except:
print(f"Warning: Could not create directory {directory.resolve()} !")
if filename == "auto":
filename = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") + ".esc.h5"
try:
d = DataSet.create_with_new_result_file(
Path(directory) / Path(filename), force_overwrite=False
)
names = []
for k, v in scan.monitor_scan_arrays.items():
names.append(k)
d.append(v, name=k)
v.store()
d.results_file.close()
scan.stored_filename = (
(Path(directory) / Path(filename)).resolve().as_posix()
)
print(
f"Stored filename {(Path(directory) / Path(filename)).resolve().as_posix()}"
)
d = DataSet.load_from_result_file(Path(directory) / Path(filename))
for name in names:
scan.monitor_scan_arrays[name] = d.datasets[name]
except:
print("Could not create dataset file!")
files = []
try:
# import mpld3
plotfilename = Path(directory) / Path(
Path(filename).stem.split(".")[0] + ".png"
)
scan.fig.savefig(
plotfilename.as_posix(),
)
files.append(plotfilename)
# print(plotfilename, plotfilename.as_posix())
except Exception:
pass
files.append(Path(directory) / Path(filename))
if elog:
if elog == True:
elog = None
scan.status_to_elog(
text=f"### Quick scan: {scan.description()}\nData stored in {filename}.",
auto_title=False,
elog=elog,
files=files,
)
def wait_done(self):
self.check_running()
self.check_still_running()
class Epicstools:
def __init__(
self,
default_channel_list={"listname": []},
default_file_path="%s",
elog=None,
name=None,
channel_list=None,
):
self.name = name
self._default_file_path = default_file_path
self._default_channel_list = default_channel_list
self._elog = elog
self.channels = []
self.pulse_id = PV("SLAAR11-LTIM01-EVR0:RX-PULSEID")
if not channel_list:
print("No channels specified, using all lists instead.")
channel_list = []
for tlist in self._default_channel_list.values():
channel_list.extend(tlist)
else:
self.channel_list = channel_list
for channel in self.channel_list:
self.channels.append(PV(channel, auto_monitor=True))
def h5(self, fina=None, channel_list=None, Npulses=None, queue_size=100):
channel_list = self.channel_list
if os.path.isfile(fina):
print("!!! File %s already exists, would you like to delete it?" % fina)
if input("(y/n)") == "y":
print("Deleting %s ." % fina)
os.remove(fina)
else:
return
data = []
counters = []
channels = self.channels
for channel in channels:
channelval = channel.value
if type(channelval) == np.ndarray:
shape = (Npulses,) + channelval.shape
dtype = channelval.dtype
else:
shape = (Npulses,)
dtype = type(channelval)
data.append(np.ndarray(shape, dtype=dtype))
counters.append(0)
def cb_getdata(ch=None, m=0, *args, **kwargs):
data[m][counters[m]] = kwargs["value"]
counters[m] = counters[m] + 1
if counters[m] == Npulses:
ch.clear_callbacks()
for m, channel in enumerate(channels):
channel.add_callback(callback=cb_getdata, ch=channel, m=m)
while True:
sleep(0.005)
if np.mean(counters) == Npulses:
break
f = h5py.File(name=fina, mode="w")
for n, channel in enumerate(channel_list):
dat = f.create_group(name=channel)
dat.create_dataset(name="data", data=data[n])
dat.create_dataset(
name="pulse_id", data=np.arange(Npulses) + round(time.time() * 100)
)
return data
def acquire(self, file_name=None, Npulses=100, default_path=True):
file_name += ".h5"
if default_path:
file_name = self._default_file_path + file_name
data_dir = Path(os.path.dirname(file_name))
if not data_dir.exists():
print(
f"Path {data_dir.absolute().as_posix()} does not exist, will try to create it..."
)
data_dir.mkdir(parents=True)
print(f"Tried to create {data_dir.absolute().as_posix()}")
data_dir.chmod(0o775)
print(f"Tried to change permissions to 775")
def acquire():
self.h5(fina=file_name, Npulses=Npulses)
return Acquisition(
acquire=acquire,
acquisition_kwargs={"file_names": [file_name], "Npulses": Npulses},
hold=False,
)
def wait_done(self):
self.check_running()
self.check_still_running()
-112
View File
@@ -1,112 +0,0 @@
import numpy as np
import h5py
from epics import PV
import os
import data_api as api
import datetime
from threading import Thread
from time import sleep
from .utilities import Acquisition
class Ioxostools:
def __init__(
self,
default_channel_list={"listname": []},
default_file_path="%s",
elog=None,
sleeptime=0.0305,
channel_list=None,
):
self.sleeptime = sleeptime
self._default_file_path = default_file_path
self._default_channel_list = default_channel_list
self._elog = elog
self.channels = []
if not channel_list:
print(
"No channels specified, using default list '%s' instead."
% list(self._default_channel_list.keys())[0]
)
self.channel_list = self._default_channel_list[
list(self._default_channel_list.keys())[0]
]
else:
self.channel_list = channel_list
for channel in self.channel_list:
self.channels.append(PV(channel))
def h5(
self,
fina=None,
channel_list=None,
N_pulses=None,
default_path=True,
queue_size=100,
):
channel_list = self.channel_list
if default_path:
fina = self._default_file_path % fina
if os.path.isfile(fina):
print("!!! File %s already exists, would you like to delete it?" % fina)
if input("(y/n)") == "y":
print("Deleting %s ." % fina)
os.remove(fina)
else:
return
data = []
counters = []
channels = self.channels
for channel in channels:
channelval = channel.value
if type(channelval) == np.ndarray:
shape = (N_pulses,) + channelval.shape
dtype = channelval.dtype
else:
shape = (N_pulses,)
dtype = type(channelval)
data.append(np.ndarray(shape, dtype=dtype))
counters.append(0)
def cb_getdata(ch=None, m=0, *args, **kwargs):
data[m][counters[m]] = kwargs["value"]
counters[m] = counters[m] + 1
if counters[m] == N_pulses - 1:
ch.clear_callbacks()
for (m, channel) in enumerate(channels):
channel.add_callback(callback=cb_getdata, ch=channel, m=m)
while True:
sleep(0.01)
if np.mean(counters) == N_pulses - 1:
break
# for n in range(N_pulses):
# channelvals = []
# sleep(self.sleeptime)
f = h5py.File(name=fina, mode="w")
for (n, channel) in enumerate(channel_list):
f.create_dataset(name=channel, data=data[n])
return data
def acquire(self, file_name=None, Npulses=100):
file_name += ".h5"
def acquire():
self.h5(fina=file_name, N_pulses=Npulses)
return Acquisition(
acquire=acquire,
acquisition_kwargs={"file_names": [file_name], "Npulses": Npulses},
hold=False,
)
def wait_done(self):
self.check_running()
self.check_still_running()
+1161 -148
View File
File diff suppressed because it is too large Load Diff
+256
View File
@@ -0,0 +1,256 @@
from numbers import Number
import os
from pathlib import Path
from escape.swissfel import load_dataset_from_scan
# from eco.elements.assembly import Assembly
import json
from pandas import DataFrame
class RunData:
def __init__(
self,
pgroup_adj,
path_search="/sf/bernina/data/{pgroup:s}/raw",
load_kwargs={},
name="",
):
# super().__init__(name=name)
# self._append(pgroup_adj, name="pgroup")
self.pgroup = pgroup_adj
self.path_search = path_search
self.load_kwargs = load_kwargs
self.loaded_runs = {}
def get_available_run_numbers(self):
pgroup = self.pgroup.get_current_value()
p = Path(self.path_search.format(pgroup=pgroup))
runs = []
for tp in p.iterdir():
if not tp.is_dir():
continue
if tp.name[:3] == "run":
numstring = tp.name.split("run")[1]
if numstring.isdecimal():
runs.append(int(numstring))
runs.sort()
return runs
def load_run(self, run_number, **kwargs):
if run_number < 0:
run_number = self.get_available_run_numbers()[run_number]
print(f"Loading run number {run_number}")
tkwargs = self.load_kwargs.copy()
tkwargs.update(kwargs)
tks = {}
for tk, tv in tkwargs.items():
if type(tv) is str:
tv = tv.format(pgroup=self.pgroup.get_current_value())
tks[tk] = tv
trun = load_dataset_from_scan(
pgroup=self.pgroup.get_current_value(), run_numbers=[run_number], **tks
)
###
# self.adjust_group()
self.loaded_runs[run_number] = {"dataset": trun}
self.__setattr__(f"run{run_number:04d}", trun)
return trun
# def __dir__(self):
# l = [
# "get_available_run_numbers",
# "get_run",
# "load_kwargs",
# "load_run",
# "loaded_runs",
# "path_search",
# "pgroup",
# ]
# # l = dir(self)
# l += [f"run{runno:04d}" for runno in self.get_available_run_numbers()]
# return l
# def __getattribute__(self, name):
# if name in [f"run{runno:04d}" for runno in self.get_available_run_numbers()]:
# return self.get_run(int(name.split("run")[1]))
# else:
# return getattr(self, name)
def adjust_group(self, subdir_type="scratch/.escape_parse_result"):
os.system(
"chgrp -R "
+ self.pgroup.get_current_value()[1:]
+ f" /sf/bernina/data/{self.pgroup.get_current_value()}/{subdir_type}"
)
def get_run(self, run_number, **kwargs):
if run_number < 0:
run_number = self.get_available_run_numbers()[run_number]
print(f"Finding run number {run_number}")
if run_number in self.loaded_runs.keys():
return self.loaded_runs[run_number]["dataset"]
else:
return self.load_run(run_number, **kwargs)
def __getitem__(self, run_number):
return self.get_run(run_number)
def __repr__(self):
s = "<%s.%s object at %s>" % (
self.__class__.__module__,
self.__class__.__name__,
hex(id(self)),
)
runnos = self.get_available_run_numbers()
s += "\n"
s += f"{len(runnos)} available from {min(runnos)} to {max(runnos)}."
return s
STATUS_DATA = {}
class StatusData:
def __init__(
self,
pgroup_adj,
path_search="/sf/bernina/data/{pgroup:s}/raw",
status_search="aux/status.json",
load_kwargs={},
name="",
):
# super().__init__(name=name)
# self._append(pgroup_adj, name="pgroup")
self.pgroup = pgroup_adj
self.path_search = path_search
self.status_search = status_search
self.load_kwargs = load_kwargs
self.loaded_statii = {}
STATUS_DATA[self.pgroup.get_current_value()] = self
def get_available_run_numbers(self):
pgroup = self.pgroup.get_current_value()
p = Path(self.path_search.format(pgroup=pgroup))
runs = []
for tp in p.iterdir():
if not tp.is_dir():
continue
if tp.name[:3] == "run":
numstring = tp.name.split("run")[1]
if numstring.isdecimal():
if (tp / Path(self.status_search)).exists():
runs.append(int(numstring))
runs.sort()
return runs
def get_run_status(self, run_number, **kwargs):
if run_number < 0:
run_number = self.get_available_run_numbers()[run_number]
print(f"Finding run number {run_number}")
if run_number in self.loaded_statii.keys():
return self.loaded_statii[run_number]
else:
return self.load_run_status(run_number, **kwargs)
def load_run_status(self, run_number, **kwargs):
if run_number < 0:
run_number = self.get_available_run_numbers()[run_number]
print(f"Loading run number {run_number}")
tkwargs = self.load_kwargs
tkwargs.update(kwargs)
tks = {}
for tk, tv in tkwargs.items():
if type(tv) is str:
tv = tv.format(pgroup=self.pgroup.get_current_value())
tks[tk] = tv
pgroup = self.pgroup.get_current_value()
with open(
Path(self.path_search.format(pgroup=pgroup))
/ Path(f"run{run_number:04d}/aux/status.json"),
"r",
) as fh:
r = json.load(fh)
self.loaded_statii[run_number] = r
return r
def run_status_convenience(Obj):
# if not hasattr(Obj, "alias"):
# return Obj
def run_status(
self,
run_number=None,
par_type="status",
force_reload=False,
pgroup="auto",
status_type="status_run_start",
as_dataframe=False,
):
if pgroup == "auto":
pgroup = list(STATUS_DATA.keys())[0]
if run_number is None:
return STATUS_DATA[pgroup].get_available_run_numbers()
if isinstance(run_number, Number):
run_number = [run_number]
nam = self.alias.get_full_name()
stat = {}
for runno in run_number:
if runno < 0:
runno = STATUS_DATA[pgroup].get_available_run_numbers()[runno]
if force_reload:
dic = STATUS_DATA[pgroup].load_run_status(runno)[status_type][par_type]
else:
dic = STATUS_DATA[pgroup].get_run_status(runno)[status_type][par_type]
dic = {
"".join(k.split(nam + ".")[1:]): v
for k, v in dic.items()
if k.startswith(nam)
}
stat[runno] = dic
if as_dataframe:
return DataFrame.from_dict(stat)
return stat
Obj.run_status = run_status
def apply_run_settings(
self,
run_number=None,
par_type="settings",
force_reload=False,
pgroup="auto",
status_type="status_run_start",
as_dataframe=False,
**kwargs,
):
stat = self.run_status(
run_number=run_number,
par_type=par_type,
force_reload=force_reload,
pgroup=pgroup,
status_type=status_type,
as_dataframe=as_dataframe,
)
run_number = list(stat.keys())
if not len(run_number) == 1:
raise Exception("Cannot apply mutiple settings")
run_number = run_number[0]
stat = stat[run_number]
self.memory.recall(input_obj=dict(settings=stat), **kwargs)
Obj.apply_run_settings = apply_run_settings
return Obj
+95
View File
@@ -0,0 +1,95 @@
from .scan import StepScan
import numpy as np
from numpy.random import RandomState
# class Scan:
# def __init__(
# self,
# adjustables,
# values,
# counterCallers,
# fina,
# Npulses=100,
# basepath="",
# scan_info_dir="",
# checker=None,
# scan_directories=False,
# callbackStartStep=None,
# checker_sleep_time=0.2,
# return_at_end="question",
# run_table=None,
# elog=None,
# ):
class ScanND:
def __init__(
self,
adjustables,
arrays,
counters,
fina,
Npulses=100,
basepath="",
scan_info_dir="",
checker=None,
scan_directories=False,
cb_start_scan=None,
cb_start_step=None,
cb_end_step=None,
cb_end_scan=None,
checker_sleep_time=0.2,
return_at_end="question",
run_table=None,
elog=None,
):
scan_array = []
scan_adjustables = []
for n_dim, (adj_tdim, arr_tdim) in enumerate(zip(adjustables, arrays)):
# check if the dimension is a multi adjustable thing
try:
iter(adj_tdim)
if not len(adj_tdim) == len(arr_tdim):
raise Exception(
f"arrays and adjusbles in dimension {n_dim}don't match"
)
if len(set([len(sarr) for sarr in arr_tdim])) > 1:
raise Exception(
f"subarrays in dimension {n_dim} do not have equal length!"
)
scan_array.append(tuple(arr_tdim))
scan_adjustables.append(tuple(adj_tdim))
except TypeError:
scan_array.append(tuple([arr_tdim]))
scan_adjustables.append(tuple([adj_tdim]))
self.scan_adjustables = scan_adjustables
self.scan_array = scan_array
self.scan_dimension = n_dim + 1
@property
def steps_total(self):
return np.prod([len(ta[0]) for ta in self.scan_array])
@property
def shape(self):
return tuple([len(ta[0]) for ta in self.scan_array])
def create_stepping_order(self, order="C"):
return [
tuple(te)
for te in np.vstack(
np.unravel_index(np.arange(self.steps_total), self.shape, order=order)
).T
]
def create_random_selection(
self,
N_elements=None,
scan_percentage=None,
random_type=equal,
sort_dimensions=False,
):
rs = RandomState(seed=0)
rs.choice(a, 5, p=np.exp(-a) / sum(np.exp(-a)), replace=False)
+74 -6
View File
@@ -1,20 +1,35 @@
from threading import Thread
from ..utilities import PropagatingThread
from epics import PV
from asyncio import Future
class Acquisition:
def __init__(
self, parent=None, acquire=None, acquisition_kwargs={}, hold=True, stopper=None
self,
parent=None,
acquire=lambda: None,
acquisition_kwargs={},
hold=True,
stopper=None,
get_result=lambda: None,
):
self.acquisition_kwargs = acquisition_kwargs
self.file_names = acquisition_kwargs["file_names"]
self._acquire = acquire
for key, val in acquisition_kwargs.items():
self.__dict__[key] = val
self._stopper = stopper
self._thread = Thread(target=self._acquire)
self._get_result = get_result
if acquire:
self.set_acquire_foo(acquire, hold=hold)
def set_acquire_foo(self, acquire, hold=True):
self._acquire = acquire
self._thread = PropagatingThread(target=self._acquire)
if not hold:
self._thread.start()
def wait(self):
self._thread.join()
return self._get_result()
def start(self):
self._thread.start()
@@ -23,10 +38,63 @@ class Acquisition:
if self._thread.ident is None:
return "waiting"
else:
if self._thread.isAlive():
if self._thread.is_alive():
return "acquiring"
else:
return "done"
def stop(self):
self._stopper()
def getPVchecker(pvname, config_file=None):
checkerPV = PV(pvname)
def checker_function(limits):
cv = checkerPV.get()
if cv > limits[0] and cv < limits[1]:
return True
else:
return False
checker = {}
checker["checker_call"] = checker_function
checker["args"] = [[100, 700]]
checker["kwargs"] = {}
checker["wait_time"] = 3
return checker
def checker_function(limits):
cv = checkerPV.get()
if cv > limits[0] and cv < limits[1]:
return True
else:
print(f"Gas detector intensity {cv} outside limits {limits} !")
return False
class Checker_obj:
def __init__(self, PV):
self.PV = PV
self.data = []
def append_to_data(self, **kwargs):
self.data.append(kwargs["value"])
def clear_and_start_counting(self):
self.data = []
self.PV.add_callback(self.append_to_data)
def stopcounting(self):
self.PV.clear_callbacks()
def stop_and_analyze(self, limits, fraction_min):
self.stopcounting()
data = np.asarray(self.data)
good = np.logical_and(data > np.min(limits), data < np.max(limits))
fraction = good.sum() / len(good)
print(f"Gas detector intensity was {fraction*100}% inside limits {limits},")
print(f"given limit was {fraction_min*100}%.")
return fraction >= fraction_min
-31
View File
@@ -1,31 +0,0 @@
class Adjustable:
def __init__(self, f_get_value, f_change_value, f_set_value_to=None):
self._f_get_value = f_get_value
self._f_change_value = f_change_value
self._f_set_value_to = f_set_value_to
def wrap_spec_convenience(obj):
# spec-inspired convenience methods
def mv(self, value):
self._currentChange = self.changeTo(value)
def wm(self, *args, **kwargs):
return self.get_current_value(*args, **kwargs)
def mvr(self, value, *args, **kwargs):
if self.get_moveDone == 1:
startvalue = self.get_current_value(readback=True, *args, **kwargs)
else:
startvalue = self.get_current_value(readback=False, *args, **kwargs)
self._currentChange = self.changeTo(value + startvalue, *args, **kwargs)
def wait(self):
self._currentChange.wait()
obj.wm = wm
obj.mv = mv
obj.mvr = mvr
obj.wait = wait
+63 -10
View File
@@ -1,14 +1,18 @@
import os
from pathlib import Path
import json
import logging
logger = logging.getLogger(__name__)
class Alias:
def __init__(self, alias, channel=None, channeltype=None):
def __init__(self, alias, channel=None, channeltype=None, parent=None):
self.alias = alias
self.channel = channel
self.channeltype = channeltype
self.children = []
self.parent = parent
def append(self, subalias):
assert type(subalias) is Alias, "You can only append aliases to aliases!"
@@ -16,8 +20,20 @@ class Alias:
subalias.alias in [tc.alias for tc in self.children]
), f"Alias {subalias.alias} exists already!"
self.children.append(subalias)
if subalias.parent is None:
subalias.parent = self
else:
print(subalias.parent)
logger.warning(
f"parent of alias {subalias.alias} has been defined already {subalias.parent.alias}."
)
def get_all(self,joiner='.'):
def pop_object(self, obj):
i = self.children.index(obj)
o = self.children.pop(i)
o.parent = None
def get_all(self, joiner=".", channeltypes=None):
aa = []
if self.channel:
ta = {}
@@ -25,14 +41,42 @@ class Alias:
ta["channel"] = self.channel
if self.channeltype:
ta["channeltype"] = self.channeltype
aa.append(ta)
if (not channeltypes) or (ta["channeltype"] in channeltypes):
aa.append(ta)
if self.children:
for tc in self.children:
taa = tc.get_all()
for ta in taa:
aa.append({'alias':joiner.join([self.alias,ta['alias']]),'channel':ta['channel'], 'channeltype':ta['channeltype']})
if (not channeltypes) or (ta["channeltype"] in channeltypes):
aa.append(
{
"alias": joiner.join([self.alias, ta["alias"]]),
"channel": ta["channel"],
"channeltype": ta["channeltype"],
}
)
return aa
def get_full_name(self, base=None, joiner="."):
"""allembles full name with parent names down to base (is supplied). Joiner is the separator between the hirarchical names."""
if (not (base is None)) and (self is base.alias):
name = []
return ""
else:
name = [self.alias]
parent = self.parent
while not parent == None:
if (not (base is None) and (parent is base.alias)) or (parent is None):
break
name.append(parent.alias)
parent = parent.__dict__.get("parent", None)
if joiner:
return joiner.join(reversed(name))
else:
return name
# def add_children(self, *args):
# self.children.append(find_aliases(*args))
@@ -47,6 +91,14 @@ def find_aliases(*args):
return tuple(o)
def append_object_to_object(obj_target, obj_init, *args, name=None, **kwargs):
"""append a new object to another object together with the alias.
The new object needs to be defined with a name keyword. Theadditional
args and kwargs are the expected input for the new opject."""
obj_target.__dict__[name] = obj_init(*args, **kwargs, name=name)
obj_target.alias.append(obj_target.__dict__[name].alias)
class Namespace:
def __init__(self, namespace_file=None):
path = Path(namespace_file)
@@ -63,19 +115,19 @@ class Namespace:
@property
def aliases(self):
if not self.data:
if self.data is None:
self.read_file()
return [td["alias"] for td in self.data]
@property
def channels(self):
if not self.data:
if self.data is None:
self.read_file()
return [td["channel"] for td in self.data]
def update(self, alias, channel, channeltype):
assert not alias in self.aliases, "Duplicate alias {alias} found!"
assert not channel in self.channels, "Duplicate channel {channel} found!"
assert not alias in self.aliases, f"Duplicate alias {alias} found!"
# assert not channel in self.channels, f"Duplicate channel {channel} found!"
self.data.append(
{"alias": alias, "channel": channel, "channeltype": channeltype}
)
@@ -89,7 +141,9 @@ class Namespace:
def get_info(self, alias=None, channel=None):
assert alias or channel, "Either search alias or channel needs to be defined!"
assert not(alias and channel), "Only either search alias or channel can be defined"
assert not (
alias and channel
), "Only either search alias or channel can be defined"
if alias:
if alias in self.aliases:
return self.data[self.aliases.index(alias)]
@@ -100,7 +154,6 @@ class Namespace:
return self.data[self.channels.index(channel)]
else:
return None
class NamespaceCollection:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -14,7 +14,7 @@ aliases = {
"alias": "slitUnd",
"z_und": 44,
"desc": "Slit after Undulator",
"eco_type": "xoptics.slits.SlitFourBlades",
"eco_type": "xoptics.slits.SlitFourBlades_old",
},
"SARFE10-PBIG050": {
"alias": "gasMon",
@@ -76,7 +76,7 @@ aliases = {
"alias": "slitSwitch",
"z_und": 104,
"desc": "Slit in Optics hutch after Photon switchyard and before Alvra mono",
"eco_type": "xoptics.slits.SlitBlades",
"eco_type": "xoptics.slits.SlitBlades_old",
},
"SAROP11-ODCM105": {
"alias": "mono",
@@ -157,7 +157,7 @@ aliases = {
"alias": "slitAttExp",
"z_und": 120,
"desc": "Slits behind attenuator",
"eco_type": "xoptics.slits.SlitPosWidth",
"eco_type": "xoptics.slits.SlitPosWidth_old",
},
"SAROP11-OLAS120": {
"alias": "refLaser",
+39
View File
@@ -0,0 +1,39 @@
from abc import ABC, abstractmethod
class Adjustable(ABC):
@abstractmethod
def get_current_value(self):
pass
@abstractmethod
def set_target_value_to(self, value):
pass
class Changer:
def __init__(self, target=None, parent=None, changer=None, hold=True, stopper=None):
self.target = target
self._changer = changer
self._stopper = stopper
self._thread = Thread(target=self._changer, args=(target,))
if not hold:
self._thread.start()
def wait(self):
self._thread.join()
def start(self):
self._thread.start()
def status(self):
if self._thread.ident is None:
return "waiting"
else:
if self._thread.is_alive():
return "changing"
else:
return "done"
def stop(self):
self._stopper()
+81
View File
@@ -1 +1,82 @@
from .bernina import *
# from ..utilities.config import initFromConfigList
# from epics import PV
# from .. import ecocnf
# from ..aliases import NamespaceCollection
# import logging
# from .config import components, config
# import sys
# _namespace = globals()
# _mod = sys.modules[__name__]
# _scope_name = "bernina"
# alias_namespaces = NamespaceCollection()
# # from ..utilities.runtable import Run_Table
# # def init(pgroup, alias_namespaces, instances):
# # run_table = Run_Table(pgroup, alias_namespaces.bernina, instances)
# # return run_table
# def init(*args, lazy=None):
# if args:
# allnames = [tc["name"] for tc in components]
# comp_toinit = []
# for arg in args:
# if not arg in allnames:
# raise Exception(f"The component {arg} has no configuration defined!")
# else:
# comp_toinit.append(components[allnames.index(arg)])
# else:
# comp_toinit = components
# if lazy is None:
# lazy = ecocnf.startup_lazy
# op = {}
# for key, value in initFromConfigList(comp_toinit, components, lazy=lazy).items():
# # _namespace[key] = value
# _mod.__dict__[key] = value
# op[key] = value
# if not lazy:
# print("made here")
# if hasattr(value, "alias"):
# for ta in value.alias.get_all():
# try:
# alias_namespaces.bernina.update(
# ta["alias"], ta["channel"], ta["channeltype"]
# )
# except:
# print(f'could not init alias {ta["alias"]}')
# else:
# print(f"object {key} has no alias!")
# alias_namespaces.bernina.store()
# # try:
# # run_table = bernina.init(config['pgroup'], alias_namespaces,_mod)
# # _mod.__dict__['rt'] = run_table
# # op['rt'] = run_table
# # except:
# # print('Initializing of run_table failed')
# return op
# def parse_for_aliases():
# names = [tc["name"] for tc in components]
# for name in names:
# to = _mod.__dict__[name]
# if hasattr(to, "alias"):
# for ta in to.alias.get_all():
# try:
# globals()["alias_namespaces"].bernina.update(
# ta["alias"], ta["channel"], ta["channeltype"]
# )
# except:
# print(f'could not init alias {ta["alias"]}')
# else:
# print(f"object {name} has no alias!")
# globals()["alias_namespaces"].bernina.store()
+2925 -10
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+87
View File
@@ -0,0 +1,87 @@
# new beamline startup
from eco import Assembly
from eco.xoptics.attenuator_aramis import AttenuatorAramis
namespace.append_obj(
"Att_usd",
name="att_usd",
module_name="eco.xoptics.att_usd",
xp=xp,
lazy=True,
)
namespace.append_obj(
"SlitPosWidth",
"SAROP21-OAPU138",
name="slit_att",
lazy=True,
module_name="eco.xoptics.slits",
),
namespace.append_obj(
"JJSlitUnd",
name="slit_und",
module_name="eco.xoptics.slits",
lazy=True,
)
namespace.append_obj(
"SlitBlades",
"SAROP21-OAPU092",
name="slit_switch",
module_name="eco.xoptics.slits",
lazy=True,
)
namespace.append_obj(
"SlitBlades",
"SAROP21-OAPU102",
name="slit_mono",
module_name="eco.xoptics.slits",
lazy=True,
)
{
"name": "pshut_und",
"type": "eco.xoptics.shutters:PhotonShutter",
"args": ["SARFE10-OPSH044:REQUEST"],
"kwargs": {},
"z_und": 44,
"desc": "First shutter after Undulators",
},
{
"name": "xp",
"args": [],
"kwargs": {
"Id": "SAROP21-OPPI113",
"evronoff": "SGE-CPCW-72-EVR0:FrontUnivOut15-Ena-SP",
"evrsrc": "SGE-CPCW-72-EVR0:FrontUnivOut15-Src-SP",
},
"z_und": 103,
"desc": "X-ray pulse picker",
"type": "eco.xoptics.pp:Pulsepick",
},
{
"name": "att_fe",
"type": "eco.xoptics.attenuator_aramis:AttenuatorAramis",
"args": ["SARFE10-OATT053"],
"kwargs": {"shutter": Component("pshut_und")},
"z_und": 53,
"desc": "Attenuator in Front End",
},
class AttenuationFELBernina(Assembly):
def __init__(self, name=None):
super().__init__(name=name)
self._append(
AttenuatorAramis, "SAROP21-OATT135", set_limits=[], name="opt", shutter=None
)
self._append(
AttenuatorAramis, "SARFE10-OATT053", set_limits=[], name="fe", shutter=None
)
+313
View File
@@ -0,0 +1,313 @@
import numpy as np
from eco import Assembly
from eco.devices_general.motors import MotorRecord
from eco.devices_general.cameras_swissfel import CameraBasler
from eco.epics.adjustable import AdjustablePv
from eco.microscopes import MicroscopeMotorRecord
from eco.devices_general.powersockets import MpodModule, MpodChannel
from eco.detector import Jungfrau
from eco.devices_general.wago import AnalogOutput
from eco.elements.adjustable import AdjustableFS, AdjustableVirtual
from eco.elements.detector import DetectorGet
from eco.devices_general.pipelines_swissfel import Pipeline
from eco.devices_general.pv_adjustable import PvRecord
from eco.devices_general.motors import ThorlabsPiezoRecord
class LiquidJetSpectroscopy(Assembly):
def __init__(
self, pgroup_adj=None, config_JF_adj=None, name=None, v_g=None, e2v=None
):
super().__init__(name=name)
self._append(
MpodChannel,
pvbase="SARES21-PS7071",
channel_number=4,
name="illumination_inline",
)
# self._append(
# MpodChannel,
# pvbase="SARES21-PS7071",
# channel_number=2,
# name="illumination_side",
# )
self._append(
CameraBasler,
# pvname_camera="SARES20-CAMS142-M3", #THC
"SARES20-CAMS142-C2", # GIC
name="jetcam_inline",
)
self._append(Pipeline, "SARES20-CAMS142-C1_fb", name="pipeline_fb")
# this is the large camera
self._append(
MicroscopeMotorRecord,
pvname_camera="SARES20-CAMS142-C1", # GIC
pvname_zoom="SARES20-MF1:MOT_14",
name="jetcam_top",
)
# self.jetcam_top._append(Pipeline, "SARES20-CAMS142-C1_fb", name="pipeline_fb")
self._append(
CameraBasler,
# pvname_camera="SARES20-CAMS142-M3", #THC
"SARES20-CAMS142-M1", # GIC
name="jetcam_back",
)
# jetcam_top._append(Pipeline,'SARES20-CAMS142-C1_fb',name="pipeline_fb")
self._v_g = v_g
self._e2v = e2v
self._append(
MotorRecord,
"SARES20-MF1:MOT_5",
name="x",
backlash_definition=True,
is_setting=True,
)
self._append(
MotorRecord,
"SARES20-MF1:MOT_6",
name="y",
backlash_definition=True,
is_setting=True,
)
self._append(
MotorRecord,
"SARES20-MF1:MOT_7",
name="z",
backlash_definition=True,
is_setting=True,
)
self._append(
MpodChannel,
pvbase="SARES21-PS7071",
channel_number=4,
name="light",
)
self._append(
MotorRecord,
"SARES20-MF1:MOT_2",
name="dist_vHamos",
backlash_definition=True,
is_setting=True,
)
# self._append(
# MotorRecord,y=True,
# "SARES20-MF1:MOT_3",
# name="x_analyzer",
# backlash_definition=True,
# is_setting=True,
# )
# self._append(
# MotorRecord,
# "SARES21-XRD:MOT_P_T",
# name="y_vhdet",
# is_setting=True,
#
self._append(
Jungfrau,
"JF03T01V02",
name="det_totem",
pgroup_adj=pgroup_adj,
config_adj=config_JF_adj,
)
self._append(
Jungfrau,
"JF14T01V01",
name="det_vhamos",
pgroup_adj=pgroup_adj,
config_adj=config_JF_adj,
)
self._append(
MpodChannel,
pvbase="SARES21-PS7071",
module_string="HV_EHS_3",
channel_number=1,
name="apd",
)
self._append(
AdjustableFS,
"/sf/bernina/code/gac-bernina/eco_cnf_bernina/configuration/apd_voltage_calibration.json",
name="apd_voltage_calibration",
is_display=False,
is_setting=True,
)
# Convert energy - voltage using calibration
def ene2volt(energy):
try:
E, V = np.asarray(self.apd_voltage_calibration()).T
return np.interp(energy, E, V)
except:
return np.nan
# Read the APD voltage and return it as the virtual value
def get_voltage(apd_voltage):
return self.apd.voltage.get_current_value()
# compute voltage from energy and set it
def set_voltage(target_energy):
voltage = ene2volt(target_energy)
self.apd.voltage.set_target_value(voltage)
return voltage
# Create virtual adjustable:
self._append(
AdjustableVirtual,
[self.apd.voltage],
get_voltage,
set_voltage,
reset_current_value_to=False,
name="ene2volt",
is_display=True,
is_setting=True,
)
# Feedback adjustables
self._append(
AdjustablePv,
pvsetname="SARES20-FEEDBACK-SAMPLE:TARGET",
name="feedback_setpoint",
)
self._append(
AdjustablePv,
pvsetname="SARES20-FEEDBACK-SAMPLE:ENABLE",
name="feedback_enabled",
)
class SaxsSpectrometer(Assembly):
def __init__(
self,
pv_xgc="SARES20-MF1:MOT_4",
pv_rana="SARES20-MF1:MOT_3",
jf_id="JF03T01V02",
config_jf_adj=None,
pgroup_adj=None,
name="xspec_gc",
):
super().__init__(name=name),
self._append(
MotorRecord,
pv_xgc,
name="x_gc",
)
self._append(
MotorRecord,
pv_rana,
name="r_ana",
)
self._append(
Jungfrau,
jf_id,
pgroup_adj=pgroup_adj,
config_adj=config_jf_adj,
name="detector",
)
class LinearFresnelZonePlate(Assembly):
def __init__(
self,
name=None,
):
super().__init__(name=name)
self._append(
MpodChannel,
pvbase="SARES21-PS7071",
channel_number=4,
name="light",
)
self.motor_configuration_thorlabs = {
"hwp_mon": {
"pvname": "SLAAR21-LMOT-ELL1",
},
"hwp_pump": {
"pvname": "SLAAR21-LMOT-ELL5",
},
}
### thorlabs piezo motors ###
for name, config in self.motor_configuration_thorlabs.items():
self._append(
ThorlabsPiezoRecord,
pvname=config["pvname"],
name=name,
is_setting=True,
)
# self._append(
# CameraBasler,
# # pvname_camera="SARES20-CAMS142-M3", #THC
# "SARES20-CAMS142-C2", # GIC
# name="cam_inline",
# )
# self._append(
# MicroscopeMotorRecord,
# pvname_camera="SARES20-CAMS142-C1", # GIC
# pvname_zoom="SARES20-MF1:MOT_14",
# name="cam_top",
# )
self._append(
MotorRecord,
"SARES20-MCS1:MOT_8",
name="rot",
is_setting=True,
is_display=True,
)
self._append(
MotorRecord,
"SARES20-MCS1:MOT_3",
name="tilt",
is_setting=True,
is_display=True,
)
self._append(
MotorRecord,
"SARES20-MCS3:MOT_6",
name="x",
is_setting=True,
is_display=True,
)
self._append(
MotorRecord,
"SARES20-XPS1:MOT_2",
name="y",
is_setting=True,
is_display=True,
)
self._append(
MotorRecord,
"SARES20-MCS3:MOT_4",
name="z",
is_setting=True,
is_display=True,
)
self._append(
MotorRecord,
"SARES20-XPS1:MOT_1",
name="foc",
is_setting=True,
is_display=True,
)
self._append(
MotorRecord,
"SARES20-XPS1:MOT_3",
name="beam_stop_y",
is_setting=True,
is_display=True,
)
self._append(
MotorRecord,
"SARES20-MCS1:MOT_1",
name="i0_pos",
is_setting=True,
is_display=True,
)
+469 -299
View File
@@ -11,309 +11,479 @@
# if arg or kwarg is of type eco.utilities.Component (dummy class)
# this indicates that an earlier initialized object is used
# (e.g. from same configuration).
from ..utilities.config import Component, Alias, init_device, initFromConfigList
from ..utilities.config import (
Component,
init_device,
initFromConfigList,
Configuration,
)
_eco_lazy_init = False
config = Configuration(
"/sf/bernina/code/gac-bernina/eco_cnf_bernina/bernina_config_eco.json", name="bernina_config"
)
components = [
# {
# 'name' : 'device_alias_name',
# 'type' : 'package.module.submodule:ClassOrFactory',
# 'args' : ['all','the','requires','args'],
# 'kwargs': {}
# }
{
"name": "elog",
"type": "eco.utilities.elog:Elog",
"args": ["https://elog-gfa.psi.ch/Bernina"],
"kwargs": {
"user": "gac-bernina",
"screenshot_directory": "/sf/bernina/config/screenshots",
},
},
{
"name": "screenshot",
"type": "eco.utilities.elog:Screenshot",
"args": [],
"kwargs": {"screenshot_directory": "/sf/bernina/config/screenshots"},
},
{
"name": "slitUnd",
"type": "eco.xoptics.slits:SlitFourBlades",
"args": ["SARFE10-OAPU044"],
"kwargs": {},
"desc": "Slit after Undulator",
},
{
"name": "attFE",
"type": "eco.xoptics.attenuator_aramis:AttenuatorAramis",
"args": ["SARFE10-OATT053"],
"kwargs": {},
"desc": "Attenuator in Front End",
},
{
"name": "profFE",
"args": ["SARFE10-PPRM064"],
"kwargs": {},
"z_und": 64,
"desc": "Profile monitor after Front End",
"type": "eco.xdiagnostics.profile_monitors:Pprm",
},
{
"name": "profMirrAlv1",
"args": ["SAROP11-PPRM066"],
"kwargs": {},
"z_und": 66,
"desc": "Profile monitor after Alvra Mirror 1",
"type": "eco.xdiagnostics.profile_monitors:Pprm",
},
{
"name": "slitSwitch",
"z_und": 92,
"desc": "Slit in Optics hutch after Photon switchyard and before Bernina optics",
"type": "eco.xoptics.slits:SlitBlades",
"args": ["SAROP21-OAPU092"],
"kwargs": {},
},
{
"name": "profMirr1",
"args": ["SAROP21-PPRM094"],
"kwargs": {},
"z_und": 94,
"desc": "Profile monitor after Mirror 1",
"type": "eco.xdiagnostics.profile_monitors:Pprm",
},
{
"name": "mono",
"args": ["SAROP21-ODCM098"],
"kwargs": {},
"z_und": 98,
"desc": "DCM Monochromator",
"type": "eco.xoptics.dcm:Double_Crystal_Mono",
},
{
"name": "profMono",
"args": ["SAROP21-PPRM102"],
"kwargs": {},
"z_und": 102,
"desc": "Profile monitor after Monochromator",
"type": "eco.xdiagnostics.profile_monitors:Pprm",
},
{
"name": "monOpt",
"z_und": 133,
"desc": "Intensity/position monitor after Optics hutch",
"type": "eco.xdiagnostics.intensity_monitors:SolidTargetDetectorPBPS",
"args": ["SAROP21-PBPS133"],
"kwargs": {"VME_crate": "SAROP21-CVME-PBPS1", "link": 9},
},
{
"name": "profOpt",
"args": ["SAROP21-PPRM133"],
"kwargs": {},
"z_und": 133,
"desc": "Profile monitor after Optics hutch",
"type": "eco.xdiagnostics.profile_monitors:Pprm",
},
{
"name": "att",
"args": ["SAROP21-OATT135"],
"kwargs": {},
"z_und": 135,
"desc": "Attenuator Bernina",
"type": "eco.xoptics.attenuator_aramis:AttenuatorAramis",
},
{
"name": "refLaser",
"args": ["SAROP21-OLAS136"],
"kwargs": {},
"z_und": 136,
"desc": "Bernina beamline reference laser before KBs",
"type": "eco.xoptics.reflaser:RefLaser_Aramis",
},
{
"name": "slitAtt",
"args": ["SAROP21-OAPU136"],
"kwargs": {},
"z_und": 136,
"desc": "Slits behind attenuator",
"type": "eco.xoptics.slits:SlitPosWidth",
},
{
"name": "monAtt",
"args": ["SAROP21-PBPS138"],
"z_und": 138,
"desc": "Intensity/Position monitor after Attenuator",
"type": "eco.xdiagnostics.intensity_monitors:SolidTargetDetectorPBPS",
"kwargs": {"VME_crate": "SAROP21-CVME-PBPS2", "link": 9},
},
{
"name": "detDio",
"args": ["SAROP21-PDIO138"],
"z_und": 138,
"desc": "Diode digitizer for exp data",
"type": "eco.devices_general.detectors:DiodeDigitizer",
"kwargs": {"VME_crate": "SAROP21-CVME-PBPS2", "link": 9},
},
{
"name": "profAtt",
"args": ["SAROP21-PPRM138"],
"kwargs": {},
"z_und": 138,
"desc": "Profile monitor after Attenuator",
"type": "eco.xdiagnostics.profile_monitors:Pprm",
},
{
"name": "kbVer",
"args": ["SAROP21-OKBV139"],
"z_und": 139,
"desc": "Vertically focusing Bernina KB mirror",
"type": "eco.xoptics.KBver:KBver",
"kwargs": {},
},
{
"args": ["SAROP21-OKBH140"],
"name": "kbHor",
"z_und": 140,
"desc": "Horizontally focusing Bernina KB mirror",
"type": "eco.xoptics.KBhor:KBhor",
"kwargs": {},
},
# {
# 'args' : ['SARES22-GPS'],
# 'name' : 'gps',
# 'z_und' : 142,
# 'desc' : 'General purpose station',
# 'type' : 'eco.endstations.bernina_gps:GPS',
# 'kwargs': {}
# {
# "type": "eco.utilities.config:append_to_path",
# "args": config["path_exp"],
# "name": "path_exp",
# "kwargs": {},
# "lazy": True,
# },
# {
# "name": "screenshot",
# "type": "eco.utilities.elog:Screenshot",
# "args": [],
# "kwargs": {"screenshot_directory": "/sf/bernina/config/screenshots"},
# },
# {
# "name": "fel",
# "type": "eco.fel.swissfel:SwissFel",
# "args": [],
# "kwargs": {},
# "desc": "Fel related control and feedback",
# },
# {
# "name": "mono",
# "args": ["SAROP21-ODCM098"],
# "kwargs": {},
# "z_und": 98,
# "desc": "DCM Monochromator",
# "type": "eco.xoptics.dcm_new:DoubleCrystalMono",
# },
# {
# "name": "slit_und",
# "type": "eco.xoptics.slits:SlitFourBlades_old",
# "args": ["SARFE10-OAPU044"],
# "kwargs": {},
# "desc": "Slit after Undulator",
# },
# {
# "name": "slit_und_epics",
# "type": "eco.xoptics.slits:SlitFourBlades_old",
# "args": ["SARFE10-OAPU044"],
# "kwargs": {},
# "desc": "Slit after Undulator",
# },
# {
# "name": "mon_und",
# "args": ["SARFE10-PBPS053"],
# "z_und": 53,
# "desc": "Intensity/Position monitor after Undolator",
# "type": "eco.xdiagnostics.intensity_monitors:SolidTargetDetectorPBPS",
# "kwargs": {"VME_crate": "SAROP21-CVME-PBPS2", "link": 9},
# },
# {
# "name": "mon_und",
# "z_und": 53,
# "desc": "Intensity/position monitor after Undulator",
# "type": "eco.xdiagnostics.intensity_monitors:SolidTargetDetectorPBPS_new",
# "args": ["SARFE10-PBPS053"],
# "kwargs": {
# "VME_crate": "SAROP21-CVME-PBPS1",
# "link": 9,
# "channels": {
# "up": "SLAAR21-LSCP1-FNS:CH6:VAL_GET",
# "down": "SLAAR21-LSCP1-FNS:CH7:VAL_GET",
# "left": "SLAAR21-LSCP1-FNS:CH4:VAL_GET",
# "right": "SLAAR21-LSCP1-FNS:CH5:VAL_GET",
# },
# "calc": {
# "itot": "SLAAR21-LTIM01-EVR0:CALCI",
# "xpos": "SLAAR21-LTIM01-EVR0:CALCX",
# "ypos": "SLAAR21-LTIM01-EVR0:CALCY",
# },
# },
# },
# {
# "name": "pshut_und",
# "type": "eco.xoptics.shutters:PhotonShutter",
# "args": ["SARFE10-OPSH044:REQUEST"],
# "kwargs": {},
# "z_und": 44,
# "desc": "First shutter after Undulators",
# },
# {
# "name": "pshut_fe",
# "type": "eco.xoptics.shutters:PhotonShutter",
# "args": ["SARFE10-OPSH059:REQUEST"],
# "kwargs": {},
# "z_und": 59,
# "desc": "Photon shutter end of front end",
# },
# {
# "name": "sshut_opt",
# "type": "eco.xoptics.shutters:SafetyShutter",
# "args": ["SGE01-EPKT822:BST1_oeffnen"],
# "kwargs": {},
# "z_und": 115,
# "desc": "Bernina safety shutter",
# },
# {
# "name": "sshut_fe",
# "type": "eco.xoptics.shutters:SafetyShutter",
# "args": ["SGE01-EPKT820:BST1_oeffnen"],
# "kwargs": {},
# "z_und": 115,
# "desc": "Bernina safety shutter",
# },
# {
# "name": "att_fe",
# "type": "eco.xoptics.attenuator_aramis:AttenuatorAramis",
# "args": ["SARFE10-OATT053"],
# "kwargs": {"shutter": Component("pshut_und")},
# "z_und": 53,
# "desc": "Attenuator in Front End",
# },
# {
# "name": "mon_und",
# "z_und": 53,
# "desc": "Intensity/position monitor after Optics hutch",
# "type": "eco.xdiagnostics.intensity_monitors:SolidTargetDetectorPBPS",
# "args": ["SARFE10-PBPS053"],
# "kwargs": {},
# },
# {
# "name": "xspect",
# "z_und": 53,
# "desc": "X-ray single shot spectrometer",
# "type": "eco.xdiagnostics.xspect:Xspect",
# "args": [],
# "kwargs": {},
# },
# {
# "name": "mono_old",
# "args": ["SAROP21-ODCM098"],
# "kwargs": {
# "energy_sp": "SAROP21-ARAMIS:ENERGY_SP",
# "energy_rb": "SAROP21-ARAMIS:ENERGY",
# },
# "z_und": 98,
# "desc": "DCM Monochromator",
# "type": "eco.xoptics.dcm:Double_Crystal_Mono",
# },
# {
# "name": "xp",
# "args": [],
# "kwargs": {
# "Id": "SAROP21-OPPI113",
# "evronoff": "SGE-CPCW-72-EVR0:FrontUnivOut15-Ena-SP",
# "evrsrc": "SGE-CPCW-72-EVR0:FrontUnivOut15-Src-SP",
# },
# "z_und": 103,
# "desc": "X-ray pulse picker",
# "type": "eco.xoptics.pp:Pulsepick",
# },
# {
# "name": "mon_opt_old",
# "z_und": 133,
# "desc": "Intensity/position monitor after Optics hutch",
# "type": "eco.xdiagnostics.intensity_monitors:SolidTargetDetectorPBPS",
# "args": ["SAROP21-PBPS133"],
# "kwargs": {"VME_crate": "SAROP21-CVME-PBPS1", "link": 9},
# },
# {
# "name": "att",
# "args": ["SAROP21-OATT135"],
# "kwargs": {"shutter": Component("xp"), "set_limits": []},
# "z_und": 135,
# "desc": "Attenuator Bernina",
# "type": "eco.xoptics.attenuator_aramis:AttenuatorAramis",
# },
# {
# "name": "slit_att",
# "args": ["SAROP21-OAPU136"],
# "kwargs": {},
# "z_und": 136,
# "desc": "Slits behind attenuator",
# "type": "eco.xoptics.slits:SlitPosWidth",
# },
# {
# "name": "det_dio",
# "args": ["SAROP21-PDIO138"],
# "z_und": 138,
# "desc": "Diode digitizer for exp data",
# "type": "eco.devices_general.detectors:DiodeDigitizer",
# "kwargs": {"VME_crate": "SAROP21-CVME-PBPS2", "link": 9},
# },
# {
# "name": "spatial_tt",
# "args": [],
# "kwargs": {"reduction_client_address": "http://sf-daqsync-02:12003/"},
# "z_und": 141,
# "desc": "spatial encoding timing diagnostics before sample.",
# "type": "eco.xdiagnostics.timetools:SpatialEncoder",
# "lazy": True,
# },
# {
# "name": "slit_kb",
# "args": [],
# "kwargs": {"pvname": "SARES20-MF1"},
# "z_und": 141,
# "desc": "Slits behind Kb",
# "type": "eco.xoptics.slits:SlitBlades_JJ",
# # "type": "eco.xoptics.slits:SlitBladesJJ_old",
# },
# {
# "args": [],
# "name": "gps_old",
# "z_und": 142,
# "desc": "General purpose station",
# "type": "eco.endstations.bernina_diffractometers:GPS_old",
# "kwargs": {
# "Id": "SARES22-GPS",
# "configuration": config["gps_config"],
# "fina_hex_angle_offset": "/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/hex_pi_angle_offset.json",
# },
# "lazy": True,
# },
# {
# "args": [],
# "name": "xrd_old",
# "z_und": 142,
# "desc": "Xray diffractometer",
# "type": "eco.endstations.bernina_diffractometers:XRD_old",
# "kwargs": {"Id": "SARES21-XRD", "configuration": config["xrd_config"]},
# },
# {
# "args": [],
# "name": "xrd",
# "z_und": 142,
# "desc": "Xray diffractometer",
# "type": "eco.endstations.bernina_diffractometers:XRD",
# "kwargs": {
# "Id": "SARES21-XRD",
# "configuration": config["xrd_config"],
# "diff_detector": {"jf_id": "JF01T03V01"},
# },
# },
# {
# "args": [],
# "name": "gasjet",
# "z_und": 142,
# "desc": "ToF comm. gasjet",
# "type": "tof:jet",
# "kwargs": {},
# },
# {
# "args": [],
# "name": "xeye",
# "z_und": 142,
# "desc": "Mobile X-ray eye in Bernina hutch",
# "type": "eco.xdiagnostics.profile_monitors:Bernina_XEYE",
# "kwargs": {
# "zoomstage_pv": config["xeye"]["zoomstage_pv"],
# "camera_pv": config["xeye"]["camera_pv"],
# "bshost": "sf-daqsync-01.psi.ch",
# "bsport": 11151,
# },
# },
# {
# "args": ["SARES20-CAMS142-C3"],
# "name": "cam_sample_xrd",
# "z_und": 142,
# "desc": "",
# "type": "eco.devices_general.cameras_swissfel:CameraBasler",
# "kwargs": {},
# },
# {
# "args": [],
# "name": "cams_qioptiq",
# "z_und": 142,
# "desc": "Qioptic sample viewer in Bernina hutch",
# "type": "eco.endstations.bernina_cameras:Qioptiq",
# "kwargs": {
# "bshost": "sf-daqsync-01.psi.ch",
# "bsport": 11149,
# "zoomstage_pv": config["cams_qioptiq"]["zoomstage_pv"],
# "camera_pv": config["cams_qioptiq"]["camera_pv"],
# },
# },
# {
# "args": ["SLAAR02-TSPL-EPL"],
# "name": "phase_shifter",
# "z_und": 142,
# "desc": "Experiment laser phase shifter",
# "type": "eco.devices_general.timing:PhaseShifterAramis",
# "kwargs": {},
# },
# {
# "args": ["SLAAR21-LTIM01-EVR0"],
# "name": "laser_shutter",
# "z_und": 142,
# "desc": "Laser Shutter",
# "type": "eco.loptics.laser_shutter:laser_shutter",
# "kwargs": {},
# },
# {
# "args": [],
# "name": "daq_dia_old",
# "desc": "server based acquisition",
# "type": "eco.acquisition.dia:DIAClient",
# "kwargs": {
# "instrument": "bernina",
# "api_address": config["daq_address"],
# "pgroup": config["pgroup"],
# "pedestal_directory": config["jf_pedestal_directory"],
# "gain_path": config["jf_gain_path"],
# "config_default": config["daq_dia_config"],
# "jf_channels": config["jf_channels"],
# "default_file_path": None,
# },
# },
# {
# "args": [
# config["checker_PV"],
# config["checker_thresholds"],
# config["checker_fractionInThreshold"],
# ], #'SARFE10-PBPG050:HAMP-INTENSITY-CAL',[60,700],.7],
# "name": "checker",
# "desc": "checker functions for data acquisition",
# "type": "eco.acquisition.checkers:CheckerCA",
# "kwargs": {},
# },
# {
# "args": [
# "SARES20-LSCP9-FNS:CH1:VAL_GET",
# [-100000, 100000],
# config["checker_fractionInThreshold"],
# ], #'SARFE10-PBPG050:HAMP-INTENSITY-CAL',[60,700],.7],
# "name": "checker_epics",
# "desc": "checker functions for data acquisition",
# "type": "eco.acquisition.checkers:CheckerCA",
# "kwargs": {},
# },
# {
# "args": [],
# "name": "lxt",
# "desc": "laser timing with pockels cells and phase shifter",
# "type": "eco.timing.lasertiming:Lxt",
# "kwargs": {},
# },
# {
# "args": ["SARES20-CVME-01-EVR0"],
# "name": "evr_bernina",
# "desc": "Bernina event receiver",
# "type": "eco.timing.event_timing:EventReceiver",
# "kwargs": {},
# },
# {
# "args": [],
# "name": "default_channel_list",
# "desc": "Bernina default channels, used in daq",
# "type": "eco.utilities.config:ChannelList",
# "kwargs": {
# "file_name": "/sf/bernina/config/channel_lists/default_channel_list"
# },
# },
# {
# "args": [],
# "name": "default_channel_list_bs",
# "desc": "Bernina default bs channels, used by bs_daq",
# "type": "eco.utilities.config:ChannelList",
# "kwargs": {
# "file_name": "/sf/bernina/config/channel_lists/default_channel_list_bs"
# },
# },
# {
# "args": [],
# "name": "channels_spectrometer_projection",
# "desc": "",
# "type": "eco.utilities.config:ChannelList",
# "kwargs": {
# "file_name": "/sf/bernina/config/channel_lists/channel_list_PSSS_projection"
# },
# },
# {
# "args": [],
# "name": "bs_daq",
# "desc": "bs daq writer (locally!)",
# "type": "eco.acquisition.bs_data:BStools",
# "kwargs": {
# "default_channel_list": {
# "bernina_default_channels_bs": Component("default_channel_list_bs")
# },
# "default_file_path": f"/sf/bernina/data/{config['pgroup']}/res/%s",
# },
# },
# {
# "args": ["SARES23-"],
# "name": "slit_kb",
# "z_und": 141,
# "desc": "Upstream diagnostics slits",
# "type": "eco.xoptics.slit_USD:Upstream_diagnostic_slits",
# "kwargs": {"right": "LIC4", "left": "LIC3", "up": "LIC2", "down": "LIC1"},
# },
# {
# "args": ["SARES23-"],
# "name": "slit_cleanup",
# "z_und": 141,
# "desc": "Upstream diagnostics slits",
# "type": "eco.xoptics.slit_USD:Upstream_diagnostic_slits",
# "kwargs": {"right": "LIC7", "left": "LIC8", "up": "LIC8", "down": "LIC5"},
# },
# {
# "args": [
# [
# Component("slit_und"),
# Component("slit_switch"),
# Component("slit_att"),
# Component("slit_kb"),
# ]
# ],
# "name": "slits",
# "desc": "collection of all slits",
# "type": "eco.utilities.beamline:Slits",
# "kwargs": {},
# },
# {
# "args": [
# [Component("slit_switch"), Component("slit_att"), Component("slit_kb"),]
# ],
# "name": "slits",
# "desc": "collection of all slits",
# "type": "eco.utilities.beamline:Slits",
# "kwargs": {},
# "lazy": False,
# },
# {
# "args": [],
# "name": "thc",
# "z_und": 142,
# "desc": "High field THz Chamber",
# "type": "eco.endstations.bernina_sample_environments:High_field_thz_chamber",
# "kwargs": {"Id": "SARES23", "configuration": ["ottifant"]},
# },
# {
# "args": [],
# "name": "ocb",
# "z_und": 142,
# "desc": "Organic Crystal Breadboard",
# "type": "eco.endstations.bernina_sample_environments:Organic_crystal_breadboard",
# "kwargs": {"Id": "SARES23"},
# },
# {
# "args": [],
# "name": "eos",
# "z_und": 142,
# "desc": "electro optic sampling stages",
# "type": "eco.endstations.bernina_sample_environments:Electro_optic_sampling",
# "kwargs": {
# "Id": "SARES23",
# "pgroup": config["pgroup"],
# "diode_channels": {
# "d1": "SARES20-LSCP9-FNS:CH1:VAL_GET",
# "d2": "SARES20-LSCP9-FNS:CH2:VAL_GET",
# "diff": "SARES20-LSCP9-FNS:CH3:VAL_GET",
# },
{
"args": [],
"name": "xrd",
"z_und": 142,
"desc": "Xray diffractometer",
"type": "eco.endstations.bernina_diffractometers:XRD",
"kwargs": {'Id':"SARES21-XRD"},
},
{
"args": ["SARES20-PROF142-M1"],
"name": "xeye",
"z_und": 142,
"desc": "Mobile X-ray eye in Bernina hutch",
"type": "eco.xdiagnostics.profile_monitors:Bernina_XEYE",
"kwargs": {"bshost": "sf-daqsync-01.psi.ch", "bsport": 11173},
},
{
"args": ["SLAAR02-TSPL-EPL"],
"name": "phaseShifter",
"z_und": 142,
"desc": "Experiment laser phase shifter",
"type": "eco.devices_general.timing:PhaseShifterAramis",
"kwargs": {},
},
{
"args": ["SLAAR21-LMOT"],
"name": "las",
"z_und": 142,
"desc": "Experiment laser optics",
"type": "eco.loptics.bernina_experiment:Laser_Exp",
"kwargs": {},
},
{
"args": ["SLAAR21-LTIM01-EVR0"],
"name": "laserShutter",
"z_und": 142,
"desc": "Laser Shutter",
"type": "eco.loptics.laser_shutter:laser_shutter",
"kwargs": {},
},
{
"args": ["/sf/bernina/config/channel_lists/default_channel_list_ioxos"],
"name": "ioxos_channel_list",
"desc": "ioxos channel list",
"type": "eco.utilities.config:parseChannelListFile",
"kwargs": {},
},
{
"args": [],
"name": "ioxosdaq",
"z_und": 142,
"desc": "ioxos acquisition",
"type": "eco.acquisition.ioxos_data:Ioxostools",
"kwargs": {"channel_list": Component("ioxos_channel_list")},
},
# },
# },
]
components_old = {
"SARFE10-OPSH044": {
"alias": "ShutUnd",
"z_und": 44,
"desc": "Photon shutter after Undulator",
},
"SARFE10-PBIG050": {
"alias": "GasMon",
"z_und": 50,
"desc": "Gas Monitor Intensity",
},
"SARFE10-PBPS053": {
"alias": "MonUnd",
"z_und": 44,
"desc": "Intensity position monitor after Undulator",
},
"SARFE10-SBST060": {
"alias": "ShutFE",
"z_und": 60,
"desc": "Photon shutter in the end of Front End",
},
"SAROP11-OOMH064": {
"alias": "MirrAlv1",
"z_und": 64,
"desc": "Horizontal mirror Alvra 1",
},
"SAROP21-OOMV092": {
"alias": "Mirr1",
"z_und": 92,
"desc": "Vertical offset Mirror 1",
},
"SAROP21-OOMV096": {
"alias": "Mirr2",
"z_und": 96,
"desc": "Vertical offset mirror 2",
},
"SAROP21-PSCR097": {
"alias": "ProfMirr2",
"z_und": 97,
"desc": "Profile Monitor after Mirror 2",
},
"SAROP21-OPPI103": {"alias": "Pick", "z_und": 103, "desc": "X-ray pulse picker"},
"SAROP21-BST114": {
"alias": "ShutOpt",
"z_und": 114,
"desc": "Shutter after Optics hutch",
},
"SAROP21-PALM134": {
"alias": "TimTof",
"z_und": 134,
"desc": "Timing diagnostics THz streaking/TOF",
},
"SAROP21-PSEN135": {
"alias": "TimRef",
"z_und": 135,
"desc": "Timing diagnostics spectral encoding of ref. index change",
}
# 'SLAAR21-LMOT' : {
# 'alias' : 'Palm',
# 'z_und' : 142,
# 'desc' : 'Streaking arrival time monitor',
# 'eco_type' : 'timing.palm.Palm'},
# 'SLAAR21-LMOT' : {
# 'alias' : 'Psen',
# 'z_und' : 142,
# 'desc' : 'Streaking arrival time monitor',
# 'eco_type' : 'timing.psen.Psen'}
# = dict(
# alias = ''
# z_und =
# desc = ''},
}
try:
components.extend(config["components"])
print("Did append additional components!")
except:
print("Could not append components from config.")
+17
View File
@@ -0,0 +1,17 @@
from eco import ecocnf
def get_from_archive(Obj, attribute_name="pvname"):
def get_archiver_time_range(self, start=None, end=None, plot=True, **kwargs):
"""Try to retrieve data within timerange from archiver. A time delta from now is assumed if end time is missing."""
channelname = self.__dict__[attribute_name]
return ecocnf.archiver.get_data_time_range(
channels=[channelname],
start=start,
end=end,
plot=plot,
**kwargs,
)
Obj.get_archiver_time_range = get_archiver_time_range
return Obj
+224
View File
@@ -0,0 +1,224 @@
from enum import IntEnum
from time import time, sleep
import numpy as np
from epics import PV
from eco.acquisition.utilities import Acquisition
from eco.aliases import Alias
from eco.elements.assembly import Assembly
from eco.epics.adjustable import AdjustablePvString
from eco.epics import get_from_archive
@get_from_archive
class DetectorBsData(Assembly):
def __init__(self, bschannel, name=None):
super().__init__(name=name)
self.status_collection.append(self)
self.bschannel = bschannel
if epics_pv_available & epics_pv_availabe == "same":
self._pv = PV(pvname)
self._append(
AdjustablePvString, self.pvname + ".EGU", name="unit", is_setting=False
)
self.name = name
self.alias = Alias(self.name, channel=self.pvname, channeltype="BS")
def get_current_value(self):
return self._pv.get()
def __call__(self):
return self.get_current_value()
@get_from_archive
class DetectorPvEnum(Assembly):
def __init__(self, pvname, name=None):
super().__init__(name=name)
self.pvname = pvname
self._pv = PV(pvname, connection_timeout=0.05)
self.name = name
self.enum_strs = self._pv.enum_strs
self.PvEnum = IntEnum(name, {tstr: n for n, tstr in enumerate(self.enum_strs)})
self.alias = Alias(name, channel=self.pvname, channeltype="CA")
def validate(self, value):
if type(value) is str:
return self.PvEnum.__members__[value]
else:
return self.PvEnum(value)
def get_current_value(self):
return self.validate(self._pv.get())
def __repr__(self):
if not self.name:
name = self.Id
else:
name = self.name
cv = self.get_current_value()
s = f"{name} (enum) at value: {cv}" + "\n"
s += "{:<5}{:<5}{:<}\n".format("Num.", "Sel.", "Name")
# s+= '_'*40+'\n'
for name, val in self.PvEnum.__members__.items():
if val == cv:
sel = "x"
else:
sel = " "
s += "{:>4} {} {}\n".format(val, sel, name)
return s
def __call__(self):
return self.get_current_value()
class DetectorPvString:
def __init__(self, pvname, name=None, elog=None):
self.name = name
self.pvname = pvname
self._pv = PV(pvname, connection_timeout=0.05)
self._elog = elog
self.alias = Alias(name, channel=self.pvname, channeltype="CA")
def get_current_value(self):
return self._pv.get()
def set_target_value(self, value, hold=False):
changer = lambda value: self._pv.put(bytes(value, "utf8"), wait=True)
return Changer(
target=value, parent=self, changer=changer, hold=hold, stopper=None
)
def __repr__(self):
return self.get_current_value()
def __call__(self, string=None):
if not string is None:
self.set_target_value(string)
else:
return self.get_current_value()
@get_from_archive
class DetectorPvDataStream(Assembly):
def __init__(self, pvname, name=None):
super().__init__(name=name)
self.Id = pvname
self.pvname = pvname
self._pv = PV(pvname)
self.alias = Alias(self.name, channel=self.pvname, channeltype="CA")
self._append(
AdjustablePvString, self.pvname + ".EGU", name="unit", is_setting=False
)
# self._append(
# PvString, self.pvname + ".DESC", name="description", is_setting=False
# )
def collect(self, seconds=None, samples=None):
if (not seconds) and (not samples):
raise Exception(
"Either a time interval or number of samples need to be defined."
)
try:
self._pv.callbacks.pop(self._collection["ix_cb"])
except:
pass
self._collection = {"done": False}
self.data_collected = []
if seconds:
self._collection["start_time"] = time()
self._collection["seconds"] = seconds
stopcond = (
lambda: (time() - self._collection["start_time"])
> self._collection["seconds"]
)
def addData(**kw):
if not stopcond():
self.data_collected.append(kw["value"])
else:
self._pv.callbacks.pop(self._collection["ix_cb"])
self._collection["done"] = True
elif samples:
self._collection["samples"] = samples
stopcond = lambda: len(self.data_collected) >= self._collection["samples"]
def addData(**kw):
self.data_collected.append(kw["value"])
if stopcond():
self._pv.callbacks.pop(self._collection["ix_cb"])
self._collection["done"] = True
self._collection["ix_cb"] = self._pv.add_callback(addData)
time_wait_start = time()
while not self._collection["done"]:
sleep(0.005)
if seconds:
if (time() - time_wait_start) > seconds:
if len(self.data_collected) == 0:
print(
f"No {self.name}({self.Id}) data update in time interval, reporting last value"
)
self._pv.callbacks.pop(self._collection["ix_cb"])
self.data_collected.append(self.get_current_value())
break
return self.data_collected
def acquire(self, hold=False, seconds=None, samples=None, **kwargs):
return Acquisition(
acquire=lambda: self.collect(seconds=seconds, samples=samples, **kwargs),
hold=hold,
stopper=None,
get_result=lambda: self.data_collected,
)
def accumulate_ring_buffer(self, n_buffer):
if not hasattr(self, "_accumulate"):
self._accumulate = {"n_buffer": n_buffer, "ix": 0, "n_cb": -1}
else:
self._accumulate["n_buffer"] = n_buffer
self._accumulate["ix"] = 0
self._pv.callbacks.pop(self._accumulate["n_cb"], None)
self._data = np.squeeze(np.zeros([n_buffer * 2, self._pv.count])) * np.nan
def addData(**kw):
self._accumulate["ix"] = (self._accumulate["ix"] + 1) % self._accumulate[
"n_buffer"
]
self._data[self._accumulate["ix"] :: self._accumulate["n_buffer"]] = kw[
"value"
]
self._accumulate["n_cb"] = self._pv.add_callback(addData)
def accumulate_start(self):
if not hasattr(self, "_accumulate_inf"):
self._accumulate_inf = {"n_cb": -1}
self._pv.callbacks.pop(self._accumulate_inf["n_cb"], None)
self._data_inf = []
def addData(**kw):
self._data_inf.append(kw["value"])
self._accumulate_inf["n_cb"] = self._pv.add_callback(addData)
def accumulate_stop(self):
self._pv.callbacks.pop(self._accumulate_inf["n_cb"], None)
return self._data_inf
def get_data(self):
return self._data[
self._accumulate["ix"]
+ 1 : self._accumulate["ix"]
+ 1
+ self._accumulate["n_buffer"]
]
data = property(get_data)
def get_current_value(self):
return self._pv.get()
+292
View File
@@ -0,0 +1,292 @@
# from data_api import get_data, search
from ..epics.detector import DetectorPvDataStream
from fnmatch import translate
import datetime, dateutil
from numbers import Number
from matplotlib import pyplot as plt
import numpy as np
from .. import ecocnf
from ..elements.assembly import Assembly
from datahub import DataBuffer, Table, Stdout
class DataHub(Assembly):
def __init__(self, pv_pulse_id=None, name=None, add_to_cnf=False):
super().__init__(name=name)
if pv_pulse_id:
self._append(DetectorPvDataStream, pv_pulse_id, name="pulse_id")
if add_to_cnf:
ecocnf.archiver = self
self._databuffer = None
@property
def databuffer(self):
if self._databuffer is None:
self._databuffer = DataBuffer(backend="sf-databuffer")
return self._databuffer
def get_data(self, channels, start, end, range_type=None):
table = Table()
self.databuffer.add_listener(table)
self.databuffer.request(dict(channels=channels, start=start, end=end))
op = table.as_dataframe()
self.databuffer.remove_listeners()
return op
def get_data_time_range(
self,
channels=[],
start=None,
end=None,
plot=False,
force_type=None,
labels=None,
convert_timezone=False,
**kwargs,
):
if not end:
end = datetime.datetime.now()
if isinstance(start, datetime.timedelta):
start = end + start
elif isinstance(start, dict):
start = datetime.timedelta(**start)
start = end + start
elif isinstance(start, Number):
start = datetime.timedelta(seconds=start)
start = end + start
else:
start = datetime.timedelta(**kwargs)
start = end + start
if force_type:
archive_types = ["CA", "BS"]
if force_type in archive_types:
if force_type == "CA":
channels_req = [f"sf-archiverappliance/{tch}" for tch in channels]
elif force_type == "BS":
channels_req = [f"sf-databuffer/{tch}" for tch in channels]
else:
raise Exception(f"force_type must be one of {archive_types}")
else:
channels_req = channels
if type(start) is str:
start = dateutil.parser.parse(start)
if type(end) is str:
end = dateutil.parser.parse(end)
start = datetime2str(local2utc(start))
end = datetime2str(local2utc(end))
data = self.get_data(channels_req, start=start, end=end, range_type="time")
if convert_timezone:
data.index = data.index.tz_convert("Europe/Zurich")
if plot:
ah = plt.gca()
if not labels:
labels = channels
for chan, label in zip(channels, labels):
sel = ~data[chan].isnull()
if any(sel):
x = data.index[sel]
y = data[chan][sel]
ah.step(x, y, ".-", label=label, where="post")
plt.xticks(rotation=30)
plt.legend()
plt.tight_layout()
plt.xlabel(data.index.name)
ah.figure.tight_layout()
return data
def get_data_pulse_id_range(
self,
channels=[],
start=None,
end=None,
plot=False,
force_type=None,
convert_timezone=False,
labels=None,
):
if not end:
if hasattr(self, "pulse_id"):
end = int(self.pulse_id.get_current_value())
else:
raise Exception("no end pulse id provided")
start = start + end
if force_type:
archive_types = ["CA", "BS"]
if force_type in archive_types:
if force_type == "CA":
channels_req = [f"sf-archiverappliance/{tch}" for tch in channels]
elif force_type == "BS":
channels_req = [f"sf-databuffer/{tch}" for tch in channels]
else:
raise Exception(f"force_type must be one of {archive_types}")
else:
channels_req = channels
data = self.get_data(channels_req, start=start, end=end, range_type="pulseId")
if convert_timezone:
data.index = data.index.tz_convert("Europe/Zurich")
if plot:
ah = plt.gca()
if not labels:
labels = channels
for chan, label in zip(channels, labels):
sel = ~np.isnan(data[chan])
x = data.index[sel]
y = data[chan][sel]
ah.step(x, y, ".-", label=label, where="post")
plt.xticks(rotation=30)
plt.legend()
plt.tight_layout()
plt.xlabel(data.index.name)
ah.figure.tight_layout()
return data
def search(self, searchstring):
"""A search in database using simpler unix glob expressions (e.g. '*ARES*')"""
return search(translate(searchstring))
class DataApi(Assembly):
def __init__(self, pv_pulse_id=None, name=None, add_to_cnf=True):
super().__init__(name=name)
if pv_pulse_id:
self._append(DetectorPvDataStream, pv_pulse_id, name="pulse_id")
if add_to_cnf:
ecocnf.archiver = self
def get_data_time_range(
self,
channels=[],
start=None,
end=None,
plot=False,
force_type=None,
labels=None,
convert_timezone=True,
**kwargs,
):
if not end:
end = datetime.datetime.now()
if isinstance(start, datetime.timedelta):
start = end + start
elif isinstance(start, dict):
start = datetime.timedelta(**start)
start = end + start
elif isinstance(start, Number):
start = datetime.timedelta(seconds=start)
start = end + start
else:
start = datetime.timedelta(**kwargs)
start = end + start
if force_type:
archive_types = ["CA", "BS"]
if force_type in archive_types:
if force_type == "CA":
channels_req = [f"sf-archiverappliance/{tch}" for tch in channels]
elif force_type == "BS":
channels_req = [f"sf-databuffer/{tch}" for tch in channels]
else:
raise Exception(f"force_type must be one of {archive_types}")
else:
channels_req = channels
if type(start) is str:
start = dateutil.parser.parse(start)
if type(end) is str:
end = dateutil.parser.parse(end)
start = datetime2str(local2utc(start))
end = datetime2str(local2utc(end))
data = get_data(channels_req, start=start, end=end, range_type="time")
if convert_timezone:
data.index = data.index.tz_convert("Europe/Zurich")
if plot:
ah = plt.gca()
if not labels:
labels = channels
for chan, label in zip(channels, labels):
sel = ~data[chan].isnull()
if any(sel):
x = data.index[sel]
y = data[chan][sel]
ah.step(x, y, ".-", label=label, where="post")
plt.xticks(rotation=30)
plt.legend()
plt.tight_layout()
plt.xlabel(data.index.name)
ah.figure.tight_layout()
return data
def get_data_pulse_id_range(
self,
channels=[],
start=None,
end=None,
plot=False,
force_type=None,
convert_timezone=True,
labels=None,
):
if not end:
if hasattr(self, "pulse_id"):
end = int(self.pulse_id.get_current_value())
else:
raise Exception("no end pulse id provided")
start = start + end
if force_type:
archive_types = ["CA", "BS"]
if force_type in archive_types:
if force_type == "CA":
channels_req = [f"sf-archiverappliance/{tch}" for tch in channels]
elif force_type == "BS":
channels_req = [f"sf-databuffer/{tch}" for tch in channels]
else:
raise Exception(f"force_type must be one of {archive_types}")
else:
channels_req = channels
data = get_data(channels_req, start=start, end=end, range_type="pulseId")
if convert_timezone:
data.index = data.index.tz_convert("Europe/Zurich")
if plot:
ah = plt.gca()
if not labels:
labels = channels
for chan, label in zip(channels, labels):
sel = ~np.isnan(data[chan])
x = data.index[sel]
y = data[chan][sel]
ah.step(x, y, ".-", label=label, where="post")
plt.xticks(rotation=30)
plt.legend()
plt.tight_layout()
plt.xlabel(data.index.name)
ah.figure.tight_layout()
return data
def search(self, searchstring):
"""A search in database using simpler unix glob expressions (e.g. '*ARES*')"""
return search(translate(searchstring))
def datetime2str(datetime_date):
return datetime_date.isoformat()
def local2utc(datetime_date):
return datetime_date.replace(
tzinfo=None,
).astimezone(
tz=datetime.timezone.utc,
)
+19
View File
@@ -0,0 +1,19 @@
import subprocess, os
def get_strip_chart_function():
return strip_chart
def strip_chart(*args, **kwargs):
"""Usage: Arguments represent channels in the strip_chart config command line argument.
Alternatively arguments can be detectors or adjustables, from which _all_ channels are determined
"""
channels = list(args)
cmd = ["strip_chart"]
cmd += ['-config="' + str(channels) + '"']
cmd += ["-start"]
line = " ".join(cmd)
print(f"Starting following commandline silently:\n" + line)
with open(os.devnull, "w") as FNULL:
subprocess.Popen(line, shell=True, stdout=FNULL, stderr=subprocess.STDOUT)
+2
View File
@@ -0,0 +1,2 @@
ELOG = None
ARCHIVER=None
+1
View File
@@ -0,0 +1 @@
from .jungfrau import Jungfrau
+188
View File
@@ -0,0 +1,188 @@
from ..elements.assembly import Assembly
from ..aliases import Alias
from eco import ecocnf
from epics.pv import PV
# try:
# from bsread.bsavail import pollStream
# except:
# from bsread.unused.bsavail import pollStream
from bsread import dispatcher, source
from ..epics import get_from_archive
from escape import stream
from time import time, sleep
from eco.acquisition.utilities import Acquisition
from eco.acquisition.decorators import scannable
from eco.epics.detector import CallbackEpics
@get_from_archive
@scannable
class DetectorBsStream:
def __init__(self, bs_channel, cachannel="same", name=None):
self.name = name
self.bs_channel = bs_channel
if cachannel == "same":
self.pvname = bs_channel
elif (not cachannel) or cachannel == "none":
self.pvname = None
else:
self.pvname = cachannel
if self.pvname:
self._pv = PV(self.pvname)
self.alias = Alias(name, channel=bs_channel, channeltype="BS")
self.stream = stream.EscData(source=stream.EventSource(self.bs_channel, None))
def bs_avail(self):
return self.bs_channel in [
tmp["name"] for tmp in dispatcher.get_current_channels()
]
def get_current_value(self, force_bsstream=False):
if not force_bsstream:
if not hasattr(self, "_pv"):
return None
return self._pv.get()
else:
raise NotImplementedError(
"setup of stream for bs channel not implemented yet"
)
# def get_stream_state(self, timeout=1):
# return pollStream(self.bs_channel, timeout=1)
def create_stream_callback(self, foo):
with source(channels=[self.bs_channel]) as s:
done = False
while not done:
done = foo(s.receive())
def collect(self, seconds=None, samples=None):
if (not seconds) and (not samples):
raise Exception(
"Either a time interval or number of samples need to be defined."
)
try:
self._pv.callbacks.pop(self._collection["ix_cb"])
except:
pass
self._collection = {"done": False}
self.data_collected = []
if seconds:
self._collection["start_time"] = time()
self._collection["seconds"] = seconds
stopcond = (
lambda: (time() - self._collection["start_time"])
> self._collection["seconds"]
)
def addData(**kw):
if not stopcond():
self.data_collected.append(kw["value"])
else:
try:
self._pv.callbacks.pop(self._collection["ix_cb"])
except:
pass
self._collection["done"] = True
elif samples:
self._collection["samples"] = samples
stopcond = lambda: len(self.data_collected) >= self._collection["samples"]
def addData(**kw):
self.data_collected.append(kw["value"])
if stopcond():
try:
self._pv.callbacks.pop(self._collection["ix_cb"])
except:
pass
self._collection["done"] = True
self._collection["ix_cb"] = self._pv.add_callback(addData)
time_wait_start = time()
while not self._collection["done"]:
sleep(0.005)
if seconds:
if (time() - time_wait_start) > seconds:
if len(self.data_collected) == 0:
print(
f"No {self.name}({self.pvname}) data update in time interval, reporting last value"
)
self._pv.callbacks.pop(self._collection["ix_cb"])
self.data_collected.append(self.get_current_value())
break
return self.data_collected
def acquire(self, hold=False, seconds=None, samples=None, **kwargs):
return Acquisition(
acquire=lambda: self.collect(seconds=seconds, samples=samples, **kwargs),
hold=hold,
stopper=None,
get_result=lambda: self.data_collected,
)
def accumulate_ring_buffer(self, n_buffer):
if not hasattr(self, "_accumulate"):
self._accumulate = {"n_buffer": n_buffer, "ix": 0, "n_cb": -1}
else:
self._accumulate["n_buffer"] = n_buffer
self._accumulate["ix"] = 0
self._pv.callbacks.pop(self._accumulate["n_cb"], None)
self._data = np.squeeze(np.zeros([n_buffer * 2, self._pv.count])) * np.nan
def addData(**kw):
self._accumulate["ix"] = (self._accumulate["ix"] + 1) % self._accumulate[
"n_buffer"
]
self._data[self._accumulate["ix"] :: self._accumulate["n_buffer"]] = kw[
"value"
]
self._accumulate["n_cb"] = self._pv.add_callback(addData)
def accumulate_start(self):
if not hasattr(self, "_accumulate_inf"):
self._accumulate_inf = {"n_cb": -1}
self._pv.callbacks.pop(self._accumulate_inf["n_cb"], None)
self._data_inf = []
def addData(**kw):
self._data_inf.append(kw["value"])
self._accumulate_inf["n_cb"] = self._pv.add_callback(addData)
def accumulate_stop(self):
self._pv.callbacks.pop(self._accumulate_inf["n_cb"], None)
return self._data_inf
@property
def data(self):
return self._data[
self._accumulate["ix"]
+ 1 : self._accumulate["ix"]
+ 1
+ self._accumulate["n_buffer"]
]
def set_current_value_callback(
self, func="accumulate", run_once=True, print_output=False, **kwargs
):
if hasattr(self, "_pv"):
return CallbackEpics(
self._pv,
func=func,
run_once=run_once,
print_output=print_output,
**kwargs,
)
@get_from_archive
class DetectorBsCam:
def __init__(self, bschannel, name=None):
self.name = name
self.bschannel = bschannel
self.alias = Alias(name, channel=bschannel, channeltype="BSCAM")
+569
View File
@@ -0,0 +1,569 @@
import shutil
import time
from tkinter import W
from eco.base.adjustable import Adjustable
from eco.devices_general.therm import ChillerThermotek
from eco.elements.adj_obj import AdjustableObject
from eco.elements.detector import DetectorGet
from ..elements.adjustable import AdjustableFS, AdjustableVirtual, AdjustableGetSet
from ..epics.adjustable import AdjustablePv
from ..elements.assembly import Assembly
from ..aliases import Alias
from pathlib import Path
from ..elements import memory
from datetime import datetime
import requests
class JungfrauChannel(Assembly):
def __init__(
self,
jf_id,
name=None,
):
super().__init__(name=name)
self.alias = Alias(name, channel=jf_id, channeltype="JF")
class Jungfrau(Assembly):
def __init__(
self,
jf_id,
pv_trigger="SAR-CVME-TIFALL5-EVG0:SoftEvt-EvtCode-SP",
trigger_on=254,
trigger_off=255,
broker_address="http://sf-daq:10002",
broker_address_aux="http://sf-daq:10003",
pgroup_adj=None,
config_adj=None,
chiller_thermotek="SARES20-CHIL",
name=None,
):
super().__init__(name=name)
# self.alias = Alias(name, channel=jf_id, channeltype="JF")
self.pgroup = pgroup_adj
self.jf_id = jf_id
self.broker_address = broker_address
self.broker_address_aux = broker_address_aux
self._append(
DetectorGet, lambda: f"http://{self.get_vis_url()}", name="visulization_url"
)
self._append(JungfrauChannel, jf_id, name="data")
self._append(JungfrauChannel, jf_id + "_rawdata", name="data_raw")
self._append(
JungfrauChannel, jf_id + "_dap_col4", name="data_online_processing"
)
for n in range(10):
self._append(
JungfrauChannel,
jf_id + f"_dap_col{n+4}",
name=f"data_online_processing_roi{n}",
is_display=False,
)
self._append(
JungfrauChannel, jf_id + "_dap_col3", name="ppref_online_processing"
)
self._append(
AdjustablePv,
pv_trigger,
is_display=True,
is_setting=False,
name="trigger",
)
self._trigger_on = trigger_on
self._trigger_off = trigger_off
self._append(
AdjustableVirtual,
[self.trigger],
lambda value: value == self._trigger_on,
self._set_trigger_enable,
name="trigger_enable",
append_aliases=False,
is_setting=True,
)
self._append(
AdjustableGetSet,
self.get_present_pedestal_filename,
lambda value: NotImplementedError(
"Can not set the pedestal file manually yet."
),
name="pedestal_file",
is_display=True,
)
self._append(
AdjustableGetSet,
self.get_present_gain_filename,
lambda value: NotImplementedError(
"Can not set the pedestal file manually yet."
),
name="gain_file",
is_display=True,
)
self._append(
AdjustableGetSet,
self.get_present_pedestal_filename_in_run,
lambda value: NotImplementedError(
"Can not set the pedestal file manually yet."
),
name="pedestal_file_in_run",
is_display=True,
)
self._append(
AdjustableGetSet,
self.get_present_gain_filename_in_run,
lambda value: NotImplementedError(
"Can not set the pedestal file manually yet."
),
name="gain_file_in_run",
is_display=True,
)
self._last_dap_req_time = 0
self._append(
AdjustableFS,
'/sf/bernina/code/gac-bernina/eco_cnf_bernina/reference_values/dap_settings',
name="_dap_settings_storage",
is_display=False,
is_setting=False,
)
self._append(
AdjustableGetSet,
self.get_dap_settings,
self.set_dap_settings,
name="_dap_settings",
is_display=False,
is_setting=False,
)
self._append(
AdjustableObject,
self._dap_settings,
is_setting_children=True,
name="settings_dap",
)
if config_adj:
self._append(
JungfrauDaqConfig,
jf_id,
config_adj,
name="config_daq",
is_setting=True,
is_status=True,
is_display="recursive",
)
if chiller_thermotek:
self._append(
ChillerThermotek,
pvbase=chiller_thermotek,
name="chiller",
is_display="recursive",
)
def set_dap_rois(self,*rois):
tmp = self.settings_dap._base_dict()
tmp['roi_x1']=[roi[0] for roi in rois if roi]
tmp['roi_x2']=[roi[1] for roi in rois if roi]
tmp['roi_y1']=[roi[2] for roi in rois if roi]
tmp['roi_y2']=[roi[3] for roi in rois if roi]
self.settings_dap._base_dict(tmp)
def _set_trigger_enable(self, value):
if value:
self.trigger.set_target_value(self._trigger_on).wait()
else:
self.trigger.set_target_value(self._trigger_off).wait()
def get_present_gain_filename(self):
filepath = Path(f"/sf/jungfrau/config/gainMaps/{self.jf_id}/gains.h5")
if filepath.exists():
return filepath.as_posix()
else:
raise Exception(f"File {filepath.as_posix()} seems not to exist!")
def get_present_gain_filename_in_run(self, intempdir=False):
f = Path(self.get_present_gain_filename())
dest = Path(
f"/sf/bernina/data/{self.pgroup()}/res/tmp/gainmaps_{self.jf_id}.h5"
)
try:
if not dest.exists():
dest.parent.mkdir(parents=True, exist_ok=True, mode=0o775)
try:
dest.parent.chmod(0o775)
except:
pass
shutil.copyfile(f, dest)
except PermissionError:
return "No permissions to res directory!"
if intempdir:
return dest.as_posix()
else:
return f"aux/{dest.name}"
def get_present_pedestal_filename(self):
searchpath = Path(f"/sf/jungfrau/data/pedestal/{self.jf_id}")
filelist = list(searchpath.glob("*.h5"))
times = [datetime.strptime(f.stem, "%Y%m%d_%H%M%S") for f in filelist]
return filelist[times.index(max(times))].as_posix()
def get_present_pedestal_filename_in_run(self, intempdir=False):
f = Path(self.get_present_pedestal_filename())
dest = Path(
f"/sf/bernina/data/{self.pgroup()}/res/tmp/pedestal_{self.jf_id}_{f.stem}.h5"
)
try:
if not dest.exists():
dest.parent.mkdir(parents=True, exist_ok=True, mode=0o775)
try:
dest.parent.chmod(0o775)
except:
pass
shutil.copyfile(f, dest)
except PermissionError:
return "No poermissions to res directory!"
if intempdir:
return dest.as_posix()
else:
return f"aux/{dest.name}"
def get_dap_settings(self, force=False):
if force:
if 5 < (time.time() - self._last_dap_req_time):
self._last_dap_message = requests.get(
f"{self.broker_address_aux}/get_dap_settings",
json={"detector_name": self.jf_id},
).json()
self._last_dap_req_time = time.time()
if self._last_dap_message["status"] == "ok":
self._dap_settings_storage.set_target_value(self._last_dap_message["parameters"]).wait()
return self._last_dap_message["parameters"]
else:
val = self._dap_settings_storage.get_current_value()
if not val:
val = self.get_dap_settings(force=True)
return val
def set_dap_settings(self, dap_setting_dict):
# print("Setting not implmented yet!")
# return
m = requests.post(
f"{self.broker_address_aux}/set_dap_settings",
json={"detector_name": self.jf_id, "parameters": dap_setting_dict},
).json()
if m["status"] == "ok":
self._dap_settings_storage.set_target_value(dap_setting_dict).wait()
return m
def get_detector_frequency(self):
return self._event_master.event_codes[
self._detectors_event_code
].frequency.get_current_value()
def get_availability(self):
is_available = (
self.jf_id
in requests.get(f"{self.broker_address}/get_allowed_detectors").json()[
"detectors"
]
)
return is_available
def get_vis_url(self):
tmp = requests.get(f"{self.broker_address}/get_allowed_detectors").json()
ix = tmp["detectors"].index(self.jf_id)
return tmp["visualisation_address"][ix]
def get_isrunning(self):
is_running = (
self.jf_id
in requests.get(f"{self.broker_address}/get_running_detectors").json()[
"detectors"
]
)
return is_running
def power_on(self):
JF_channel = self.jf_id
par = {"detector_name": JF_channel}
return requests.post(
f"{self.broker_address}/power_on_detector", json=par
).json()
# def take_pedestal(self, JF_list=None, pgroup=None):
# if pgroup is None:
# pgroup = self.pgroup
# if not JF_list:
# JF_list = self.get_JFs_running()
# parameters = {
# "pgroup": pgroup,
# "rate_multiplicator": 1,adc_to_energy
# "detectors": {tJF: {} for tJF in JF_list},
# }
# return requests.post(
# f"{self.broker_address}/take_pedestal", json=parameters
# ).json()
class JungfrauDaqConfig(Assembly):
def __init__(self, jf_id, jf_daq_cfg: Adjustable, name=None):
super().__init__(name=name)
self._jf_id = jf_id
self._jf_daq_cfg = jf_daq_cfg
cfg = self._jf_daq_cfg.get_current_value()
if self._jf_id not in cfg.keys():
cfg[self._jf_id] = {}
self._jf_daq_cfg.set_target_value(cfg).wait()
self._append(
AdjustableGetSet,
self._get_adc_to_energy,
self._set_adc_to_energy,
name="convert_adc_to_energy",
is_display=True,
is_setting=True,
)
self._append(
AdjustableGetSet,
self._get_geometry_corr,
self._set_geometry_corr,
name="apply_tile_geometry",
is_display=True,
is_setting=True,
)
self._append(
AdjustableGetSet,
self._get_compressed_bitshuffle,
self._set_compressed_bitshuffle,
name="compress_bitshuffle",
is_display=True,
is_setting=True,
)
self._append(
AdjustableGetSet,
self._get_rounding_factor,
self._set_rounding_factor,
name="rounding_factor_keV",
is_display=True,
is_setting=True,
)
self._append(
AdjustableGetSet,
self._get_large_pixel_processing,
self._set_large_pixel_processing,
name="large_pixel_processing",
is_display=True,
is_setting=True,
)
self._append(
AdjustableGetSet,
self._get_disabled_modules,
self._set_disabled_modules,
name="disabled_tiles",
is_display=True,
is_setting=True,
)
self._append(
AdjustableGetSet,
self._get_binning,
self._set_binning,
name="downsample",
is_display=True,
is_setting=True,
)
self._append(
AdjustableGetSet,
self._get_keep_raw_data,
self._set_keep_raw_data,
name="keep_raw_data",
is_display=True,
is_setting=True,
)
self._append(
AdjustableGetSet,
self._get_save_online_processing,
self._set_save_online_processing,
name="save_online_processing",
is_display=True,
is_setting=True,
)
def _get_adc_to_energy(self, *args):
try:
return self._jf_daq_cfg.get_current_value()[self._jf_id]["adc_to_energy"]
except KeyError:
return False
def _set_adc_to_energy(self, value):
if value:
cfg = self._jf_daq_cfg.get_current_value()
cfg[self._jf_id]["adc_to_energy"] = True
self._jf_daq_cfg.set_target_value(cfg).wait()
else:
cfg = self._jf_daq_cfg.get_current_value()
cfg[self._jf_id]["adc_to_energy"] = False
self._jf_daq_cfg.set_target_value(cfg).wait()
def _get_geometry_corr(self, *args):
try:
return self._jf_daq_cfg.get_current_value()[self._jf_id]["geometry"]
except KeyError:
return "not sure what happens"
def _set_geometry_corr(self, value):
if value:
cfg = self._jf_daq_cfg.get_current_value()
cfg[self._jf_id]["geometry"] = True
self._jf_daq_cfg.set_target_value(cfg).wait()
else:
cfg = self._jf_daq_cfg.get_current_value()
cfg[self._jf_id]["geometry"] = False
self._jf_daq_cfg.set_target_value(cfg).wait()
def _get_compressed_bitshuffle(self, *args):
try:
return self._jf_daq_cfg.get_current_value()[self._jf_id]["compression"]
except KeyError:
return False
def _set_compressed_bitshuffle(self, value):
if value:
cfg = self._jf_daq_cfg.get_current_value()
cfg[self._jf_id]["compression"] = True
self._jf_daq_cfg.set_target_value(cfg).wait()
else:
cfg = self._jf_daq_cfg.get_current_value()
cfg[self._jf_id]["compression"] = False
self._jf_daq_cfg.set_target_value(cfg).wait()
def _get_save_online_processing(self, *args):
try:
return self._jf_daq_cfg.get_current_value()[self._jf_id]["save_dap_results"]
except KeyError:
# raise Exception("unclear what the default for keeping raw files is!")
return None
def _set_save_online_processing(self, value):
if value:
cfg = self._jf_daq_cfg.get_current_value()
cfg[self._jf_id]["save_dap_results"] = True
self._jf_daq_cfg.set_target_value(cfg).wait()
else:
cfg = self._jf_daq_cfg.get_current_value()
cfg[self._jf_id]["save_dap_results"] = False
self._jf_daq_cfg.set_target_value(cfg).wait()
def _get_keep_raw_data(self, *args):
try:
return not self._jf_daq_cfg.get_current_value()[self._jf_id][
"remove_raw_files"
]
except KeyError:
# raise Exception("unclear what the default for keeping raw files is!")
return None
def _set_keep_raw_data(self, value):
if value:
cfg = self._jf_daq_cfg.get_current_value()
cfg[self._jf_id]["remove_raw_files"] = False
self._jf_daq_cfg.set_target_value(cfg).wait()
else:
cfg = self._jf_daq_cfg.get_current_value()
cfg[self._jf_id]["remove_raw_files"] = True
self._jf_daq_cfg.set_target_value(cfg).wait()
def _get_large_pixel_processing(self, *args):
try:
return self._jf_daq_cfg.get_current_value()[self._jf_id][
"double_pixels_action"
]
except KeyError:
# raise Exception("unclear what the default for double pixels is!")
return None
def _set_large_pixel_processing(self, value):
cfg = self._jf_daq_cfg.get_current_value()
cfg[self._jf_id]["double_pixels_action"] = value
self._jf_daq_cfg.set_target_value(cfg).wait()
def _get_rounding_factor(self, *args):
try:
return self._jf_daq_cfg.get_current_value()[self._jf_id]["factor"]
except KeyError:
# raise Exception("unclear what the default for double pixels is!")
return None
def _set_rounding_factor(self, value):
cfg = self._jf_daq_cfg.get_current_value()
cfg[self._jf_id]["factor"] = value
self._jf_daq_cfg.set_target_value(cfg).wait()
def _get_disabled_modules(self, *args):
try:
return self._jf_daq_cfg.get_current_value()[self._jf_id]["disabled_modules"]
except KeyError:
return []
def _set_disabled_modules(self, value):
cfg = self._jf_daq_cfg.get_current_value()
if value == []:
cfg[self._jf_id].pop("disabled_modules")
else:
cfg[self._jf_id]["disabled_modules"] = value
self._jf_daq_cfg.set_target_value(cfg).wait()
def _get_binning(self, *args):
try:
return self._jf_daq_cfg.get_current_value()[self._jf_id]["downsample"]
except KeyError:
return [1, 1]
def _set_binning(self, value):
cfg = self._jf_daq_cfg.get_current_value()
if value == [1, 1]:
cfg[self._jf_id].pop("downsample")
else:
cfg[self._jf_id]["downsample"] = value
self._jf_daq_cfg.set_target_value(cfg).wait()
def _get_keepraw(self, *args):
try:
remove_raw = self._jf_daq_cfg.get_current_value()[self._jf_id][
"remove_raw_files"
]
# if type(remove_raw) is bool:
return remove_raw
except KeyError:
return "not sure what happens"
def _set_keepraw(self, value):
cfg = self._jf_daq_cfg.get_current_value()
cfg[self._jf_id]["remove_raw_files"] = value
self._jf_daq_cfg.set_target_value(cfg).wait()
# {
# "adc_to_energy": true,
# "compression": true,
# "double_pixels_actions": "interpolate",
# "downsample": [
# 1,
# 1
# ],
# "factor": 0.25,x
# "geometry": true,
# "remove_raw_files": false
# "disabled_modules": [],
# },
Binary file not shown.
+299
View File
@@ -0,0 +1,299 @@
#!/usr/bin/env python
# *-----------------------------------------------------------------------*
# | |
# | Copyright (c) 2015 by Paul Scherrer Institute (http://www.psi.ch) |
# | |
# | Author Thierry Zamofing (thierry.zamofing@psi.ch) |
# *-----------------------------------------------------------------------*
"""
PBComm:
SSH communication class with gpascii.
"""
# backup Motor[1].status
# backup Coord[x].Status
# backup Sys.Status
# ? -> $00000080
from __future__ import print_function
# import wx
import time
import paramiko, re, os
# WX3=(wx.VERSION[0]==3) #old version 3.x
import logging
_log = logging.getLogger(__name__)
def debug(*args):
_log.debug(" ".join(map(str, args)))
class SSHComm:
"""Communicates with the Delta Tau gpascii programm wia SSH"""
gpascii_ack = "\r\n\x06\r\n"
gpascii_inp = "Input\r\n"
def __init__(self):
self.reqSet = set()
# def __init__(self,args=None):
# self.args=args
self.defDictStack = [
dict()
] # definition dictionaties. stacked with OPEN and CLOSE commands
pass
def connect(self, host, password="deltatau", timeout=30.0):
"start communication to gpascii by starting a SSH session (if needed and gpascii on the PowerBrick"
p = host.rfind(":")
if p >= 0:
hostname = host[:p]
port = int(host[p + 1 :])
else:
hostname = host
port = 22
p = hostname.rfind("@")
if p >= 0:
username = hostname[:p]
hostname = hostname[p + 1 :]
else:
username = "root"
self.client = client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# client.set_missing_host_key_policy(paramiko.WarningPolicy())
cfg = {
"hostname": hostname,
"port": port,
"username": username,
"password": password,
"timeout": timeout,
}
try:
with open(os.path.expanduser("~/.ssh/config")) as fh:
ssh_config = paramiko.SSHConfig()
ssh_config.parse(fh)
except IOError as e:
pass
else:
host_conf = ssh_config.lookup(hostname)
for k, v in host_conf.items():
if k == "hostname":
cfg[k] = v
elif k == "user":
cfg["username"] = v
elif k == "port":
cfg[k] = int(v)
elif k == "identityfile":
cfg["key_filename"] = os.path.expanduser(v[0])
# elif k == "stricthostkeychecking" and v == "no":
# client.set_missing_host_key_policy(paramiko.WarningPolicy())
elif k == "requesttty":
self.get_pty = v in ("yes", "force")
elif k == "gssapikeyexchange":
cfg["gss_auth"] = v == "yes"
elif k == "gssapiauthentication":
cfg["gss_kex"] = v == "yes"
elif k == "proxycommand":
cfg["sock"] = paramiko.ProxyCommand(v)
client.connect(**cfg)
self.gpascii()
def gpascii(self):
self.chan = (
chan
) = self.client.invoke_shell() # -> returns a paramiko.channel.Channel
chan.settimeout(1.0)
self.read_until("root@.*?# ")
chan.send("stty -echo\n") # dont't echo the send data
chan.send("gpascii -2\n")
try:
self.read_until(".*?STDIN Open for ASCII Input")
except TimeoutError as err:
raise ValueError("GPASCII startup string not found")
# chan.send('#1..8p\n')
# print(self.read_until_endswith(SSHComm.gpascii_ack).rstrip(SSHComm.gpascii_ack))
# print('done')
def read_until(self, regex, timeout=1.0):
"SSH wait, for regex in buffer up to `timeout` seconds,"
chan = self.chan
wait_re = re.compile(regex)
t0 = time.time()
buf = ""
while True:
if chan.recv_ready():
t0 = time.time() # reset timeout
s = chan.recv(4096).decode()
debug(repr(s))
buf += s
m = wait_re.search(buf)
if m:
return (buf, m.span())
elif (time.time() - t0) > timeout:
raise TimeoutError(repr(buf))
else:
time.sleep(0.001)
def read_until_endswith(self, txt, timeout=1.0):
"SSH wait, up until received data ends with text up to `timeout` seconds"
chan = self.chan
t0 = time.time()
buf = ""
while True:
if chan.recv_ready():
t0 = time.time() # reset timeout
s = chan.recv(4096).decode()
debug(repr(s))
buf += s
if buf.endswith(txt):
return buf
elif (time.time() - t0) > timeout:
raise TimeoutError(repr(buf))
else:
time.sleep(0.001)
def iawrite(self, msg):
"""interactive write. if 1 is returned, nothing is done"""
m = re.match("\s*(define|open|close)", msg, re.IGNORECASE)
if m:
kw = m.group(1).lower()
if kw == "open":
self.defDictStack.append(dict())
print(self.defDictStack)
elif kw == "close":
if len(self.defDictStack) > 1:
del self.defDictStack[-1]
else:
print("close stack error")
print(self.defDictStack)
elif kw == "define":
m = re.match("\s*define\s*\(([^)]*)", msg, re.IGNORECASE)
if m:
args = eval("dict(" + m.group(1) + ")")
self.defDictStack[-1].update(args)
print(self.defDictStack)
return 1
for defDic in reversed(self.defDictStack):
for k, v in defDic.items():
msg = re.sub(
r"(\W|^)" + k + r"(?=\W|$)", r"\g<1>" + str(v), msg
) # define must be separated with non-alphanumeric character [^a-zA-Z0-9_], or at begin/end
if False:
# self.args.dryrun:
# print(msg)
return 1
elif hasattr(self, "chan"):
self.chan.send(msg + "\n")
else:
print("nothing done! no opened connection.")
return 1
return None
if __name__ == "__main__":
import argparse # since python 2.7
def ParseArgs(required=True):
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawTextHelpFormatter
)
parser.add_argument(
"--host",
default="SARES20-CPPM-EXP1",
help="the hostname (default=%(default)s)",
)
return parser.parse_args()
# --- main code ---
logging.basicConfig(
level=logging.DEBUG,
format="%(levelname).1s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ",
)
args = ParseArgs()
gpascii = SSHComm()
gpascii.connect(args.host)
chan = gpascii.chan
cmd = {
b"EncTable[1].PrevEnc\n",
b"EncTable[3].PrevEnc\n",
b"#9;p\n",
b"Motor[5].pos\n",
b"EncTable[7].PrevEnc\n",
b"Motor[4].idCmd\n",
b"Motor[2].Ctrl\n",
b"Motor[2].pos\n",
b"EncTable[4].PrevEnc\n",
b"Motor[1].Ctrl\n",
b"#8?\n",
b"#13;p\n",
b"EncTable[2].PrevEnc\n",
b"Motor[5].idCmd\n",
b"#5?\n",
b"EncTable[5].PrevEnc\n",
b"Motor[2].idCmd\n",
b"#1;p\n",
b"#2;p\n",
b"#7?\n",
b"Motor[4].Ctrl\n",
b"Motor[8].idCmd\n",
b"#12;p\n",
b"Motor[6].idCmd\n",
b"#14;p\n",
b"#5;p\n",
b"Motor[5].Ctrl\n",
b"#6?\n",
b"Motor[4].pos\n",
b"Motor[1].idCmd\n",
b"Motor[3].pos\n",
b"#10;p\n",
b"#15;p\n",
b"#4?\n",
b"#6;p\n",
b"Motor[3].idCmd\n",
b"#8;p\n",
b"Motor[7].Ctrl\n",
b"#7;p\n",
b"Motor[8].Ctrl\n",
b"Motor[6].pos\n",
b"#3;p\n",
b"?\n",
b"EncTable[8].PrevEnc\n",
b"#11;p\n",
b"#1?\n",
b"Motor[8].pos\n",
b"Motor[7].pos\n",
b"#3?\n",
b"EncTable[6].PrevEnc\n",
b"#2?\n",
b"#16;p\n",
b"#4;p\n",
b"Motor[7].idCmd\n",
b"Motor[3].Ctrl\n",
b"Motor[1].pos\n",
b"Motor[6].Ctrl\n",
}
c = ""
for k in cmd:
c += k.decode()
fn = "/tmp/PBComm.log"
fh = open(fn, "w")
fh.write(c + "\n\n\n***\n")
for i in range(100):
chan.send(c)
res = gpascii.read_until_endswith(SSHComm.gpascii_ack)
fh.write(res)
print(i, len(res))
print(fn + " generated.")
+379
View File
@@ -0,0 +1,379 @@
import subprocess
from threading import Thread
from epics import PV, ca
import time
from ..eco_epics import device
from ..eco_epics.device import Device
from .utilities import Changer
from ..aliases import Alias
_guiTypes = ["xdm"]
_status_messages = {
-13: "invalid value (cannot convert to float). Move not attempted.",
-12: "target value outside soft limits. Move not attempted.",
-11: "drive PV is not connected: Move not attempted.",
-8: "move started, but timed-out.",
-7: "move started, timed-out, but appears done.",
-5: "move started, unexpected return value from PV.put()",
-4: "move-with-wait finished, soft limit violation seen",
-3: "move-with-wait finished, hard limit violation seen",
0: "move-with-wait finish OK.",
0: "move-without-wait executed, not cpmfirmed",
1: "move-without-wait executed, move confirmed",
3: "move-without-wait finished, hard limit violation seen",
4: "move-without-wait finished, soft limit violation seen",
}
def _keywordChecker(kw_key_list_tups):
for tkw, tkey, tlist in kw_key_list_tups:
assert tkey in tlist, "Keyword %s should be one of %s" % (tkw, tlist)
class SmarActException(Exception):
""" raised to indicate a problem with a smartact"""
def __init__(self, msg, *args):
Exception.__init__(self, *args)
self.msg = msg
def __str__(self):
return str(self.msg)
class SmarAct(Device):
_extras = {"disabled": "_able.VAL"}
_init_list = ("VAL", "DESC", "RTYP")
_nonpvs = ("_prefix", "_pvs", "_delim", "_init", "_init_list", "_alias", "_extras")
def __init__(self, name=None, timeout=3.0, record=None):
if name is None:
raise SmarActException("must supply SmarAct name")
if name.endswith(".VAL"):
name = name[:-4]
if name.endswith("."):
name = name[:-1]
self._prefix = name
self._record = record
self._callbacks = {}
device.Device.__init__(
self, name, delim=".", attrs=self._init_list, timeout=timeout
)
# for key, val in self._extras.items():
# pvname = "%s%s" % (name, val)
# self.add_pv(pvname, attr=key)
# self.put('disabled', 0)
class SmarActRecord:
def __init__(
self,
Id,
name=None,
elog=None,
alias_fields={"readback": "MOTRBV", "homed": "GET_HOMED"},
):
self.Id = Id
self._drive = SmarAct(Id + ":DRIVE")
self._rbv = SmarAct(Id + ":MOTRBV")
self._hlm = SmarAct(Id + ":HLM")
self._llm = SmarAct(Id + ":LLM")
self._statusstg = SmarAct(Id + ":STATUS")
self._status = []
self._set_pos = SmarAct(Id + ":SET_POS")
self._stop = SmarAct(Id + ":STOP")
self._hold = SmarAct(Id + ":HOLD")
self._twv = SmarAct(Id + ":TWV")
self._elog = elog
self.name = name
self.units = self._drive.get("EGU")
self.alias = Alias(name)
for an, af in alias_fields.items():
self.alias.append(
Alias(an, channel=".".join([self.Id, af]), channeltype="CA")
)
# Conventional methods and properties for all Adjustable objects
def changeTo(self, value, hold=False, check=True):
""" Adjustable convention"""
def changer(value):
self._status = self.move(value, ignore_limits=(not check), wait=True)
self._status_message = _status_messages[self._status]
if not self._status == 0:
#print(self._status_message)
# mover = lambda value: self.move(\
# value, ignore_limits=(not check),
# wait=True)
return Changer(
target=value,
parent=self,
changer=changer,
hold=hold,
stopper=self._stop.put("PROC", 1),
)
def stop(self):
""" Adjustable convention"""
try:
self._currentChange.stop()
except:
self._stop.put("VAL", 1)
pass
def within_limits(self, val):
""" returns whether a value for a motor is within drive limits"""
return val <= self._hlm.get("VAL") and val >= self._llm.get("VAL")
def move(
self,
val,
relative=False,
wait=False,
timeout=300.0,
ignore_limits=False,
confirm_move=False,
):
""" moves smaract drive to position
arguments:
==========
val value to move to (float) [Must be provided]
relative move relative to current position (T/F) [F]
wait whether to wait for move to complete (T/F) [F]
ignore_limits try move without regard to limits (T/F) [F]
confirm_move try to confirm that move has begun (T/F) [F]
timeout max time for move to complete (in seconds) [300]
return values:
-13 : invalid value (cannot convert to float). Move not attempted.
-12 : target value outside soft limits. Move not attempted.
-11 : drive PV is not connected: Move not attempted.
-8 : move started, but timed-out.
-7 : move started, timed-out, but appears done.
-5 : move started, unexpected return value from PV.put()
-4 : move-with-wait finished, soft limit violation seen
-3 : move-with-wait finished, hard limit violation seen
0 : move-with-wait finish OK.
0 : move-without-wait executed, not cpmfirmed
1 : move-without-wait executed, move confirmed
3 : move-without-wait finished, hard limit violation seen
4 : move-without-wait finished, soft limit violation seen
"""
NONFLOAT, OUTSIDE_LIMITS, UNCONNECTED = -13, -12, -11
TIMEOUT = -8
UNKNOWN_ERROR = -5
DONE_OK = 0
MOVE_BEGUN, MOVE_BEGUN_CONFIRMED = 0, 1
try:
val = float(val)
except TypeError:
return NONFLOAT
if relative:
val += self._drive.get("VAL")
# Check for limit violations
if not ignore_limits:
if not self.within_limits(val):
return OUTSIDE_LIMITS
stat = self._drive.put("VAL", val, wait=wait, timeout=timeout)
if stat is None:
return UNCONNECTED
if wait and stat == -1:
return TIMEOUT
if 1 == stat:
s0 = self._statusstg.get("VAL")
s1 = s0
t0 = time.time()
t1 = t0 + min(10.0, timeout) # should be moving by now
thold = self._hold.get("VAL") * 0.001 + t0
tout = t0 + timeout
if wait or confirm_move:
while time.time() <= thold and s1 == 3:
ca.poll(evt=1.e-2)
s1 = self._statusstg.get("VAL")
while time.time() <= t1 and s1 == 0:
ca.poll(evt=1.e-2)
s1 = self._statusstg.get("VAL")
if s1 == 4:
if wait:
while time.time() <= tout and s1 == 4:
ca.poll(evt=1.e-2)
s1 = self._statusstg.get("VAL")
if s1 == 3 or s1 == 4:
if time.time() > tout:
return TIMEOUT
else:
twv = abs(self._twv.get("VAL"))
while (
s1 == 3
and time.time() <= tout
and abs(self._rbv.get("VAL") - val) >= twv
):
ca.poll(evt=1.e-2)
return DONE_OK
else:
return MOVE_BEGUN_CONFIRMED
elif time.time() > tout:
return TIMEOUT
else:
return UNKNOWN_ERROR
else:
return MOVE_BEGUN
return UNKNOWN_ERROR
def get_current_value(self, readback=True):
if readback:
return self._rbv.get("VAL")
else:
return self._drive.get("VAL")
def set_current_value(self, value):
return self._set_pos.put("VAL", value)
def get_precision(self):
""" Adjustable convention"""
pass
def set_precision(self):
""" Adjustable convention"""
pass
precision = property(get_precision, set_precision)
def set_speed(self):
""" Adjustable convention"""
pass
def get_speed(self):
""" Adjustable convention"""
pass
def set_speedMax(self):
""" Adjustable convention"""
pass
def get_moveDone(self):
pass
def set_limits(self, values, posType="user", relative_to_present=False):
""" Adjustable convention"""
if relative_to_present:
v = self.get_current_value()
values = [v - values[0], v - values[1]]
self._llm.put("VAL", values[0])
self._hlm.put("VAL", values[1])
def get_limits(self, posType="user"):
""" Adjustable convention"""
return self._llm.get("VAL"), self._hlm.get("VAL")
def gui(self, guiType="xdm"):
""" Adjustable convention"""
cmd = ["caqtdm", "-macro"]
for i in range(len(self.Id) - 1):
if self.Id[-i - 1].isnumeric() is False:
M = self.Id[-i:]
P = self.Id[:-i]
print(P, M)
break
cmd.append('"P=%s,M=%s"' % (P, M))
# #cmd.append('/sf/common/config/qt/motorx_more.ui')
cmd.append("ESB_MX_SMARACT_mot_exp.ui")
# #os.system(' '.join(cmd))
return subprocess.Popen(" ".join(cmd), shell=True)
def mv(self, value):
self._currentChange = self.changeTo(value)
def wm(self, *args, **kwargs):
return self.get_current_value(*args, **kwargs)
def mvr(self, value, *args, **kwargs):
startvalue = self.get_current_value(readback=True, *args, **kwargs)
self._currentChange = self.changeTo(value + startvalue, *args, **kwargs)
def wait(self):
self._currentChange.wait()
# return string with motor value as variable representation
def __str__(self):
return "SmarAct is at %s" % (self.wm())
# return "SmarAct is at %s %s"%(self.wm(),self.units)
def __repr__(self):
return self.__str__()
def __call__(self, value):
self._currentChange = self.changeTo(value)
class SmarActDevice(SmarActRecord):
def __init__(self, Id, alias_namespace=None):
SmarActRecord.__init__(self, Id)
# self.Id = Id
#
# self.x = SmarActRecord(Id+':DRIVE')
class SmarActStage:
def __init__(self, axes, name):
self._keys = axes.keys()
for axis in self._keys:
self.__dict__[axis] = axes[axis]
self.name = name
def __str__(self):
return "SmarAct positions\n%s" % "\n".join(
["%s: %s" % (key, self.__dict__[key].wm()) for key in self._keys]
)
def __repr__(self):
return str({key: self.__dict__[key].wm() for key in self._keys})
class Changer_old:
def __init__(self, target=None, parent=None, mover=None, hold=True, stopper=None):
self.target = target
self._mover = mover
self._stopper = stopper
self._thread = Thread(target=self._mover, args=(target,))
if not hold:
self._thread.start()
def wait(self):
self._thread.join()
def start(self):
self._thread.start()
def status(self):
if self._thread.ident is None:
return "waiting"
else:
if self._isAlive:
return "changing"
else:
return "done"
def stop(self):
self._stopper()
+4 -141
View File
@@ -1,147 +1,10 @@
import subprocess
from threading import Thread
from epics import PV
from .utilities import Changer
# exceptions
def _keywordChecker(kw_key_list_tups):
for tkw, tkey, tlist in kw_key_list_tups:
assert tkey in tlist, "Keyword %s should be one of %s" % (tkw, tlist)
# wrappers for adjustables >>>>>>>>>>>
class ValueRdback:
def __init__(self, pv_value, pv_readback, name=None, elog=None):
self.Id = pvname
self._PV_value = PV(pv_value)
self._PV_readback = PV(pv_readback)
self._elog = elog
self.name = name
self._currentChange = None
# Conventional methods and properties for all Adjustable objects
def changeTo(self, value, hold=False, check=True):
""" Adjustable convention"""
def changer(value):
self._status = self._motor.move(value, ignore_limits=(not check), wait=True)
self._status_message = _status_messages[self._status]
if not self._status == 0:
print(self._status_message)
# changer = lambda value: self._motor.move(\
# value, ignore_limits=(not check),
# wait=True)
return Changer(
target=value,
parent=self,
changer=changer,
hold=hold,
stopper=self._motor.stop,
)
def stop(self):
""" Adjustable convention"""
try:
self._currentChange.stop()
except:
self._motor.stop()
pass
def get_current_value(self):
""" Adjustable convention"""
return self._PV_readback.get()
def set_current_value(self, value):
""" Adjustable convention"""
print("not implemented: depends on a defined offset")
def get_precision(self):
""" Adjustable convention"""
if isinstance(self._precision, PV):
return self._precision.get()
else:
return self._precision
def set_precision(self, value):
""" Adjustable convention"""
if isinstance(self._precision, PV):
self._precision.put(value)
else:
self._precision = value
precision = property(get_precision, set_precision)
def set_speed(self):
""" Adjustable convention"""
pass
def get_speed(self):
""" Adjustable convention"""
pass
def set_speedMax(self):
""" Adjustable convention"""
pass
def get_moveDone(self):
""" Adjustable convention"""
""" 0: moving 1: move done"""
return PV(str(self.Id + ".DMOV")).value
def set_limits(self, values, posType="user", relative_to_present=False):
""" Adjustable convention"""
_keywordChecker([("posType", posType, _posTypes)])
ll_name, hl_name = "LLM", "HLM"
if posType is "dial":
ll_name, hl_name = "DLLM", "DHLM"
if relative_to_present:
v = self.get_current_value(posType=posType)
values = [v - values[0], v - values[1]]
self._motor.put(ll_name, values[0])
self._motor.put(hl_name, values[1])
def get_limits(self, posType="user"):
""" Adjustable convention"""
_keywordChecker([("posType", posType, _posTypes)])
ll_name, hl_name = "LLM", "HLM"
if posType is "dial":
ll_name, hl_name = "DLLM", "DHLM"
return self._motor.get(ll_name), self._motor.get(hl_name)
# return string with motor value as variable representation
def __str__(self):
return "Motor is at %s" % self.wm()
def __repr__(self):
return self.__str__()
def __call__(self, value):
self._currentChange = self.changeTo(value)
# wrappers for adjustables <<<<<<<<<<<
class ChangerOld:
def __init__(self, target=None, parent=None, mover=None, hold=True, stopper=None):
self.target = target
self._mover = mover
self._stopper = stopper
self._thread = Thread(target=self._mover, args=(target,))
if not hold:
self._thread.start()
def wait(self):
self._thread.join()
def start(self):
self._thread.start()
def status(self):
if self._thread.ident is None:
return "waiting"
else:
if self._isAlive:
return "changing"
else:
return "done"
def stop(self):
self._stopper()
# @default_representation
+10 -9
View File
@@ -1,15 +1,12 @@
import numpy as np
from epics import caget
from epics import PV
from ..eco_epics.utilities_epics import EnumWrapper
from eco.epics.utilities_epics import EnumWrapper
from cam_server import PipelineClient
from cam_server.utils import get_host_port_from_stream_address
from bsread import source, SUB
import subprocess
import h5py
from time import sleep
from threading import Thread
from datetime import datetime
from ..acquisition.utilities import Acquisition
@@ -63,7 +60,7 @@ class CameraCA:
f["images"] = np.asarray(d)
def gui(self, guiType="xdm"):
""" Adjustable convention"""
"""Adjustable convention"""
cmd = ["caqtdm", "-macro"]
cmd.append('"NAME=%s,CAMNAME=%s"' % (self.Id, self.Id))
@@ -483,7 +480,9 @@ class DIAClient:
print("ERROR: please configure the instrument parameter in DIAClient")
self.update_config()
def update_config(self,):
def update_config(
self,
):
self.writer_config = {
"output_file": "/sf/%s/data/p%d/raw/test_data.h5"
% (self.instrument, self.pgroup),
@@ -553,8 +552,8 @@ class DIAClient:
"general/process": __name__,
"general/created": str(datetime.now()),
"general/instrument": self.instrument,
#'Npulses':100,
#'channels': default_channels_list
# 'Npulses':100,
# 'channels': default_channels_list
}
# self.default_channels_list = jungfrau_utils.load_default_channel_list()
@@ -574,7 +573,9 @@ class DIAClient:
self.pgroup = pgroup
self.update_config()
def set_bs_channels(self,):
def set_bs_channels(
self,
):
print(
"Please update /sf/%s/config/com/channel_lists/default_channel_list and restart all services on the DAQ server"
% self.instrument
+9 -9
View File
@@ -58,7 +58,7 @@ class Storage(object):
class Pockels_trigger(PV):
""" this class is needed to store the offset in files and read in s """
"""this class is needed to store the offset in files and read in s"""
def __init__(self, pv_basename):
pvname = pv_basename + "-RB"
@@ -75,7 +75,7 @@ class Pockels_trigger(PV):
return super().get() * 1e-6
def get(self):
""" convert time to sec """
"""convert time to sec"""
return self.get_dial() - self.offset
def store(self, value=None):
@@ -98,7 +98,7 @@ class Pockels_trigger(PV):
class Phase_shifter(PV):
""" this class is needed to store the offset in files and read in ps """
"""this class is needed to store the offset in files and read in ps"""
def __init__(self, pv_basename="SLAAR01-TSPL-EPL"):
pvname = pv_basename + ":CURR_DELTA_T"
@@ -116,7 +116,7 @@ class Phase_shifter(PV):
return super().get() * 1e-12
def get(self):
""" convert time to sec """
"""convert time to sec"""
return self.get_dial() - self.offset
def store(self, value=None):
@@ -155,18 +155,18 @@ class PhaseShifterAramis:
self._elog = elog
self.name = name
def changeTo(self, value, hold=False, check=True):
""" Adjustable convention"""
def set_target_value(self, value, hold=False, check=True):
"""Adjustable convention"""
mover = lambda value: self._pshifter.move(value)
return Changer(target=value, parent=self, mover=mover, hold=hold, stopper=None)
def stop(self):
""" Adjustable convention"""
"""Adjustable convention"""
pass
def get_current_value(self, posType="user", readback=True):
""" Adjustable convention"""
"""Adjustable convention"""
_keywordChecker([("posType", posType, _posTypes)])
if posType == "user":
return self._pshifter.get()
@@ -174,7 +174,7 @@ class PhaseShifterAramis:
return self._pshifter.get_dial()
def set_current_value(self, value, posType="user"):
""" Adjustable convention"""
"""Adjustable convention"""
_keywordChecker([("posType", posType, _posTypes)])
if posType == "user":
return self._motor.set(value)
+299
View File
@@ -0,0 +1,299 @@
import requests
import time
from ..elements.assembly import Assembly
from ..elements.adjustable import AdjustableGetSet, Tweak
from numpy import polyval
import numpy as np
import urllib.request
import io
from PIL import Image
import os
from timg import Renderer, Ansi24HblockMethod
from shutil import get_terminal_size
from enum import IntEnum, auto
# function ptz_slider_onChange(group)
# {
# if ((group == "pan" && "abs" == "rel") ||
# (group == "tilt" && "abs" == "rel") ||
# (group == "zoom" && "abs" == "rel") ||
# (group == "focus" && "no" == "rel") ||
# (group == "brightness" && "" == "rel") ||
# (group == "iris" && "abs" == "rel")) {
# group = "r" + group;
# }
# if (theCameraNumber == "") theCameraNumber = "1";
# var url = "/axis-cgi/com/ptz.cgi?camera="+theCameraNumber+"&"+group+"="+parseFloat(Math.round(theNewSliderValue * 10)/10);
# }
#
# Looks like 'pan' is an absolute pan where 'rpan' is a relative pan.
#
# curl 'http://<<camera address>>/axis-cgi/com/ptz.cgi?camera=1&continuouspantiltmove=0,0&imagerotation=0&timestamp=1491243098477'
AUTOFOCUS = IntEnum("autofocus", {"on": 1, "off": 0})
AUTOIRIS = IntEnum("autoiris", {"on": 1, "off": 0})
class AxisPTZ(Assembly):
def __init__(
self,
camera_address,
name="dummycam",
timeout=0.1,
tweak_steps=[-3, -3],
):
super().__init__(name=name)
self.camera_address = camera_address
self.camera_n = 1
self.camera_ir = 0
self.timeout = timeout
try:
self.get_position()
except:
raise Exception(f"Could not connect to camera {self.name}!!")
self._append(
AdjustableGetSet,
lambda: polyval([0.00290058, 0.99709942], self.get_position()["zoom"]),
lambda val: self.set_par(
"zoom", int(polyval([344.75862069, -343.75862069], val))
),
precision=10 / 9999 * 30,
check_interval=0.05,
name="zoom",
is_setting=True,
)
self._append(
AdjustableGetSet,
lambda: self.get_position()["tilt"],
lambda val: self.set_par("tilt", val),
name="tilt",
is_setting=True,
)
self._append(
AdjustableGetSet,
lambda: self.get_position()["pan"],
lambda val: self.set_par("pan", val),
name="pan",
is_setting=True,
)
self._append(
AdjustableGetSet,
lambda: (self.get_position()["iris"] - 1) / 9995,
lambda val: self.set_par("iris", val * 9995 + 1),
name="iris",
is_setting=True,
)
self._append(
AdjustableGetSet,
lambda: (self.get_position()["focus"] - 750) / (9999 - 750),
lambda val: self.set_par("focus", val * (9999 - 750) + 750),
name="focus",
is_setting=True,
)
self._append(
AdjustableGetSet,
lambda: AUTOFOCUS.__dict__[self.get_position()["autofocus"]],
lambda val: self.set_par("autofocus", AUTOFOCUS(val).name),
name="autofocus",
is_setting=True,
)
self._append(
AdjustableGetSet,
lambda: AUTOFOCUS.__dict__[self.get_position()["autoiris"]],
lambda val: self.set_par("autoiris", AUTOFOCUS(val).name),
name="autoiris",
is_setting=True,
)
self._tweak_steps = tweak_steps
def tweak(self):
t = Tweak([self.pan, self._tweak_steps[0]], [self.tilt, self._tweak_steps[1]])
t.xy_adjustable_tweak()
# camera_n = 1
# camera_url = 'http://<<camera address>>/axis-cgi/com/ptz.cgi'
# camera_ir = 0
# presets = {
# 'Home':
# {
# 'pan' : -120.7629,
# 'tilt' : -4.8568,
# 'zoom' : 696.0,
# 'brightness' : 3333.0,
# #'autofocus' : 'on',
# #'autoiris' : 'on',
# #'focus' : 6424.0, # generates error
# #'iris' : 2739.0,
# },
# 'Mt. Washington':
# #value="tilt=154749:focus=32766.000000:pan=267468:iris=32766.000000:zoom=11111.000000"
# {
# 'pan' : 156.7195,
# 'tilt' : -0.6732,
# 'zoom' : 11111.0,
# #'autofocus' : 'on',
# #'autoiris' : 'on',
# #'focus' : 7964.0,
# #'iris' : 2583.0,
# }
# }
@property
def camera_url(self):
return f"http://{self.camera_address}/axis-cgi/com/ptz.cgi"
def get_image(self, filename=None, as_array=False):
img_url = f"http://{self.camera_address}/jpg/image.jpg"
with urllib.request.urlopen(img_url) as url:
f = io.BytesIO(url.read())
img = Image.open(f)
if as_array:
return np.asarray(img)
else:
return img
def show(self, in_terminal=True):
r = Renderer()
r.load_image(self.get_image())
r.resize(get_terminal_size()[0])
r.render(Ansi24HblockMethod)
def cameraCmd(self, q_cmd):
resp_data = {}
base_q_args = {
"camera": self.camera_n,
"imagerotation": self.camera_ir,
"html": "no",
"timestamp": int(time.time()),
}
q_args = merge_dicts(q_cmd, base_q_args)
resp = requests.get(self.camera_url, params=q_args, timeout=self.timeout)
if resp.text.startswith("Error"):
print(resp.text)
else:
for line in resp.text.splitlines():
(name, var) = line.split("=", 2)
try:
resp_data[name.strip()] = float(var)
except ValueError:
resp_data[name.strip()] = var
return resp_data
def get_par(self, query):
# print("cameraGet(" + query + ")")
return self.cameraCmd({"query": query})
# resp_data = {}
# q_args = { 'query': query,
# 'camera': camera_n, 'imagerotation': camera_ir,
# 'html': 'no', 'timestamp': int(time.time())
# }
# resp = requests.get(camera_url, params=q_args)
# for line in resp.text.splitlines():
# (name, var) = line.split("=", 2)
# try:
# resp_data[name.strip()] = float(var)
# except ValueError:
# resp_data[name.strip()] = var
#
# return resp_data
def set_par(self, group, val):
print(val)
# print("cameraSet(" + group + ", " + str(val) + ")")
return self.cameraCmd({group: val})
# resp_data = {}
# q_args = { group: val,
# 'camera': camera_n, 'imagerotation': camera_ir,
# 'html': 'no', 'timestamp': int(time.time())
# }
#
# resp = requests.get(camera_url, params=q_args)
# for line in resp.text.splitlines():
# (name, var) = line.split("=", 2)
# try:
# resp_data[name.strip()] = float(var)
# except ValueError:
# resp_data[name.strip()] = var
#
# return resp_data
# def cameraGoToPreset(preset_name):
# preset = presets[preset_name]
# if preset != None:
# for key, value in preset.items():
# cameraSet(key, value)
def home(self):
return self.cameraCmd({"move": "home"})
def get_position(self):
return self.get_par("position")
def get_limits(self):
return self.get_par("limits")
def set_pan(self, value):
return self.set_par("pan", value)
def set_tilt(self, value):
return self.set_par("tilt", value)
def set_zoom(self, value):
return self.set_par("zoom", value)
def set_panrelative(self, value):
return self.set_par("rpan", value)
def set_tiltrelative(self, value):
return self.set_par("rtilt", value)
def set_zoomrelative(self, value):
return self.set_par("rzoom", value)
# print("Move to home...")
# print(cameraHome())
# #time.sleep(1)
# print("Get PTZ and Limits...")
# print(cameraGetPTZ())
# print(cameraGetLimits())
# for i in range(5):
# time.sleep(1)
# print("Move left...")
# print(cameraPanRelative(-1))
# for i in range(5):
# time.sleep(1)
# print("Move right...")
# print(cameraPanRelative(1))
# print(cameraTilt(-1))
# time.sleep(5)
# print("Show Mt. Washington...")
# print(cameraGoToPreset('Mt. Washington'))
# time.sleep(2)
# print("Move back home...")
# print(cameraHome())
# print(cameraGoToPreset('Home'))
def merge_dicts(*dict_args):
"""
Given any number of dicts, shallow copy and merge into a new dict,
precedence goes to key value pairs in latter dicts.
"""
result = {}
for dictionary in dict_args:
result.update(dictionary)
return result
+751
View File
@@ -0,0 +1,751 @@
from cam_server import CamClient, PipelineClient
from matplotlib.backend_bases import MouseButton
from eco.devices_general.utilities import Changer
from eco.epics.detector import DetectorPvData, DetectorPvEnum
from ..aliases import Alias, append_object_to_object
from ..elements.adjustable import AdjustableVirtual, AdjustableGetSet, value_property
from eco.elements.detector import DetectorGet
from ..epics.adjustable import AdjustablePv, AdjustablePvEnum
from eco.elements.adj_obj import AdjustableObject, DetectorObject
from .pipelines_swissfel import Pipeline
from ..elements.assembly import Assembly
from .motors import MotorRecord
import sys
from pathlib import Path
import time
import matplotlib.pyplot as plt
import numpy as np
sys.path.append("/sf/bernina/config/src/python/sf_databuffer/")
import bufferutils
CAM_CLIENT = None
PIPELINE_CLIENT = None
def get_camclient():
global CAM_CLIENT
if not CAM_CLIENT:
CAM_CLIENT = CamClient()
CAM_CLIENT.timeout = 8
return CAM_CLIENT
def get_pipelineclient():
global PIPELINE_CLIENT
if not PIPELINE_CLIENT:
PIPELINE_CLIENT = PipelineClient()
PIPELINE_CLIENT.timeout = 8
return PIPELINE_CLIENT
@value_property
class CamserverConfig2(Assembly):
def __init__(self, cam_id, camserver_alias=None, name=None, camserver_group=None):
super().__init__(name=name)
self.cam_id = cam_id
self.camserver_alias = camserver_alias
self.camserver_group = camserver_group
self._cross = None
self._append(
AdjustableGetSet,
self._get_config,
self._set_config,
cache_get_seconds=0.05,
precision=0,
check_interval=None,
name="_config",
is_setting=True,
is_display=False,
)
self._append(
AdjustableObject,
self._config,
name="config",
is_setting=False,
is_display="recursive",
)
self._append(
DetectorGet,
self._get_info,
cache_get_seconds=0.05,
name="_info",
is_setting=False,
is_display=False,
)
self._append(
DetectorObject,
self._info,
name="info",
is_display="recursive",
is_setting=False,
)
@property
def pc(self):
return get_pipelineclient()
@property
def cc(self):
return get_camclient()
def _get_config(self):
return self.cc.get_camera_config(self.cam_id)
def _set_config(self, value, hold=False):
return Changer(
target=value,
changer=lambda v: self.cc.set_camera_config(self.cam_id, v),
hold=hold,
)
def _get_info(self):
fields = {
"camera_geometry": self.cc.get_camera_geometry(self.cam_id),
"pipelines": self._get_pipelines(),
}
return fields
### convenience functions ###
def get_camera_image(self):
im = self.cc.get_camera_array(self.cam_id)
return im
def set_alias(self, alias=None):
"""creates an alias in the camera config on the server. If no alias is provided, it defaults to the camera name"""
if not alias:
alias = self.camserver_alias
self.set_config_fields({"alias": [alias.upper()]})
def set_group(self, group=None):
"""adds the camera to the given group"""
if not group:
group = self.camserver_group
self.config.group(group)
def _get_pipelines(self):
return [p for p in self.pc.get_pipelines() if self.cam_id in p]
def set_config_fields(self, fields):
"""fields is a dictionary containing the keys and values that should be updated, e.g. fields={'group': ['Laser', 'Bernina']}"""
config = self.cc.get_camera_config(self.cam_id)
config.update(fields)
self.cc.set_camera_config(self.cam_id, config)
def set_config_fields_multiple_cams(self, conditions, fields):
"""
conditions is a dictionary holding the conditions to select a subset of cameras, e.g. {"group": Bernina}
fields is a dictionary containing the keys and values that should be updated, e.g. fields={'alias': ['huhu', 'duda']}
"""
cams = {
cam: self.cc.get_camera_config(cam)
for cam in self.cc.get_cameras()
if not "jungfrau" in cam
}
cams_selected = {}
for cam, cfg in cams.items():
try:
if all([value in cfg[key] for key, value in conditions.items()]):
cfg.update(fields)
self.cc.set_camera_config(cam, cfg)
cams_selected[cam] = cfg
except Exception as e:
print(f"{type(e)} {e} in cam {cam}")
return cams_selected
def clear_all_bernina_aliases(self, verbose=True):
cams_selected = self.set_config_fields_multiple_cams(
conditions={"group": "Bernina"}, fields={"alias": []}
)
if verbose:
print(f"Reset alias of {len(cams_selected)} cameras")
print(cams_selected.keys())
def _run_cmd(self, line, silent=True):
if silent:
print(f"Starting following commandline silently:\n" + line)
with open(os.devnull, "w") as FNULL:
subprocess.Popen(
line, shell=True, stdout=FNULL, stderr=subprocess.STDOUT
)
else:
subprocess.Popen(line, shell=True)
def gui(self):
self._run_cmd(f"csm")
@value_property
class CamserverConfig(Assembly):
def __init__(self, cam_id, camserver_alias=None, name=None, camserver_group=None):
super().__init__(name=name)
self.cam_id = cam_id
self.camserver_alias = camserver_alias
self.camserver_group = camserver_group
@property
def cc(self):
return get_camclient()
@property
def pc(self):
return get_pipelineclient()
def get_current_value(self):
return self.cc.get_camera_config(self.cam_id)
def set_target_value(self, value, hold=False):
return Changer(
target=value,
changer=lambda v: self.cc.set_camera_config(self.cam_id, v),
hold=hold,
)
def set_config_fields(self, fields):
"""fields is a dictionary containing the keys and values that should be updated, e.g. fields={'group': ['Laser', 'Bernina']}"""
config = self.get_current_value()
config.update(fields)
self.cc.set_camera_config(self.cam_id, config)
### convenience functions ###
def set_alias(self, alias=None):
"""creates an alias in the camera config on the server. If no alias is provided, it defaults to the camera name"""
if not alias:
alias = self.camserver_alias
self.set_config_fields({"alias": [alias.upper()]})
def set_group(self, group=None):
"""creates an alias in the camera config on the server. If no alias is provided, it defaults to the camera name"""
if not group:
group = self.camserver_group
self.set_config_fields({"group": group})
def restart_pipeline(self):
base_directory = "/sf/bernina/config/src/python/sf_databuffer/"
label = self.cam_id
policies = bufferutils.read_files(base_directory / Path("policies"), "policies")
sources = bufferutils.read_files(base_directory / Path("sources"), "sources")
sources_new = sources.copy()
# Only for debugging purposes
labeled_sources = bufferutils.get_labeled_sources(sources_new, label)
for s in labeled_sources:
bufferutils.logging.info(f"Restarting {s['stream']}")
sources_new = bufferutils.remove_labeled_source(sources_new, label)
# Stopping the removed source(s)
bufferutils.update_sources_and_policies(sources_new, policies)
# Starting the source(s) again
bufferutils.update_sources_and_policies(sources, policies)
def stop(self):
self.cc.stop_instance(self.cam_id)
def set_cross(self, x, y, x_um_per_px=None, y_um_per_px=None):
"""set x and y position of the refetence marker on a camera px/um calibration is conserved if no new value is given"""
calib = self.get_current_value()["camera_calibration"]
if calib:
if not x_um_per_px:
x_um_per_px = calib["reference_marker_width"] / abs(
calib["reference_marker"][2] - calib["reference_marker"][0]
)
if not y_um_per_px:
y_um_per_px = calib["reference_marker_height"] / abs(
calib["reference_marker"][3] - calib["reference_marker"][1]
)
else:
calib = {}
x_um_per_px = 1
y_um_per_px = 1
# calib["reference_marker"] = [x - 1, y - 1, x + 1, y + 1]
calib["reference_marker_width"] = 2 * x_um_per_px
calib["reference_marker_height"] = 2 * y_um_per_px
self.set_config_fields(fields={"camera_calibration": calib})
def set_config_fields_multiple_cams(self, conditions, fields):
"""
conditions is a dictionary holding the conditions to select a subset of cameras, e.g. {"group": Bernina}
fields is a dictionary containing the keys and values that should be updated, e.g. fields={'alias': ['huhu', 'duda']}
"""
cams = {
cam: self.cc.get_camera_config(cam)
for cam in self.cc.get_cameras()
if not "jungfrau" in cam
}
cams_selected = {}
for cam, cfg in cams.items():
try:
if all([value in cfg[key] for key, value in conditions.items()]):
cfg.update(fields)
self.cc.set_camera_config(cam, cfg)
cams_selected[cam] = cfg
except Exception as e:
print(f"{type(e)} {e} in cam {cam}")
return cams_selected
def clear_all_bernina_aliases(self, verbose=True):
cams_selected = self.set_config_fields_multiple_cams(
conditions={"group": "Bernina"}, fields={"alias": []}
)
if verbose:
print(f"Reset alias of {len(cams_selected)} cameras")
print(cams_selected.keys())
def __repr__(self):
s = f"**Camera Server Config {self.cam_id} with Alias {self.name}**\n"
for key, item in self.get_current_value().items():
s += f"{key:20} : {item}\n"
return s
class CameraBasler(Assembly):
def __init__(
self,
pvname,
camserver_alias=None,
name=None,
camserver_group=None,
connect_camserver=True,
):
super().__init__(name=name)
self.pvname = pvname
if not camserver_alias:
camserver_alias = self.alias.get_full_name() + f" ({pvname})"
else:
camserver_alias = camserver_alias + f" ({pvname})"
if connect_camserver:
self._append(
CamserverConfig2,
self.pvname,
camserver_alias=camserver_alias,
camserver_group=camserver_group,
name="config_cs",
is_display=True,
is_setting=True,
)
self.config_cs.set_alias()
if camserver_group is not None:
self.config_cs.set_group()
self._append(
AdjustablePvEnum,
self.pvname + ":INIT",
name="initialize",
is_setting=True,
is_display=False,
)
self._append(
DetectorPvEnum,
self.pvname + ":BUSY_INIT",
name="is_initializing",
is_setting=False,
is_display=False,
)
self._append(
AdjustablePvEnum,
self.pvname + ":CAMERASTATUS",
name="cam_status",
is_setting=False,
is_display=True,
)
self._append(
AdjustablePv,
self.pvname + ":BOARD",
name="board_no",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ":SERIALNR",
name="serial_no",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ":EXPOSURE",
name="_exposure_time",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePvEnum,
self.pvname + ":ACQMODE",
name="_acq_mode",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePvEnum,
self.pvname + ":RECMODE",
name="_req_mode",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePvEnum,
self.pvname + ":STOREMODE",
name="_store_mode",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ":BINX",
name="_binx",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ":BINY",
name="_biny",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ":REGIONX_START",
name="_roixmin",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ":REGIONX_END",
name="_roixmax",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ":REGIONY_START",
name="_roiymin",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ":REGIONY_END",
name="_roiymax",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePvEnum,
self.pvname + ":SET_PARAM",
name="_set_parameters",
is_setting=True,
is_display=False,
)
self._append(
DetectorPvData,
self.pvname + ":DEVICEFREQUENCY",
has_unit=True,
name="frequency",
is_setting=False,
is_display=True,
)
self._append(
AdjustablePvEnum,
self.pvname + ":SW_PULSID_SRC",
name="bscheck",
is_setting=True,
is_display=True,
)
self._append(
DetectorPvData,
self.pvname + ":ERRORCOUNTER",
name="pulse_id_error_sum",
is_setting=False,
is_display=True,
)
self._append(
DetectorPvData,
self.pvname + ":FEEDBACKTIME0",
name="response_time_bs",
is_setting=False,
is_display=True,
)
self._append(
AdjustablePv,
self.pvname + ":AMPGAIN",
name="_gain",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePvEnum,
self.pvname + ":TRIGGER",
name="trigger_on",
)
self._append(
AdjustablePvEnum,
self.pvname + ":TRIGGERSOURCE",
name="trigger_source",
is_setting=True,
is_display=False,
)
# append_object_to_object(self,PvEnum,self.pvname+':TRIGGEREDGE',name='trigger_edge')
self._append(
AdjustableGetSet,
self._exposure_time.get_current_value,
lambda value: self._set_params((self._exposure_time, value)),
name="exposure_time",
unit="ms",
is_setting=True,
is_display=True,
)
self._append(
AdjustableGetSet,
self._gain.get_current_value,
lambda value: self._set_params((self._gain, value)),
name="gain",
is_setting=True,
is_display=True,
)
def set_roi(roi):
self._set_params(
[self._roixmin, roi[0]],
[self._roixmax, roi[1]],
[self._roiymin, roi[2]],
[self._roiymax, roi[3]],
)
return (roi[0], roi[1], roi[2], roi[3])
self._append(
AdjustableVirtual,
[self._roixmin, self._roixmax, self._roiymin, self._roiymax],
lambda x_from, x_to, y_from, y_to: [x_from, x_to, y_from, y_to],
set_roi,
name="roi",
is_setting=True,
)
def _set_params(self, *args):
self.cam_status(1)
for ob, val in args:
ob(val)
self._set_parameters(1)
self.cam_status(2)
def re_initialize(self, wait_before_init=1, wait_for_init=3):
self.cam_status(0)
time.sleep(wait_before_init)
self.cam_status(1)
time.sleep(wait_for_init)
self.cam_status(2)
def get_camera_images(self, n):
imgs = []
while len(np.unique(imgs, axis=0)) < n:
imgs.append(self.config_cs.get_camera_image())
return np.unique(imgs, axis=0)
def set_cross(
self, x=None, y=None, x_um_per_px=None, y_um_per_px=None, n_images=10
):
"""set x and y position of the refetence marker on a camera px/um calibration is conserved if no new value is given"""
def prompt(x, y, x_um_per_px, y_um_per_px):
x = int(x)
y = int(y)
answer = (
input(
f"Set the new cross position [{x}, {y}] with calibration [{x_um_per_px:.3}, {y_um_per_px:.3}] ([y]/n)?"
)
or "y"
)
if answer == "y":
calib.reference_marker([x - 1, y - 1, x + 1, y + 1])
calib.reference_marker_width(2 * x_um_per_px)
calib.reference_marker_height(2 * y_um_per_px)
print("\nNew calibration:")
print(calib)
else:
print("aborted")
calib = self.config_cs.config.camera_calibration
print("Current calibration:")
print(calib)
try:
w = calib.reference_marker_width()
h = calib.reference_marker_height()
rm = calib.reference_marker()
if not x_um_per_px:
x_um_per_px = w / abs(rm[2] - rm[0])
if not y_um_per_px:
y_um_per_px = h / abs(rm[3] - rm[1])
except:
rm = [0, 0, 0, 0]
x_um_per_px = 1
y_um_per_px = 1
if x is None or y is None:
x = (rm[2] + rm[0]) / 2
y = (rm[3] + rm[1]) / 2
img = np.mean(self.get_camera_images(n_images), axis=0)
run = True
def on_click(event):
if event.button is MouseButton.LEFT:
x = event.xdata
y = event.ydata
cross_plot.set_data(x, y)
plt.draw()
print(f"cross at x: {x:.4} and y: {y:.4}")
self.config_cs._cross = [x, y]
else:
plt.disconnect(bid)
plt.close(self.config_cs.cam_id)
fig = plt.figure(num=self.config_cs.cam_id)
plt.title(f"Set cross: left mouse click, Finish: right click")
plt.imshow(img)
cross_plot = plt.plot(x, y, "+r", markersize=10)[0]
bid = fig.canvas.mpl_connect("button_press_event", on_click)
plt.show(block=True)
x, y = self.config_cs._cross
print(x, y)
prompt(x, y, x_um_per_px, y_um_per_px)
def gui(self):
self._run_cmd(
f'caqtdm -macro "NAME={self.pvname},CAMNAME={self.pvname}" /sf/controls/config/qt/Camera/CameraExpert.ui'
)
# NB: please note this should be moved to microscopes which are using cameras plus zooms,
class QioptiqMicroscope(CameraBasler):
def __init__(self, pvname_camera, pvname_zoom=None, pvname_focus=None, name=None):
super().__init__(pvname_camera, name=name)
if pvname_zoom:
self._append(MotorRecord, pvname_zoom, name="zoom", is_setting=True)
if pvname_focus:
self._append(MotorRecord, pvname_focus, name="focus", is_setting=True)
class CameraPCO(Assembly):
def __init__(self, pvname, camserver_alias=None, name=None):
super().__init__(name=name)
self.pvname = pvname
if not camserver_alias:
camserver_alias = self.alias.get_full_name() + f"({pvname})"
else:
camserver_alias = camserver_alias + f"({pvname})"
self._append(
CamserverConfig,
self.pvname,
camserver_alias=camserver_alias,
name="config_cs",
)
self.config_cs.set_alias()
self._append(AdjustablePvEnum, self.pvname + ":INIT", name="initialize")
self._append(
AdjustablePvEnum,
self.pvname + ":CAMERASTATUS",
name="camera_status",
is_display=True,
)
self._append(AdjustablePv, self.pvname + ":BOARD", name="board_no")
# self._append(AdjustablePv, self.pvname + ":SERIALNR", name="serial_no") Apparently not exisitng and timing out.
self._append(AdjustablePv, self.pvname + ":EXPOSURE", name="_exposure_time")
self._append(AdjustablePvEnum, self.pvname + ":ACQMODE", name="_acq_mode")
self._append(AdjustablePvEnum, self.pvname + ":RECMODE", name="_req_mode")
self._append(AdjustablePvEnum, self.pvname + ":STOREMODE", name="_store_mode")
self._append(AdjustablePv, self.pvname + ":HSSPEED", name="_hs_speed")
self._append(
AdjustablePvEnum, self.pvname + ":SCMOSREADOUT", name="_readout_mode"
)
self._append(AdjustablePv, self.pvname + ":BINY", name="_binx")
self._append(AdjustablePv, self.pvname + ":BINY", name="_biny")
self._append(AdjustablePv, self.pvname + ":REGIONX_START", name="_roixmin")
self._append(AdjustablePv, self.pvname + ":REGIONX_END", name="_roixmax")
self._append(AdjustablePv, self.pvname + ":REGIONY_START", name="_roiymin")
self._append(AdjustablePv, self.pvname + ":REGIONY_END", name="_roiymax")
self._append(
AdjustablePvEnum, self.pvname + ":SET_PARAM", name="_set_parameters"
)
self._append(AdjustablePvEnum, self.pvname + ":TRIGGER", name="trigger_on")
# append_object_to_object(self,PvEnum,self.pvname+':TRIGGEREDGE',name='trigger_edge')
self._append(
AdjustableGetSet,
self._exposure_time.get_current_value,
lambda value: self._set_params((self._exposure_time, value)),
name="exposure_time",
is_setting=True,
)
self._append(
AdjustableVirtual,
[self.camera_status],
lambda stat: stat == 2,
lambda running: 2 if running else 1,
name="running",
is_setting=True,
)
def set_roi(roi):
self._set_params(
[self._roixmin, roi[0]],
[self._roixmax, roi[1]],
[self._roiymin, roi[2]],
[self._roiymax, roi[3]],
)
return (roi[0], roi[1], roi[2], roi[3])
self._append(
AdjustableVirtual,
[self._roixmin, self._roixmax, self._roiymin, self._roiymax],
lambda x_from, x_to, y_from, y_to: [x_from, x_to, y_from, y_to],
set_roi,
name="roi",
is_setting=True,
)
def _set_params(self, *args):
self.running(False)
for ob, val in args:
ob(val)
self._set_parameters(1)
self.running(True)
def gui(self):
self._run_cmd(
f'caqtdm -macro "NAME={self.pvname},CAMNAME={self.pvname}" /sf/controls/config/qt/Camera/CameraExpert.ui'
)
# NB: please note this should be moved to microscopes which are using cameras plus zooms,
class FeturaMicroscope(CameraBasler):
def __init__(
self, pvname_camera, pvname_base_zoom=None, name=None, camserver_alias=None
):
super().__init__(pvname_camera, name=name, camserver_alias=camserver_alias)
if pvname_base_zoom:
self._append(
AdjustablePv,
pvsetname=pvname_base_zoom + ":POS_SP",
pvreadbackname=pvname_base_zoom + ":POS_RB",
name="_zoom_motor",
is_setting=True,
is_display=False,
)
def getv(v):
return v / 10.0
def setv(v):
return v * 10.0
self._append(
AdjustableVirtual, [self._zoom_motor], getv, setv, name="zoom", unit="%"
)
+10 -9
View File
@@ -1,5 +1,6 @@
from ..devices_general.utilities import Changer
from epics import PV
from ..aliases import Alias
_status_messages = {
@@ -18,12 +19,12 @@ _status_messages = {
4: "move-without-wait finished, soft limit violation seen",
}
class DelayStage:
def __init__(self, stage):
def __init__(self, stage, name=None):
self._stage = stage
self.delay_stage_offset = 0.0
self.name = self._stage.name
self.name = name
self.alias = Alias(name)
self.Id = self._stage.Id
self._elog = self._stage._elog
@@ -32,7 +33,7 @@ class DelayStage:
return motor_pos
def get_current_value(self):
""" Adjustable convention"""
"""Adjustable convention"""
motor_pos = self._stage.get_current_value()
motor_pos -= self.delay_stage_offset
delay = motor_pos * 2.0 * 3.33333333 * 1e-12
@@ -43,17 +44,17 @@ class DelayStage:
self._stage.set_current_value(motor_pos)
return (value, motor_pos)
def changeTo(self, value, hold=False, check=True):
def set_target_value(self, value, hold=False, check=True):
value = self.delay_to_motor(value) + self.delay_stage_offset
delay = (value - self.delay_stage_offset) * 2.0 * 3.33333333 * 1e-12
return self._stage.changeTo(value, hold, check)
return self._stage.set_target_value(value, hold, check)
def gui(self, guiType="xdm"):
return self._stage.gui()
# spec-inspired convenience methods
def mv(self, value):
self._stage._currentChange = self.changeTo(value)
self._stage._currentChange = self.set_target_value(value)
def wm(self, *args, **kwargs):
return self.get_current_value(*args, **kwargs)
@@ -66,7 +67,7 @@ class DelayStage:
self._stage._currentChange.wait()
def stop(self):
""" Adjustable convention"""
"""Adjustable convention"""
try:
self._stage._currentChange.stop()
except:
@@ -81,4 +82,4 @@ class DelayStage:
return self.__str__()
def __call__(self, value):
self._currentChange = self.changeTo(value)
self._currentChange = self.set_target_value(value)
+7 -295
View File
@@ -1,29 +1,16 @@
import numpy as np
from epics import caget
from epics import PV
from ..eco_epics.utilities_epics import EnumWrapper
from eco.epics.utilities_epics import EnumWrapper
from cam_server import PipelineClient
from cam_server.utils import get_host_port_from_stream_address
from bsread import source, SUB
import subprocess
import h5py
from time import sleep
from threading import Thread
from datetime import datetime
from ..acquisition.utilities import Acquisition
try:
import sys, os
tpath = os.path.dirname(__file__)
sys.path.insert(0, os.path.join(tpath, "../../detector_integration_api"))
# ask Leo(2018.03.14):
# sys.path.insert(0,os.path.join(tpath,'../../jungfrau_utils'))
from detector_integration_api import DetectorIntegrationClient
except:
print("NB: detector integration could not be imported!")
from ..elements.assembly import Assembly
from ..elements.adjustable import AdjustableMemory
from ..elements.detector import DetectorVirtual
_cameraArrayTypes = ["monochrome", "rgb"]
@@ -48,8 +35,8 @@ class CameraCA:
return self.px_width
def get_data(self):
w = self.get_px_width()
h = self.get_px_height()
w = int(self.get_px_width())
h = int(self.get_px_height())
numpix = int(caget(self.Id + ":FPICTURE.NORD"))
i = caget(self.Id + ":FPICTURE", count=numpix)
return i.reshape(h, w)
@@ -63,7 +50,7 @@ class CameraCA:
f["images"] = np.asarray(d)
def gui(self, guiType="xdm"):
""" Adjustable convention"""
"""Adjustable convention"""
cmd = ["caqtdm", "-macro"]
cmd.append('"NAME=%s,CAMNAME=%s"' % (self.Id, self.Id))
@@ -140,278 +127,3 @@ class DiodeDigitizer:
if VME_crate:
self.diode_0 = FeDigitizer("%s:Lnk%dCh%d" % (VME_crate, link, ch_0))
self.diode_1 = FeDigitizer("%s:Lnk%dCh%d" % (VME_crate, link, ch_1))
class DIAClient:
def __init__(
self,
Id,
instrument=None,
api_address="http://sf-daq-2:10000",
jf_name="JF_1.5M",
):
self.Id = Id
self._api_address = api_address
self.client = DetectorIntegrationClient(api_address)
print("\nDetector Integration API on %s" % api_address)
# No pgroup by default
self.pgroup = 0
self.n_frames = 100
self.jf_name = jf_name
self.pede_file = ""
self.gain_file = ""
self.instrument = instrument
if instrument is None:
print("ERROR: please configure the instrument parameter in DIAClient")
self.update_config()
def update_config(self,):
self.writer_config = {
"output_file": "/sf/%s/data/p%d/raw/test_data.h5"
% (self.instrument, self.pgroup),
"user_id": self.pgroup,
"n_frames": self.n_frames,
"general/user": str(self.pgroup),
"general/process": __name__,
"general/created": str(datetime.now()),
"general/instrument": self.instrument,
# "general/correction": "test"
}
self.backend_config = {
"n_frames": self.n_frames,
"bit_depth": 16,
"gain_corrections_filename": self.gain_file, # "/sf/alvra/config/jungfrau/jungfrau_4p5_gaincorrections_v0.h5",
# "gain_corrections_dataset": "gains",
# "pede_corrections_filename": "/sf/alvra/data/res/p%d/pedestal_20171210_1628_res.h5" % self.pgroup,
# "pede_corrections_dataset": "gains",
# "pede_mask_dataset": "pixel_mask",
# "activate_corrections_preview": True,
# FIXME: HARDCODED!!!
"is_HG0": False,
}
if self.pede_file != "":
self.backend_config[
"gain_corrections_filename"
] = (
self.gain_file
) # "/sf/alvra/config/jungfrau/jungfrau_4p5_gaincorrections_v0.h5",
self.backend_config["gain_corrections_dataset"] = "gains"
self.backend_config[
"pede_corrections_filename"
] = (
self.pede_file
) # "/sf/alvra/data/res/p%d/pedestal_20171210_1628_res.h5" % self.pgroup,
self.backend_config["pede_corrections_dataset"] = "gains"
self.backend_config["pede_mask_dataset"] = "pixel_mask"
self.backend_config["activate_corrections_preview"] = True
else:
self.backend_config["pede_corrections_dataset"] = "gains"
self.backend_config["pede_mask_dataset"] = "pixel_mask"
self.backend_config["gain_corrections_filename"] = ""
self.backend_config["pede_corrections_filename"] = ""
self.backend_config["activate_corrections_preview"] = False
self.detector_config = {
"timing": "trigger",
# FIXME: HARDCODED
"exptime": 0.000010,
"cycles": self.n_frames,
# "delay" : 0.001992,
"frames": 1,
"dr": 16,
}
# Not needed anymore?
# default_channels_list = parseChannelListFile(
# '/sf/alvra/config/com/channel_lists/default_channel_list')
self.bsread_config = {
"output_file": "/sf/%s/data/p%d/raw/test_bsread.h5"
% (self.instrument, self.pgroup),
"user_id": self.pgroup,
"general/user": str(self.pgroup),
"general/process": __name__,
"general/created": str(datetime.now()),
"general/instrument": self.instrument,
#'Npulses':100,
#'channels': default_channels_list
}
# self.default_channels_list = jungfrau_utils.load_default_channel_list()
def reset(self):
self.client.reset()
# pass
def get_status(self):
return self.client.get_status()
def get_config(self):
config = self.client.get_config()
return config
def set_pgroup(self, pgroup):
self.pgroup = pgroup
self.update_config()
def set_bs_channels(self,):
print(
"Please update /sf/%s/config/com/channel_lists/default_channel_list and restart all services on the DAQ server"
% self.instrument
)
def set_config(self):
self.reset()
self.client.set_config(
{
"writer": self.writer_config,
"backend": self.backend_config,
"detector": self.detector_config,
"bsread": self.bsread_config,
}
)
def check_still_running(self, time_interval=0.5):
cfg = self.get_config()
running = True
while running:
if not self.get_status()["status"][-7:] == "RUNNING":
running = False
break
# elif not self.get_status()['status'][-20:]=='BSREAD_STILL_RUNNING':
# running = False
# break
else:
sleep(time_interval)
def take_pedestal(
self, n_frames, analyze=True, n_bad_modules=0, update_config=True
):
from jungfrau_utils.scripts.jungfrau_run_pedestals import (
run as jungfrau_utils_run,
)
directory = "/sf/%s/data/p%d/raw/JF_pedestal/" % (self.instrument, self.pgroup)
if not os.path.exists(directory):
print("Directory %s not existing, creating it" % directory)
os.makedirs(directory)
res_dir = directory.replace("/raw/", "/res/")
if not os.path.exists(res_dir):
print("Directory %s not existing, creating it" % res_dir)
os.makedirs(res_dir)
filename = "pedestal_%s.h5" % datetime.now().strftime("%Y%m%d_%H%M")
period = 0.04
jungfrau_utils_run(
self._api_address,
filename,
directory,
self.pgroup,
period,
self.detector_config["exptime"],
n_frames,
1,
analyze,
n_bad_modules,
self.instrument,
self.jf_name,
)
if update_config:
self.pede_file = (
(directory + filename).replace("raw/", "res/").replace(".h5", "_res.h5")
)
print("Pedestal file updated to %s" % self.pede_file)
return self.pede_file
def start(self):
self.client.start()
print("start acquisition")
pass
def stop(self):
self.client.stop()
print("stop acquisition")
pass
def config_and_start_test(self):
self.reset()
self.set_config()
self.start()
pass
def wait_for_status(self, *args, **kwargs):
return self.client.wait_for_status(*args, **kwargs)
def acquire(self, file_name=None, Npulses=100, JF_factor=1, bsread_padding=0):
"""
JF_factor?
bsread_padding?
"""
file_rootdir = "/sf/%s/data/p%d/raw/" % (self.instrument, self.pgroup)
if file_name is None:
# FIXME /dev/null crashes the data taking (h5py can't close /dev/null and crashes)
print("Not saving any data, as file_name is not set")
file_name_JF = file_rootdir + "DelMe" + "_JF1p5M.h5"
file_name_bsread = file_rootdir + "DelMe" + ".h5"
else:
# FIXME hardcoded
file_name_JF = file_rootdir + file_name + "_JF1p5M.h5"
file_name_bsread = file_rootdir + file_name + ".h5"
if self.pgroup == 0:
raise ValueError("Please use set_pgroup() to set a pgroup value.")
def acquire():
self.n_frames = Npulses * JF_factor
self.update_config()
# self.detector_config.update({
# 'cycles': n_frames})
self.writer_config.update(
{
"output_file": file_name_JF,
# 'n_messages': n_frames
}
)
# self.backend_config.update({
# 'n_frames': n_frames})
self.bsread_config.update(
{
"output_file": file_name_bsread,
# 'Npulses': Npulses + bsread_padding
}
)
self.reset()
self.set_config()
# print(self.get_config())
self.client.start()
done = False
while not done:
stat = self.get_status()
if stat["status"] == "IntegrationStatus.FINISHED":
done = True
if stat["status"] == "IntegrationStatus.BSREAD_STILL_RUNNING":
done = True
if stat["status"] == "IntegrationStatus.INITIALIZED":
done = True
if stat["status"] == "IntegrationStatus.DETECTOR_STOPPED":
done = True
sleep(0.1)
return Acquisition(
acquire=acquire,
acquisition_kwargs={
"file_names": [file_name_bsread, file_name_JF],
"Npulses": Npulses,
},
hold=False,
)
def wait_done(self):
self.check_running()
self.check_still_running()
+225
View File
@@ -0,0 +1,225 @@
from ..detector.detectors_psi import DetectorBsStream
from eco.elements.assembly import Assembly
from eco.epics.detector import DetectorPvDataStream
from eco.epics.adjustable import AdjustablePv, AdjustablePvEnum
class DigitizerKeysightBoxcarChannel(Assembly):
def __init__(self, pvbase, name=None):
super().__init__(name=name)
self.pvbase = pvbase
self._append(DetectorBsStream, self.pvbase + "_VAL_GET", name="value")
self._append(
DetectorPvDataStream,
self.pvbase + "_BOXCAR.VALF",
name="waveform_slow",
is_display=False,
is_status=True,
)
# self.status_collection.append(self.waveform_slow, force=True)
self._append(
DetectorPvDataStream,
self.pvbase + "_BOXCAR.VALH",
name="background_average",
)
self._append(
DetectorPvDataStream,
self.pvbase + "_BOXCAR.VALI",
name="signal_average",
)
self._append(
DetectorPvDataStream,
self.pvbase + "_BOXCAR.VALG",
name="difference_average",
)
self._append(
DetectorPvDataStream,
self.pvbase + "_BOXCAR.VALP",
name="background_integral",
)
self._append(
DetectorPvDataStream, self.pvbase + "_BOXCAR.VALO", name="signal_integral"
)
self._append(
DetectorPvDataStream,
self.pvbase + "_BOXCAR.VALQ",
name="difference_integral",
)
self._append(
AdjustablePv,
self.pvbase + "_BSTART",
name="bgregion_start",
is_setting="auto",
)
self._append(
AdjustablePv, self.pvbase + "_BEND", name="bgregion_end", is_setting="auto"
)
self._append(
AdjustablePv,
self.pvbase + "_START",
name="sigregion_start",
is_setting="auto",
)
self._append(
AdjustablePv, self.pvbase + "_END", name="sigregion_end", is_setting="auto"
)
self._append(
AdjustablePv, self.pvbase + "_LEVEL", name="cross_level", is_setting="auto"
)
self._append(
AdjustablePv,
self.pvbase + "_CALIB",
name="calibration_gain",
is_setting="auto",
)
self._append(
AdjustablePv,
self.pvbase + "_CALIB_OFFS",
name="calibration_offset",
is_setting="auto",
)
self._append(
AdjustablePvEnum,
self.pvbase + "_WHICH_CHAN",
name="output_mode",
is_setting="auto",
)
def get_current_value(self):
return self.value.get_current_value()
class DigitizerKeysight(Assembly):
def __init__(self, pvbase="SARES20-LSCP9-FNS", name=None):
super().__init__(name=name)
self.pvbase = pvbase
for chno in range(2):
chno = chno + 1
self._append(
DigitizerKeysightBoxcarChannel,
f"{self.pvbase}:PR1_CH{chno}",
name=f"channel_{chno:d}",
is_setting=True,
)
self._append(
AdjustablePv,
self.pvbase + ":A_SCANRATERB",
name="sample_rate",
is_setting="auto",
)
self._append(
AdjustablePv,
self.pvbase + ":A_TRIGGERDELAYNS",
pvreadbackname=self.pvbase + ":A_TRIGGERDELAYRB",
name="trigger_delay",
is_setting="auto",
)
class DigitizerIoxos(Assembly):
def __init__(self, pvbase="SARES20-LSCP9-FNS", name=None):
super().__init__(name=name)
self.pvbase = pvbase
for chno in range(8):
self._append(
DigitizerIoxosBoxcarChannel,
f"{self.pvbase}:CH{chno}",
name=f"channel_{chno:d}",
is_setting=True,
)
class DigitizerIoxosBoxcarChannel(Assembly):
def __init__(self, pvbase, name=None):
super().__init__(name=name)
self.pvbase = pvbase
self._append(DetectorBsStream, self.pvbase + ":VAL_GET", name="value")
self._append(
AdjustablePv, self.pvbase + ":VAL_GET.EGU", name="unit", is_setting="auto"
)
self._append(
DetectorPvDataStream,
self.pvbase + ":WFM",
name="waveform_slow",
is_display=False,
is_status=True,
)
# self.status_collection.append(self.waveform_slow)
self._append(
DetectorPvDataStream,
self.pvbase + ":BOXCAR.VALH",
name="background",
)
self._append(
DetectorPvDataStream,
self.pvbase + ":BOXCAR.VALI",
name="signal",
)
self._append(
DetectorPvDataStream,
self.pvbase + ":BOXCAR.VALG",
name="difference",
)
self._append(
DetectorPvDataStream, self.pvbase + ":BOXCAR.VALO", name="signal_integral"
)
self._append(
DetectorPvDataStream, self.pvbase + ":BOXCAR.VALE", name="signal_average"
)
self._append(
DetectorPvDataStream,
self.pvbase + ":BOXCAR.VALP",
name="background_integral",
)
self._append(
DetectorPvDataStream,
self.pvbase + ":BOXCAR.VALD",
name="background_average",
is_setting="auto",
)
self._append(
AdjustablePv,
self.pvbase + ":BSTART",
name="bgregion_start",
is_setting="auto",
)
self._append(
AdjustablePv, self.pvbase + ":BEND", name="bgregion_end", is_setting="auto"
)
self._append(
AdjustablePv,
self.pvbase + ":START",
name="sigregion_start",
is_setting="auto",
)
self._append(
AdjustablePv, self.pvbase + ":END", name="sigregion_end", is_setting="auto"
)
self._append(
AdjustablePv,
self.pvbase + ":CALIB",
name="calibration_gain",
is_setting="auto",
)
self._append(
AdjustablePv,
self.pvbase + ":CALIB_OFFS",
name="calibration_offset",
is_setting="auto",
)
self._append(
AdjustablePvEnum,
self.pvbase + ":BOXCAR.SCAN",
name="scan_mode",
is_setting="auto",
)
self._append(
AdjustablePvEnum,
self.pvbase + ":WHICH_CHAN",
name="output_mode",
is_setting="auto",
)
def get_current_value(self):
return self.value.get_current_value()
+65
View File
@@ -0,0 +1,65 @@
from eco import Assembly
from eco.epics.adjustable import AdjustablePv, AdjustablePvEnum, AdjustablePvString
from eco.epics.detector import DetectorPvData, DetectorPvEnum
from epics import PV
class I2cChannel(Assembly):
def __init__(self, pvbase, channelnumber, name=None):
super().__init__(name=name)
self.pvbase = pvbase
self.channel_number = channelnumber
self._append(
AdjustablePvString,
f'{self.pvbase}_CH{self.channel_number}:PROCESS.DESC',
name="description",
is_setting=True,
)
self._append(DetectorPvData,f'{self.pvbase}_CH{self.channel_number}:TEMP', has_unit=True, name='temp')
self._append(DetectorPvData,f'{self.pvbase}_CH{self.channel_number}:HUMIREL', has_unit=True, name = 'humi')
self._append(DetectorPvData,f'{self.pvbase}_CH{self.channel_number}:PRES', has_unit=True, name='pres')
self._append(AdjustablePv,f'{self.pvbase}_CH{self.channel_number}:ONOFF', name='enabled', is_setting=True)
self._append(AdjustablePvEnum,f'{self.pvbase}_CH{self.channel_number}:SENSOR_TYPE', name='sensor_type', is_setting=True)
self._pv_init = PV(self.pvbase+':INIT.PROC')
def get_current_value(self,*args,**kwargs):
return f'{self.temp.get_current_value(*args,**kwargs):.2f}°C , {self.humi.get_current_value(*args,**kwargs):.2f}%relHum, {self.pres.get_current_value(*args,**kwargs):.2f} mB'
# def set_target_value(self,*args,**kwargs):
# return self.enabled.set_target_value(*args,**kwargs)
def initialize(self):
self._pv_init.put(1)
class I2cModule(Assembly):
def __init__(self,pvbase='SARES20-CI2C',N_channels=8, name=None):
super().__init__(name=name)
for n in range(1,N_channels+1):
self._append(I2cChannel,pvbase,channelnumber=n,name=f'ch{n}')
class BerninaEnvironment(Assembly):
def __init__(self,pvbases=['SLAAR21-LI2C01', 'SARES20-CI2C'],channels=[[1,2,3,4,5,6,7,8],[4,5,6,7,8]], channelnames = [['las_tab1_in', 'las_tab1_cen', 'las_tab1_out', 'las_tab2_in', 'las_tab2_cen', 'las_tab2_out', 'las_tab2_below', 'tt_spec'], ['tt_opt', 'tt_kb', 'exp1', 'exp2', 'exp3']], name=None):
super().__init__(name=name)
for pvbase,channelnumbers,tnames in zip(pvbases,channels,channelnames):
for n,tname in zip(channelnumbers,tnames):
self._append(I2cChannel,pvbase,channelnumber=n,name=tname)
self._append(DetectorPvData,"IKA-GAMA:Luft_SF", unit='%', name='he_recovery_air_content')
class WagoSensor(Assembly):
def __init__(self,pvbase='SARES20-CWAG-GPS01:TEMP-T9', name=None):
super().__init__(name=name)
self.pvbase = pvbase
self._append(DetectorPvData,f'{self.pvbase}', unit='°C', name='temperature')
self._append(DetectorPvEnum,f'{self.pvbase}-SS', name='status')
self._append(AdjustablePv,f'{self.pvbase}-WLEN', name='cable_length', unit='m', is_setting=True)
self.unit = self.temperature.unit
def get_current_value(self):
return self.temperature.get_current_value()
@@ -0,0 +1,295 @@
*-------------------------------------------------------
* itemType: Rectangle
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: Line
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: Triangle
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: Circle
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2,11,12
contextActions(editmode)=10(ClosePath),21(Pie),20(Arc),30(90deg),31(180deg),32(270deg),33(360deg)
*-------------------------------------------------------
* itemType: Polygon
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: Path
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
contextActions(editmode)=10(ClosePath),11(FillSolid),20(OddEven),21(Winding)
*-------------------------------------------------------
* itemType: Text
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: Image
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Monitor)
controlPoints=1,2
*-------------------------------------------------------
* itemType: Symbol
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: AttachLabel
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: AttachTransform
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: Frame
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: ScrollArea
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: GroupBox
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: Stacked
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
contextActions(editmode)=100(Add Tab),101(Delete Tab)
*-------------------------------------------------------
* itemType: SubPicture
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: MonitorText
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Monitor)
controlPoints=1,2,10
contextActions(editmode)=1(Show Unit)
*-------------------------------------------------------
* itemType: MonitorBar
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Monitor),3(Monitor)
controlPoints=1,2,10,11
contextActions(editmode)=30(No),31(Gradient),32(Solid),33(Warning&Alarm Gradient),34(Warning&Alarm Solid),35(Alarm Solid),36(Warning Solid),20(No),21(Inside),22(Outside)
*-------------------------------------------------------
* itemType: MonitorMeter
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Monitor),3(Monitor)
controlPoints=1,2,10,11
contextActions(editmode)=30(No),31(Gradient),32(Solid),33(Warning&Alarm Gradient),34(Warning&Alarm Solid),35(Alarm Solid),36(Warning Solid),20(No),21(Inside),22(Outside),50(Two Third),51(Three Quarters),60(Upper Half),70(TopRight Quarter),71(TopLeft Quarter),72(BotRight Quarter),73(BotLeft Quarter)
*-------------------------------------------------------
* itemType: MonitorIndic
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Monitor)
controlPoints=1,2
*-------------------------------------------------------
* itemType: MonitorBits
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Monitor)
controlPoints=1,2
contextActions(editmode)=1(Up),2(Down),3(Left),4(Right)
*-------------------------------------------------------
* itemType: MonitorBitnames
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Monitor),3(Monitor)
controlPoints=1,2
contextActions(editmode)=1(Up),2(Down),3(Left),4(Right)
*-------------------------------------------------------
* itemType: StateSymbol
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Monitor)
controlPoints=1,2
*-------------------------------------------------------
* itemType: MonitorStripChart
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Monitor),3(Monitor),4(Monitor),5(Monitor),6(Monitor),7(Monitor),8(Monitor),9(Monitor),10(Monitor)
controlPoints=1,2
contextActions(editmode)=30(PV),31(Received),10(None),11(TopLeft),12(Top),13(TopRight),14(Right),15(BottomRight),16(Bottom),17(BottomLeft),18(Left),40(Absolut),41(Relative),60(Right to Left),61(Left to Right),50(None),51(Horizontal),52(Vertical),53(Hor&Vert)
contextActions(runtime)=80(Clear Curve),30(PV),31(Received),10(None),11(TopLeft),12(Top),13(TopRight),14(Right),15(BottomRight),16(Bottom),17(BottomLeft),18(Left),40(Absolut),41(Relative),60(Right to Left),61(Left to Right),50(None),51(Horizontal),52(Vertical),53(Hor&Vert)
*-------------------------------------------------------
* itemType: MonitorXYPlot
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),10(Monitor),18(Monitor),11(Monitor),19(Monitor),12(Monitor),20(Monitor),13(Monitor),21(Monitor),14(Monitor),22(Monitor),15(Monitor),23(Monitor),16(Monitor),24(Monitor),17(Monitor),25(Monitor),3(Monitor),2(Monitor),4(Monitor),50(Monitor),52(Monitor),51(Monitor),53(Monitor)
controlPoints=1,2
contextActions(editmode)=30(PV),31(Received),10(None),11(TopLeft),12(Top),13(TopRight),14(Right),15(BottomRight),16(Bottom),17(BottomLeft),18(Left),50(None),51(Horizontal),52(Vertical),53(Hor&Vert)
contextActions(runtime)=80(Clear Plot),30(PV),31(Received),10(None),11(TopLeft),12(Top),13(TopRight),14(Right),15(BottomRight),16(Bottom),17(BottomLeft),18(Left),50(None),51(Horizontal),52(Vertical),53(Hor&Vert)
*-------------------------------------------------------
* itemType: MonitorCamera
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Monitor),3(Monitor),4(Monitor),5(Monitor),6(Monitor)
controlPoints=1,2,10,11
contextActions(editmode)=21(Information),20(Histogram),22(Level Bar),11(<Disable All>),1(Default),2(Grey),3(Spectrum),4(Automatic Levels)
contextActions(runtime)=21(Information),20(Histogram),22(Level Bar),11(<Disable All>),1(Default),2(Grey),3(Spectrum),4(Automatic Levels)
*-------------------------------------------------------
* itemType: MonitorTable
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2,100,101,102
*-------------------------------------------------------
* itemType: MonitorWaterfallPlot
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Monitor),3(Monitor),4(Monitor)
controlPoints=1,2,10,11
contextActions(editmode)=30(Scroll),31(Repeate),32(Single),40(Down),41(Up),21(Information),20(Intensity),22(Grid),11(<Disable All>),2(Grey),3(Spectrum0),4(Spectrum1)
contextActions(runtime)=90(Clear Plot),30(Scroll),31(Repeate),32(Single),40(Down),41(Up),21(Information),20(Intensity),22(Grid),11(<Disable All>),2(Grey),3(Spectrum0),4(Spectrum1)
*-------------------------------------------------------
* itemType: ControlKnob
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Control)
controlPoints=1,2,10,11
contextActions(editmode)=10(Unit),30(No),31(Gradient),32(Solid),33(Warning&Alarm Gradient),34(Warning&Alarm Solid),35(Alarm Solid),36(Warning Solid)
*-------------------------------------------------------
* itemType: ControlSlider
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Control),3(Monitor)
controlPoints=1,2,10,11
contextActions(editmode)=30(No),31(Gradient),32(Solid),33(Warning&Alarm Gradient),34(Warning&Alarm Solid),35(Alarm Solid),36(Warning Solid),20(No),21(Inside),22(Outside)
*-------------------------------------------------------
* itemType: ControlTextEntry
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Control)
controlPoints=1,2,10
contextActions(editmode)=1(Show Unit)
*-------------------------------------------------------
* itemType: ControlNumeric
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Control)
controlPoints=1,2,10
contextActions(editmode)=1(Show Unit)
*-------------------------------------------------------
* itemType: ControlMessageButton
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Control)
controlPoints=1,2
*-------------------------------------------------------
* itemType: ControlChoiceButton
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Control)
controlPoints=1,2
*-------------------------------------------------------
* itemType: ControlCheckBox
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Control)
controlPoints=1,2
*-------------------------------------------------------
* itemType: ActionDisplayGroup
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: ActionLoadPicture
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: ActionShellCommand
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: PropertyModifierCheckBox
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: ScriptContext2d
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: ScriptActionButton
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: BackgroundImage
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Monitor)
*-------------------------------------------------------
* itemType: TestEvents
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: TestTGeneric
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: TestArea
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: Button
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
*-------------------------------------------------------
* itemType: SymOriginPosition
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
*-------------------------------------------------------
* itemType: SymConnectionPoint
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
*-------------------------------------------------------
* itemType: SymConnectionLine
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=100
*-------------------------------------------------------
* itemType: SymElmFrame
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
*-------------------------------------------------------
* itemType: SymElmRectangle
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
*-------------------------------------------------------
* itemType: SymElmCircle
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=11,12
contextActions(editmode)=10(ClosePath),21(Pie),20(Arc),30(90deg),31(180deg),32(270deg),33(360deg)
*-------------------------------------------------------
* itemType: SymElmText
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
*-------------------------------------------------------
* itemType: SymElmPath
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
contextActions(editmode)=10(ClosePath),11(FillSolid),20(OddEven),21(Winding)
*-------------------------------------------------------
* itemType: SymElmImage
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm),2(Monitor)
*-------------------------------------------------------
* itemType: Cross
*-------------------------------------------------------
pvs=0(Visibility),1(Alarm)
controlPoints=1,2
File diff suppressed because it is too large Load Diff
+34
View File
@@ -0,0 +1,34 @@
from ..devices_general.motors import MotorRecord
from ..aliases import Alias, append_object_to_object
def addMotorRecordToSelf(self, name=None, Id=None):
try:
self.__dict__[name] = MotorRecord(Id, name=name)
self.alias.append(self.__dict__[name].alias)
except:
print(f"Warning! Could not find motor {name} (Id:{Id})")
self.Id = Id
self.name = name
self.alias = Alias(name)
class stage:
def __init__(self, name=None, vonHamos_horiz_pv=None, vonHamos_vert_pv=None):
self.name = name
self.alias = Alias(name)
addMotorRecordToSelf(self, Id=vonHamos_horiz_pv, name="horiz")
addMotorRecordToSelf(self, Id=vonHamos_vert_pv, name="vert")
def get_adjustable_positions_str(self):
ostr = "***** VonHamos motor positions******\n"
for tkey, item in self.__dict__.items():
if hasattr(item, "get_current_value"):
pos = item.get_current_value()
ostr += " " + tkey.ljust(17) + " : % 14g\n" % pos
return ostr
def __repr__(self):
return self.get_adjustable_positions_str()
+66
View File
@@ -0,0 +1,66 @@
from ..elements.assembly import Assembly
from ..epics.adjustable import AdjustablePv
class MforceChannel(Assembly):
def __init__(self, pv_motor, pv_mcodebase, name=None):
super().__init__(name=name)
self.pv_motor = pv_motor # Example SARES20-MF1:
self.pv_mcodebase = pv_mcodebase
self._append(
AdjustablePv,
self.pv_mcodebase + "_RC",
name="run_current",
is_setting=True,
)
self._append(
AdjustablePv,
self.pv_mcodebase + "_HC",
name="holding_current",
is_setting=True,
)
self._append(
AdjustablePv,
self.pv_motor + ".DESC",
name="display_name",
is_setting=True,
)
self._append(
AdjustablePv,
self.pv_motor + ".EGU",
name="units",
is_setting=True,
)
self._append(
AdjustablePv,
self.pv_motor + ".MRES",
name="motor_resolution",
is_setting=True,
)
self._append(
AdjustablePv,
self.pv_motor + ".ERES",
name="encoder_resolution",
is_setting=True,
)
self._append(
AdjustablePv,
self.pv_motor + ".VELO",
name="velocity",
is_setting=True,
)
# self._append(
# PvRecord,
# self.pv_base + self.port + "_set",
# name="limit_switch_I",
# is_setting=True,
# ) # IS=1,2,0
# # IS=1,3,0 set wire 1 (1,3) to high limit, active when at 0
# self._append(
# PvRecord,
# self.pv_base + self.port + "_set",
# name="limit_switch_II",
# is_setting=True,
# ) # IS=2,3,0
# # IS=2,2,0 set wire 2 (2,2) to low limit, active when at 0
File diff suppressed because it is too large Load Diff
+199
View File
@@ -0,0 +1,199 @@
from cam_server import CamClient, PipelineClient
from eco.devices_general.utilities import Changer
from eco.elements.adj_obj import AdjustableObject, DetectorObject
from eco.elements.detector import DetectorGet
from ..aliases import Alias, append_object_to_object
from ..elements.adjustable import AdjustableVirtual, AdjustableGetSet, value_property
from ..epics.adjustable import AdjustablePv, AdjustablePvEnum
from ..elements.assembly import Assembly
from .motors import MotorRecord
import sys
from pathlib import Path
import time
import cachebox
sys.path.append("/sf/bernina/config/src/python/sf_databuffer/")
import bufferutils
CAM_CLIENT = None
PIPELINE_CLIENT = None
def get_camclient():
global CAM_CLIENT
if not CAM_CLIENT:
CAM_CLIENT = CamClient()
return CAM_CLIENT
def get_pipelineclient():
global PIPELINE_CLIENT
if not PIPELINE_CLIENT:
PIPELINE_CLIENT = PipelineClient()
return PIPELINE_CLIENT
@value_property
class Pipeline(Assembly):
def __init__(self, pipeline_name, name=None, camserver_group=None):
super().__init__(name=name)
self.pipeline_name = pipeline_name
self.camserver_group = camserver_group
self._append(
AdjustableGetSet,
self._get_config,
self._set_config,
cache_get_seconds=0.05,
precision=0,
check_interval=None,
name="_config",
is_setting=True,
is_display=False,
)
self._append(
AdjustableObject,
self._config,
name="config",
is_setting=False,
# recursive=False,
is_display="recursive",
)
self._append(
DetectorGet,
self._get_info,
cache_get_seconds=0.05,
name="_info",
is_setting=False,
is_display=False,
)
self._append(
DetectorObject,
self._info,
name="info",
is_display="recursive",
is_setting=False,
)
# @property
# def cc(self):
# return get_camclient()
@property
def pc(self):
return get_pipelineclient()
@cachebox.cached(cachebox.TTLCache(maxsize=0, ttl=1))
def _get_config(self):
return self.pc.get_instance_config(self.pipeline_name)
def _set_config(self, value, hold=False):
return Changer(
target=value,
changer=lambda v: self.pc.set_instance_config(self.pipeline_name, v),
hold=hold,
)
def _get_info(self, reject_kws=["config"]):
info = self.pc.get_instance_info(self.pipeline_name)
for rkw in reject_kws:
info.pop(rkw)
return info
def _get_stream(self):
return self.pc.get_instance_stream(self.pipeline_name)
# ### convenience functions ###
# def set_alias(self, alias=None):
# """creates an alias in the camera config on the server. If no alias is provided, it defaults to the camera name"""
# if not alias:
# alias = self.camserver_alias
# self.set_config_fields({"alias": [alias.upper()]})
# def set_group(self, group=None):
# """creates an alias in the camera config on the server. If no alias is provided, it defaults to the camera name"""
# if not group:
# group = self.camserver_group
# self.set_config_fields({"group": group})
def restart_pipeline(self):
base_directory = "/sf/bernina/config/src/python/sf_databuffer/"
label = self.pipeline_name
policies = bufferutils.read_files(base_directory / Path("policies"), "policies")
sources = bufferutils.read_files(base_directory / Path("sources"), "sources")
sources_new = sources.copy()
# Only for debugging purposes
labeled_sources = bufferutils.get_labeled_sources(sources_new, label)
for s in labeled_sources:
bufferutils.logging.info(f"Restarting {s['stream']}")
sources_new = bufferutils.remove_labeled_source(sources_new, label)
# Stopping the removed source(s)
bufferutils.update_sources_and_policies(sources_new, policies)
# Starting the source(s) again
bufferutils.update_sources_and_policies(sources, policies)
def stop(self):
self.pc.stop_instance(self.pipeline_name)
# def set_cross(self, x, y, x_um_per_px=None, y_um_per_px=None):
# """set x and y position of the refetence marker on a camera px/um calibration is conserved if no new value is given"""
# calib = self.get_current_value()["camera_calibration"]
# if calib:
# if not x_um_per_px:
# x_um_per_px = calib["reference_marker_width"] / abs(
# calib["reference_marker"][2] - calib["reference_marker"][0]
# )
# if not y_um_per_px:
# y_um_per_px = calib["reference_marker_height"] / abs(
# calib["reference_marker"][3] - calib["reference_marker"][1]
# )
# else:
# calib = {}
# x_um_per_px = 1
# y_um_per_px = 1
# calib["reference_marker"] = [x - 1, y - 1, x + 1, y + 1]
# calib["reference_marker_width"] = 2 * x_um_per_px
# calib["reference_marker_height"] = 2 * y_um_per_px
# self.set_config_fields(fields={"camera_calibration": calib})
# def set_config_fields_multiple_cams(self, conditions, fields):
# """
# conditions is a dictionary holding the conditions to select a subset of cameras, e.g. {"group": Bernina}
# fields is a dictionary containing the keys and values that should be updated, e.g. fields={'alias': ['huhu', 'duda']}
# """
# cams = {
# cam: self.cc.get_camera_config(cam)
# for cam in self.cc.get_cameras()
# if not "jungfrau" in cam
# }
# cams_selected = {}
# for cam, cfg in cams.items():
# try:
# if all([value in cfg[key] for key, value in conditions.items()]):
# cfg.update(fields)
# self.cc.set_camera_config(cam, cfg)
# cams_selected[cam] = cfg
# except Exception as e:
# print(f"{type(e)} {e} in cam {cam}")
# return cams_selected
# def clear_all_bernina_aliases(self, verbose=True):
# cams_selected = self.set_config_fields_multiple_cams(
# conditions={"group": "Bernina"}, fields={"alias": []}
# )
# if verbose:
# print(f"Reset alias of {len(cams_selected)} cameras")
# print(cams_selected.keys())
# def __repr__(self):
# s = f"**Camera Server Config {self.pipeline_name} with Alias {self.name}**\n"
# for key, item in self.get_current_value().items():
# s += f"{key:20} : {item}\n"
# return s
+97
View File
@@ -0,0 +1,97 @@
from eco import Assembly
from eco.elements.detector import DetectorGet
from eco.elements.adjustable import AdjustableGetSet
from .PBComm import SSHComm
from .powerbrick_parameters import *
connected_power_bricks = {}
def get_power_brick_comm(hostname):
if hostname in connected_power_bricks.keys():
return connected_power_bricks[hostname]
else:
return PowerBrickComm(hostname)
class PowerBrickComm:
def __init__(self, hostname):
self.pbsshcom = SSHComm()
self.pbsshcom.connect(hostname)
connected_power_bricks[hostname] = self
def get_parameter(self, parstring, autoconvert=True):
if isinstance(parstring, bytes):
parstring = parstring.decode()
if not parstring.endswith("\n"):
parstring += "\n"
self.pbsshcom.chan.send(parstring)
par = self.pbsshcom.read_until_endswith(SSHComm.gpascii_ack)
par = par.split(SSHComm.gpascii_ack)[0]
# print(par)
if autoconvert:
par = par.lower().split(parstring.lower().strip("\n"))[1]
if par.startswith("="):
par = par[1:]
try:
par = int(par)
except ValueError:
try:
par = float(par)
except ValueError:
pass
return par
def set_parameter(self, value, parstring):
if isinstance(parstring, bytes):
parstring = parstring.decode()
if parstring.endswith("\n"):
parstring.strip("\n")
setstring = parstring + "=" + str(value)
return self.pbsshcom.iawrite(setstring)
# def set_hmz
class PowerBrickChannelPars(Assembly):
def __init__(self, hostname, axis=None, type=None, pars_list=[], name=None):
super().__init__(name=name)
self._pbcom = get_power_brick_comm(hostname)
self._ch = axis
if type == "motor":
pars_list = pars_motors
else:
pass
for par, mode in pars_list:
if "r" in mode and "w" not in mode:
self.append_par_detector(par)
elif "r" in mode and "w" in mode:
# print(par)
self.append_par_adjustable(par)
def get_from_par(self, formstr, **kwargs):
return self._pbcom.get_parameter(formstr.format(self._ch), **kwargs)
def set_from_par(self, value, formstr, **kwargs):
return self._pbcom.set_parameter(value, formstr.format(self._ch), **kwargs)
def append_par_detector(self, par):
self._append(DetectorGet, lambda: self.get_from_par(par), name=str2varname(par))
def append_par_adjustable(self, par):
self._append(
AdjustableGetSet,
lambda: self.get_from_par(par),
lambda value: self.set_from_par(value, par),
name=str2varname(par),
)
def str2varname(s, delimeter_swaps=[(".", "_")]):
for tds in delimeter_swaps:
s = s.replace(*tds)
s = "".join(filter(lambda c: str.isidentifier(c) or str.isdecimal(c), s))
return s
@@ -0,0 +1,148 @@
pars_default = {
("EncTable[{}].PrevEnc", "r"),
("Motor[{}].pos", "r"),
("Motor[{}].idCmd", "r"),
("Motor[{}].Ctrl", "r"),
}
pars_StatusGblListCtrl = {
("WDTFault", "r"),
("PwrOnFault", "r"),
("ProjectLoadErr", "r"),
("ConfigLoadErr", "r"),
("HWChangeErr", "r"),
("FileConfigErr", "r"),
("Default", "r"),
("NoClocks", "r"),
("AbortAll", "r"),
("BufSizeErr", "r"),
("FlashSizeErr", "r"),
("CK3WConfigErr", "r"),
("CK3WHWChange", "r"),
}
pars_StatusMotorListCtrl = {
("AmpEna", "r"),
("AmpWarn", "r"),
("AmpFault", "r"),
("ClosedLoop", "r"),
("I2tFault", "r"),
("FeWarn", "r"),
("FeFatal", "r"),
("DesVelZero", "r"),
("InPos", "r"),
("InPosBand", "rw"),
("LimitStop", "r"),
("MinusLimit", "r"),
("PlusLimit", "r"),
("EncLoss", "r"),
("HomeInProgress", "r"),
("HomeComplete", "r"),
("HomeOffset", "r"),
("TriggerMove", "r"),
("DacLimit", "r"),
("SoftLimit", "r"),
("SoftMinusLimit", "r"),
("SoftPlusLimit", "r"),
("SoftLimitDir", "r"),
("TriggerNotFound", "r"),
("AuxFault", "r"),
("BlockRequest", "r"),
("PhaseFound", "r"),
("TriggerSpeedSel", "r"),
("GantryHomed", "r"),
("SpindleMotor", "r"),
("Csolve", "r"),
("Servo.OutDbOn", "rw"),
("Servo.OutDbOff", "rw"),
("Servo.BreakPosErr", "rw"),
("Servo.Kp", "rw"),
("Servo.Ki", "rw"),
("Servo.Kvff", "rw"),
("Servo.Kaff", "rw"),
("Servo.Kviff", "rw"),
("Servo.MaxPosErr", "rw"),
("Servo.MaxInt", "rw"),
("FatalFeLimit", "rw"),
("WarnFeLimit", "rw"),
("BlDir", "r"),
("BlDir", "r"),
("BlDir", "r"),
("BlDir", "r"),
# ('TraceCount' , 8,1)))
}
pars_StatusCoordListCtrl = {
("TriggerMove", "r"),
("HomeInProgress", "r"),
("MinusLimit", "r"),
("PlusLimit", "r"),
("FeWarn", "r"),
("FeFatal", "r"),
("LimitStop", "r"),
("AmpFault", "r"),
("SoftMinusLimit", "r"),
("SoftPlusLimit", "r"),
("I2tFault", "r"),
("TriggerNotFound", "r"),
("AmpWarn", "r"),
("EncLoss", "r"),
("AuxFault", "r"),
("TimerEnabled", "r"),
("HomeComplete", "r"),
("DesVelZero", "r"),
("ClosedLoop", "r"),
("AmpEna", "r"),
("InPos", "r"),
("InPosBand", "rw"),
("ErrorStatus", "r"),
("BlockRequest", "r"),
("TimersEnabled", "r"),
("Csolve", "r"),
("LinToPvtBuf", "r"),
("FeedHold", "r"),
("BlockActive", "r"),
("ContMotion", "r"),
("CCMode", "r"),
("MoveMode", "r"),
("SegMove", "r"),
("SegMoveAccel", "r"),
("SegMoveDecel", "r"),
("SegEnabled", "r"),
("SegStopReq", "r"),
("LookAheadWrap", "r"),
("LookAheadLookBack", "r"),
("LookAheadDir", "r"),
("LookAheadStop", "r"),
("LookAheadChange", "r"),
("LookAheadReCalc", "r"),
("LookAheadFlush", "r"),
("LookAheadActive", "r"),
("CCAddedArc", "r"),
("CCOffReq", "r"),
("CCMoveType", "r"),
("CC3Active", "r"),
("SharpCornerStop", "r"),
("AddedDwellDis", "r"),
# ('ProgRunning', 'r'),
# ('ProgActive', 'r'),
# ('ProgProceeding', 'r'),
# ('BufferWarn', 'r'))
}
pars_default = {
("EncTable[{}].PrevEnc", "r"),
("EncTable[{}].EncLoss", "r"),
("EncTable[{}].EncLossBit", "r"),
("EncTable[{}].MaxDelta", "rw"),
("Motor[{}].pos", "r"),
("Motor[{}].idCmd", "r"),
("Motor[{}].Ctrl", "r"),
}
pars_motors = pars_default.union(
{("Motor[{}]." + par, mode) for par, mode in pars_StatusMotorListCtrl}
)
+442
View File
@@ -0,0 +1,442 @@
from ..epics.adjustable import AdjustablePvEnum, AdjustablePvString, AdjustablePv
from ..elements.assembly import Assembly
from ..epics.detector import DetectorPvEnum, DetectorPvData
from .detectors import DetectorVirtual
from functools import partial
from eco.elements.adjustable import spec_convenience
class PowerSocket(Assembly):
def __init__(self, pvname, name=None):
super().__init__(name=name)
self.pvname = pvname
self._append(
AdjustablePvString,
pvname + ":POWERONOFF-DESC",
name="description",
is_setting=True,
)
self._append(
DetectorPvEnum, pvname + ":POWERONOFF-RB", name="stat", is_display=True
)
self._append(
AdjustablePvEnum,
pvname + ":POWERONOFF",
name="on_switch",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePvString,
pvname + ":POWERCYCLE",
name="powercycle_for_10s",
is_setting=False,
is_display=False,
)
def toggle(self):
self.on_switch(int(not (self.stat() == 1)))
def on(self):
self.on_switch(1)
def off(self):
self.on_switch(0)
def __call__(self, *args):
if not args:
self.toggle()
else:
self.on_switch(args[0])
class GudeStrip(Assembly):
def __init__(self, pvbase, name=None):
super().__init__(name=name)
self.pvbase = pvbase
for n in range(1, 5):
self._append(
PowerSocket,
pvbase + f"-CH{n}",
is_display="recursive",
is_setting=True,
name=f"ch{n}",
)
self._append(
DetectorPvData, pvbase + ":CURRENT", is_display=True, name="current"
)
self._append(
DetectorPvData, pvbase + ":VOLTAGE", is_display=True, name="voltage"
)
class MpodStatus(Assembly):
def __init__(self, pvbase, channel_number, module_string="LV_OMPV_1", name=None):
super().__init__(name=name)
self.pvbase = pvbase
self._module_string = module_string
self.channel_number = channel_number
self._append(
DetectorPvEnum,
self.pvbase + f":{self._module_string}_CH{self.channel_number}_ON",
name="is_on",
)
self._append(
DetectorPvEnum,
self.pvbase + f":{self._module_string}_CH{self.channel_number}_INHIBIT",
name="inhibited",
)
self._append(
DetectorPvEnum,
self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_FAILURE_MIN_SENS_VOLTAGE",
name="voltage_readback_low",
)
self._append(
DetectorPvEnum,
self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_FAILURE_MAX_SENS_VOLTAGE",
name="voltage_readback_high",
)
self._append(
DetectorPvEnum,
self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_FAILURE_MAX_TERM_VOLTAGE",
name="terminal_voltage_readback_high",
)
self._append(
DetectorPvEnum,
self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_FAILURE_MAX_CURRENT",
name="current_too_high",
)
self._append(
DetectorPvEnum,
self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_FAILURE_MAX_TEMP",
name="temperature_high",
)
self._append(
DetectorPvEnum,
self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_FAILURE_MAX_POWER",
name="output_power_high",
)
self._append(
DetectorPvEnum,
self.pvbase + f":{self._module_string}_CH{self.channel_number}_TIMEOUT",
name="communication_timeout",
)
self._append(
DetectorPvEnum,
self.pvbase + f":{self._module_string}_CH{self.channel_number}_CURR_CTRL",
name="constant_current_mode",
)
self._append(
DetectorPvEnum,
self.pvbase + f":{self._module_string}_CH{self.channel_number}_RMP_UP",
name="ramping_up",
)
self._append(
DetectorPvEnum,
self.pvbase + f":{self._module_string}_CH{self.channel_number}_RMP_DOWN",
name="ramping_down",
)
self._append(
DetectorPvEnum,
self.pvbase + f":{self._module_string}_CH{self.channel_number}_KILL",
name="kill_enabled",
)
self._append(
DetectorPvEnum,
self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_EMERGENCY_OFF",
name="emergency_off",
)
self._append(
DetectorPvEnum,
self.pvbase + f":{self._module_string}_CH{self.channel_number}_FINE_ADJUST",
name="fine_adjustment",
)
self._append(
DetectorPvEnum,
self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_VOLTAGE_CTRL",
name="constant_voltage_mode",
)
self._append(
DetectorPvEnum,
self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_LOW_CURR_MEAS",
name="current_readback_range_low",
)
self._append(
DetectorPvEnum,
self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_OUT_CURR_OOB",
name="current_readback_range_high",
)
self._append(
DetectorPvEnum,
self.pvbase + f":{self._module_string}_CH{self.channel_number}_OVERCURRENT",
name="overcurrent",
)
@spec_convenience
class MpodChannel(Assembly):
def __init__(self, pvbase, channel_number, module_string="LV_OMPV_1", name=None):
super().__init__(name=name)
self.pvbase = pvbase
self._module_string = module_string
self.channel_number = channel_number
self._append(
AdjustablePvEnum,
self.pvbase + f":{self._module_string}_CH{self.channel_number}_SWITCH_SP",
name="on",
is_setting=True,
)
self._append(
AdjustablePv,
self.pvbase + f":{self._module_string}_CH{self.channel_number}_OUTPUT_V_SP",
pvreadbackname=self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_MEAS_SENS_V",
pvlowlimname=self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_OUTPUT_V_SP.LOPR",
pvhighlimname=self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_OUTPUT_V_SP.HOPR",
name="voltage",
is_setting=True,
)
self._append(
AdjustablePv,
self.pvbase + f":{self._module_string}_CH{self.channel_number}_OUTPUT_V_SP",
pvlowlimname=self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_OUTPUT_V_SP.LOPR",
pvhighlimname=self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_OUTPUT_V_SP.HOPR",
name="voltage_setpoint",
is_setting=True,
)
self._append(
AdjustablePv,
self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_RMP_UP_RATE_SP",
pvlowlimname=self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_RMP_UP_RATE_SP.LOPR",
pvhighlimname=self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_RMP_UP_RATE_SP.HOPR",
name="ramp_up",
is_setting=True,
)
self._append(
AdjustablePv,
self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_RMP_DOWN_RATE_SP",
pvlowlimname=self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_RMP_DOWN_RATE_SP.LOPR",
pvhighlimname=self.pvbase
+ f":{self._module_string}_CH{self.channel_number}_RMP_DOWN_RATE_SP.HOPR",
name="ramp_down",
is_setting=True,
)
self._append(
AdjustablePv,
self.pvbase + f":{self._module_string}_CH{self.channel_number}_MEAS_OUT_A",
name="current",
is_setting=False,
is_display=True,
)
self._append(
MpodStatus,
self.pvbase,
self.channel_number,
self._module_string,
name="flags",
)
def get_current_value(self, *args, **kwargs):
return self.on.get_current_value(*args, **kwargs)
def set_target_value(self, *args, **kwargs):
return self.on.set_target_value(*args, **kwargs)
class MpodModule(Assembly):
def __init__(
self, pvbase, channelnumbers, channelnames, module_string="LV_OMPV_1", name=None
):
super().__init__(name=name)
for channelnumber, channelname in zip(channelnumbers, channelnames):
self._append(
MpodChannel,
pvbase,
channel_number=channelnumber,
module_string=module_string,
name=channelname,
)
# for new ioc by Thierry
flag_names_mpod = [
"outputOn",
"outputInhibit",
"outputFailureMinSenseVoltage",
"outputFailureMaxSenseVoltage",
"outputFailureMaxTerminalVoltage",
"outputFailureMaxCurrent",
"outputFailureMaxTemperature",
"outputFailureMaxPower",
"outputFailureTimeout",
"outputCurrentLimited",
"outputRampUp",
"outputRampDown",
"outputEnableKill",
"outputEmergencyOff",
"outputAdjusting",
"outputConstantVoltage",
"outputLowCurrentRange",
"outputCurrentBoundsExceeded",
"outputFailureCurrentLimit",
"outputCurrentIncreasing",
"outputCurrentDecreasing",
"outputConstantPower",
"outputVoltageRampSpeedLimited",
"outputVoltageBottomReached",
"outputInitCrcCheckBad",
]
class NEW_MpodFlags(Assembly):
def __init__(self, flags, name="flags"):
super().__init__(name=name)
self._flags = flags
for flag_name in flag_names_mpod:
self._append(
DetectorVirtual,
[self._flags],
partial(self._get_flag_name_value, flag_name=flag_name),
name=flag_name,
is_status=False,
is_display=True,
)
def _get_flag_name_value(self, value, flag_name=None):
index = flag_names_mpod.index(flag_name)
return int("{0:015b}".format(int(value))[-1 * (index + 1)]) == 1
class NEW_MpodChannel(Assembly):
def __init__(self, pvbase, channel_number, module_string, name=None):
super().__init__(name=name)
self.pvbase = pvbase
self._module_string = module_string
self.channel_number = channel_number
self._append(
AdjustablePvEnum,
self.pvbase + f":{self._module_string}0{self.channel_number}-SWITCH_SP",
name="on",
is_setting=True,
is_display=True,
)
self._append(
AdjustablePv,
self.pvbase + f":{self._module_string}0{self.channel_number}-V_SP",
pvreadbackname=self.pvbase
+ f":{self._module_string}0{self.channel_number}-V_RB",
pvlowlimname=self.pvbase,
name="voltage",
is_setting=True,
is_display=True,
)
self._append(
AdjustablePv,
self.pvbase + f":{self._module_string}0{self.channel_number}-I_SP",
pvreadbackname=self.pvbase
+ f":{self._module_string}0{self.channel_number}-I_RB",
pvlowlimname=self.pvbase,
name="current",
is_setting=True,
is_display=True,
)
# self._append(
# AdjustablePv,
# self.pvbase + f":{self._module_string}0{self.channel_number}-VRISE_SP",
# pvreadbackname=self.pvbase
# + f":{self._module_string}0{self.channel_number}-VRISE_RB",
# pvlowlimname=self.pvbase,
# name="V_rise",
# is_setting=True,
# is_display=True,
# )
# self._append(
# AdjustablePv,
# self.pvbase + f":{self._module_string}0{self.channel_number}-IRISE_SP",
# pvreadbackname=self.pvbase
# + f":{self._module_string}0{self.channel_number}-IRISE_RB",
# pvlowlimname=self.pvbase,
# name="I_rise",
# is_setting=True,
# is_display=True,
# )
# self._append(
# AdjustablePv,
# self.pvbase + f":{self._module_string}0{self.channel_number}-VFALL_SP",
# pvreadbackname=self.pvbase
# + f":{self._module_string}0{self.channel_number}-VFALL_RB",
# pvlowlimname=self.pvbase,
# name="V_fall",
# is_setting=True,
# is_display=True,
# )
# self._append(
# AdjustablePv,
# self.pvbase + f":{self._module_string}0{self.channel_number}-IFALL_SP",
# pvreadbackname=self.pvbase
# + f":{self._module_string}0{self.channel_number}-IFALL_RB",
# pvlowlimname=self.pvbase,
# name="I_fall",
# is_setting=True,
# is_display=True,
# )
self._append(
DetectorPvData,
self.pvbase + f":{self._module_string}0{self.channel_number}-STAT",
name="_flags",
is_setting=False,
)
self._append(
NEW_MpodFlags,
self._flags,
name="flags",
is_setting=False,
is_status=True,
)
def get_current_value(self, *args, **kwargs):
return self.on.get_current_value(*args, **kwargs)
def set_target_value(self, *args, **kwargs):
return self.on.set_target_value(*args, **kwargs)
class NEW_MpodModule(Assembly):
def __init__(self, pvbase, channelnumbers, channelnames, module_string, name=None):
super().__init__(name=name)
for channelnumber, channelname in zip(channelnumbers, channelnames):
self._append(
NEW_MpodChannel,
pvbase,
channel_number=channelnumber,
module_string=module_string,
name=channelname,
)
+99
View File
@@ -0,0 +1,99 @@
from epics import PV
import os
import numpy as np
import time
from .utilities import Changer
from ..aliases import Alias
from time import sleep
class PvRecord:
def __init__(
self,
pvsetname,
pvreadbackname=None,
accuracy=None,
sleeptime=0,
name=None,
elog=None,
):
self.Id = pvsetname
self.name = name
self.alias = Alias(name)
self.sleeptime = sleeptime
self._pv = PV(self.Id, connection_timeout=0.05, auto_monitor=True)
self._currentChange = None
self.accuracy = accuracy
if pvreadbackname is None:
self._pvreadback = PV(self.Id, connection_timeout=0.05, auto_monitor=True)
alias_fields = {"set": pvsetname}
else:
self._pvreadback = PV(
pvreadbackname, connection_timeout=0.05, auto_monitor=True
)
alias_fields = {"set": pvsetname, "readback": pvreadbackname}
for name, ch in alias_fields.items():
self.alias.append(Alias(name, channel=ch, channeltype="CA"))
def get_current_value(self, readback=True):
if readback:
currval = self._pvreadback.get()
if not readback:
currval = self._pv.get()
return currval
def get_moveDone(self):
"""Adjustable convention"""
""" 0: moving 1: move done"""
movedone = 1
if self.accuracy is not None:
if (
np.abs(
self.get_current_value(readback=False)
- self.get_current_value(readback=True)
)
> self.accuracy
):
movedone = 0
else:
sleep(self.sleeptime)
return movedone
def move(self, value):
self._pv.put(value)
time.sleep(0.1)
while self.get_moveDone() == 0:
time.sleep(0.1)
def set_target_value(self, value, hold=False):
"""Adjustable convention"""
changer = lambda value: self.move(value)
return Changer(
target=value, parent=self, changer=changer, hold=hold, stopper=None
)
# spec-inspired convenience methods
def mv(self, value):
self._currentChange = self.set_target_value(value)
def wm(self, *args, **kwargs):
return self.get_current_value(*args, **kwargs)
def mvr(self, value, *args, **kwargs):
if self.get_moveDone == 1:
startvalue = self.get_current_value(readback=True, *args, **kwargs)
else:
startvalue = self.get_current_value(readback=False, *args, **kwargs)
self._currentChange = self.set_target_value(value + startvalue, *args, **kwargs)
def wait(self):
self._currentChange.wait()
def __repr__(self):
return "%s is at: %s" % (self.Id, self.get_current_value())
+32
View File
@@ -0,0 +1,32 @@
from eco.devices_general.wago import AnalogInput
from eco.elements.adjustable import AdjustableVirtual
class OxygenSensor(AnalogInput):
def __init__(self, pvname, name=None):
super().__init__(pvname, name=name)
self.unit.set_target_value("%")
def set_no_oxygen(self, raw_val=None):
if not raw_val:
raw_val = self.raw.get_current_value()
slo = self.linear_calibration_slope.get_current_value()
off = self.linear_calibration_offset.get_current_value()
r0 = raw_val
r100 = (100 - off) / slo
slo_n = 100 / (r100 - r0)
off_n = -slo_n * r0
self.linear_calibration_offset(off_n)
self.linear_calibration_slope(slo_n)
def set_full_oxygen(self, raw_val=None):
if not raw_val:
raw_val = self.raw.get_current_value()
slo = self.linear_calibration_slope.get_current_value()
off = self.linear_calibration_offset.get_current_value()
r0 = -(off / slo)
r100 = raw_val
slo_n = 100 / (r100 - r0)
off_n = -slo_n * r0
self.linear_calibration_offset(off_n)
self.linear_calibration_slope(slo_n)
+70 -34
View File
@@ -4,9 +4,28 @@ from epics import PV, ca
import time
from ..eco_epics import device
from ..eco_epics.device import Device
from .utilities import Changer
from ..aliases import Alias
_guiTypes = ["xdm"]
_status_messages = {
-13: "invalid value (cannot convert to float). Move not attempted.",
-12: "target value outside soft limits. Move not attempted.",
-11: "drive PV is not connected: Move not attempted.",
-8: "move started, but timed-out.",
-7: "move started, timed-out, but appears done.",
-5: "move started, unexpected return value from PV.put()",
-4: "move-with-wait finished, soft limit violation seen",
-3: "move-with-wait finished, hard limit violation seen",
0: "move-with-wait finish OK.",
0: "move-without-wait executed, not cpmfirmed",
1: "move-without-wait executed, move confirmed",
3: "move-without-wait finished, hard limit violation seen",
4: "move-without-wait finished, soft limit violation seen",
}
def _keywordChecker(kw_key_list_tups):
for tkw, tkey, tlist in kw_key_list_tups:
@@ -14,7 +33,7 @@ def _keywordChecker(kw_key_list_tups):
class SmarActException(Exception):
""" raised to indicate a problem with a smartact"""
"""raised to indicate a problem with a smartact"""
def __init__(self, msg, *args):
Exception.__init__(self, *args)
@@ -55,13 +74,22 @@ class SmarAct(Device):
class SmarActRecord:
def __init__(self, Id, name=None, elog=None):
def __init__(
self,
Id,
name=None,
elog=None,
alias_fields={"readback": "MOTRBV", "homed": "GET_HOMED"},
):
self.Id = Id
self._device = Device(Id, delim=":")
self._drive = SmarAct(Id + ":DRIVE")
self._rbv = SmarAct(Id + ":MOTRBV")
self._hlm = SmarAct(Id + ":HLM")
self._llm = SmarAct(Id + ":LLM")
self._status = SmarAct(Id + ":STATUS")
self._statusstg = SmarAct(Id + ":STATUS")
self._status = []
self._set_pos = SmarAct(Id + ":SET_POS")
self._stop = SmarAct(Id + ":STOP")
self._hold = SmarAct(Id + ":HOLD")
@@ -69,22 +97,35 @@ class SmarActRecord:
self._elog = elog
self.name = name
self.units = self._drive.get("EGU")
self.alias = Alias(name)
for an, af in alias_fields.items():
self.alias.append(
Alias(an, channel=".".join([self.Id, af]), channeltype="CA")
)
# Conventional methods and properties for all Adjustable objects
def changeTo(self, value, hold=False, check=True):
""" Adjustable convention"""
def set_target_value(self, value, hold=False, check=True):
"""Adjustable convention"""
mover = lambda value: self.move(value, ignore_limits=(not check), wait=True)
def changer(value):
self._status = self.move(value, ignore_limits=(not check), wait=True)
self._status_message = _status_messages[self._status]
# if not self._status == 0:
# print(self._status_message)
# mover = lambda value: self.move(\
# value, ignore_limits=(not check),
# wait=True)
return Changer(
target=value,
parent=self,
mover=mover,
changer=changer,
hold=hold,
stopper=self._stop.put("PROC", 1),
)
def stop(self):
""" Adjustable convention"""
"""Adjustable convention"""
try:
self._currentChange.stop()
except:
@@ -92,7 +133,7 @@ class SmarActRecord:
pass
def within_limits(self, val):
""" returns whether a value for a motor is within drive limits"""
"""returns whether a value for a motor is within drive limits"""
return val <= self._hlm.get("VAL") and val >= self._llm.get("VAL")
def move(
@@ -104,7 +145,7 @@ class SmarActRecord:
ignore_limits=False,
confirm_move=False,
):
""" moves smaract drive to position
"""moves smaract drive to position
arguments:
==========
@@ -126,7 +167,7 @@ class SmarActRecord:
-3 : move-with-wait finished, hard limit violation seen
0 : move-with-wait finish OK.
0 : move-without-wait executed, not cpmfirmed
1 : move-without-wait executed, move confirmed
1 : move-without-wait executed, move confirmed
3 : move-without-wait finished, hard limit violation seen
4 : move-without-wait finished, soft limit violation seen
@@ -157,7 +198,7 @@ class SmarActRecord:
return TIMEOUT
if 1 == stat:
s0 = self._status.get("VAL")
s0 = self._statusstg.get("VAL")
s1 = s0
t0 = time.time()
t1 = t0 + min(10.0, timeout) # should be moving by now
@@ -166,15 +207,15 @@ class SmarActRecord:
if wait or confirm_move:
while time.time() <= thold and s1 == 3:
ca.poll(evt=1.0e-2)
s1 = self._status.get("VAL")
s1 = self._statusstg.get("VAL")
while time.time() <= t1 and s1 == 0:
ca.poll(evt=1.0e-2)
s1 = self._status.get("VAL")
s1 = self._statusstg.get("VAL")
if s1 == 4:
if wait:
while time.time() <= tout and s1 == 4:
ca.poll(evt=1.0e-2)
s1 = self._status.get("VAL")
s1 = self._statusstg.get("VAL")
if s1 == 3 or s1 == 4:
if time.time() > tout:
return TIMEOUT
@@ -207,33 +248,32 @@ class SmarActRecord:
return self._set_pos.put("VAL", value)
def get_precision(self):
""" Adjustable convention"""
"""Adjustable convention"""
pass
def set_precision(self):
""" Adjustable convention"""
"""Adjustable convention"""
pass
precision = property(get_precision, set_precision)
def set_speed(self):
""" Adjustable convention"""
"""Adjustable convention"""
pass
def get_speed(self):
""" Adjustable convention"""
"""Adjustable convention"""
pass
def set_speedMax(self):
""" Adjustable convention"""
"""Adjustable convention"""
pass
def get_moveDone(self):
""" Adjustable convention"""
pass
def set_limits(self, values, posType="user", relative_to_present=False):
""" Adjustable convention"""
"""Adjustable convention"""
if relative_to_present:
v = self.get_current_value()
values = [v - values[0], v - values[1]]
@@ -241,11 +281,11 @@ class SmarActRecord:
self._hlm.put("VAL", values[1])
def get_limits(self, posType="user"):
""" Adjustable convention"""
"""Adjustable convention"""
return self._llm.get("VAL"), self._hlm.get("VAL")
def gui(self, guiType="xdm"):
""" Adjustable convention"""
"""Adjustable convention"""
cmd = ["caqtdm", "-macro"]
for i in range(len(self.Id) - 1):
@@ -257,23 +297,19 @@ class SmarActRecord:
cmd.append('"P=%s,M=%s"' % (P, M))
# #cmd.append('/sf/common/config/qt/motorx_more.ui')
cmd.append("ESB_MX_SMARACT_mot_exp.ui")
cmd.append("/sf/common/config/qt/ESB_MX_SmarAct_mot_exp.ui")
# #os.system(' '.join(cmd))
return subprocess.Popen(" ".join(cmd), shell=True)
def mv(self, value):
self._currentChange = self.changeTo(value)
self._currentChange = self.set_target_value(value)
def wm(self, *args, **kwargs):
return self.get_current_value(*args, **kwargs)
def mvr(self, value, *args, **kwargs):
if self.get_moveDone == 1:
startvalue = self.get_current_value(readback=True, *args, **kwargs)
else:
startvalue = self.get_current_value(readback=False, *args, **kwargs)
self._currentChange = self.changeTo(value + startvalue, *args, **kwargs)
startvalue = self.get_current_value(readback=True, *args, **kwargs)
self._currentChange = self.set_target_value(value + startvalue, *args, **kwargs)
def wait(self):
self._currentChange.wait()
@@ -287,7 +323,7 @@ class SmarActRecord:
return self.__str__()
def __call__(self, value):
self._currentChange = self.changeTo(value)
self._currentChange = self.set_target_value(value)
class SmarActDevice(SmarActRecord):
@@ -316,7 +352,7 @@ class SmarActStage:
return str({key: self.__dict__[key].wm() for key in self._keys})
class Changer:
class Changer_old:
def __init__(self, target=None, parent=None, mover=None, hold=True, stopper=None):
self.target = target
self._mover = mover
+28
View File
@@ -0,0 +1,28 @@
from eco.elements.assembly import Assembly
from eco.epics.adjustable import AdjustablePv, AdjustablePvEnum
from eco.epics.detector import DetectorPvData
class SpectrometerAndor(Assembly):
def __init__(self, pvname, name=None):
super().__init__(name=name)
self.pvname=pvname
self._append(DetectorPvData, pvname + ":SERIAL_NUM", unit='', name="sno_spectrometer")
self._append(
AdjustablePv, pvname + ":SEND_WL", pvreadbackname= pvname + ":WAVELENGTH", name="wavelength", is_setting=True, unit='nm'
)
self._append(
AdjustablePv, pvname + ":SEND_SW", pvreadbackname= pvname + ":SLIT_WIDTH", name="slit_width", is_setting=True
)
self._append(DetectorPvData, pvname + ":MIN_WL", unit='nm', name="min_wavelength")
self._append(DetectorPvData, pvname + ":MAX_WL", unit='nm', name="max_wavelength")
self._append(DetectorPvData, pvname + ":FOCAL_POS", unit='mm', name="focal_position")
self._append(DetectorPvData, pvname + ":TURRET", unit='', name="turret_number")
self._append(DetectorPvData, pvname + ":NUM_GRATINGS", unit='', name="noof_installed_gratings")
self._append(DetectorPvData, pvname + ":GRATING_NUM", unit='', name="active_grating")
self._append(DetectorPvData, pvname + ":LPMM", unit='lns/mm', name="line_density")
+64
View File
@@ -0,0 +1,64 @@
from functools import partial
from eco import Assembly
from eco.elements.detector import DetectorVirtual
from eco.epics.detector import DetectorPvData
class ChillerThermotek(Assembly):
def __init__(self,pvbase="SARES20-CHIL",name=None):
self.pvbase = pvbase
super().__init__(name=name)
self._append(DetectorPvData,pvbase+":H2O_FLUSS", name='flow_rate', is_display=True)
self._append(DetectorPvData,pvbase+":T_VORLAUF", name='temp_feed', is_display=True)
self._append(DetectorPvData,pvbase+":bitIO",name='bitIO',is_display=False)
self._append(ThermotekChillerFlags,self.bitIO,name='flags')
flag_names_thermotek_chiller = [
"operation",
"error",
"pressure_state",
"temperature_state",
"flow_state",
"state_flow2",
"liquid_level_state",
"condictivity_state",
"ambient_temperature_state",
"interruption_clearance",
"alarm_beep",
"control_valve",
"compressor",
"heater",
"pump",
"LF_valve",
"remote_start",
"error_max_pressure",
"error_min_pressure",
"error_fill_level",
"warning_fill_level",
"flow_ping0",
"flow_ping1",
# "flow_switch",
]
class ThermotekChillerFlags(Assembly):
def __init__(self, flags, name="flags"):
super().__init__(name=name)
self._flags = flags
for flag_name in flag_names_thermotek_chiller:
self._append(
DetectorVirtual,
[self._flags],
partial(self._get_flag_name_value, flag_name=flag_name),
name=flag_name,
is_status=True,
is_display=True,
)
def _get_flag_name_value(self, value, flag_name=None):
index = flag_names_thermotek_chiller.index(flag_name)
return int("{0:015b}".format(int(value))[-1 * (index + 1)]) == 1
+9 -9
View File
@@ -58,7 +58,7 @@ class Storage(object):
class Pockels_trigger(PV):
""" this class is needed to store the offset in files and read in s """
"""this class is needed to store the offset in files and read in s"""
def __init__(self, pv_basename):
pvname = pv_basename + "-RB"
@@ -75,7 +75,7 @@ class Pockels_trigger(PV):
return super().get() * 1e-6
def get(self):
""" convert time to sec """
"""convert time to sec"""
return self.get_dial() - self.offset
def store(self, value=None):
@@ -98,7 +98,7 @@ class Pockels_trigger(PV):
class Phase_shifter(PV):
""" this class is needed to store the offset in files and read in ps """
"""this class is needed to store the offset in files and read in ps"""
def __init__(self, pv_basename="SLAAR01-TSPL-EPL"):
pvname = pv_basename + ":CURR_DELTA_T"
@@ -116,7 +116,7 @@ class Phase_shifter(PV):
return super().get() * 1e-12
def get(self):
""" convert time to sec """
"""convert time to sec"""
return self.get_dial() - self.offset
def store(self, value=None):
@@ -155,18 +155,18 @@ class PhaseShifterAramis:
self._elog = elog
self.name = name
def changeTo(self, value, hold=False, check=True):
""" Adjustable convention"""
def set_target_value(self, value, hold=False, check=True):
"""Adjustable convention"""
mover = lambda value: self._pshifter.move(value)
return Changer(target=value, parent=self, mover=mover, hold=hold, stopper=None)
def stop(self):
""" Adjustable convention"""
"""Adjustable convention"""
pass
def get_current_value(self, posType="user", readback=True):
""" Adjustable convention"""
"""Adjustable convention"""
_keywordChecker([("posType", posType, _posTypes)])
if posType == "user":
return self._pshifter.get()
@@ -174,7 +174,7 @@ class PhaseShifterAramis:
return self._pshifter.get_dial()
def set_current_value(self, value, posType="user"):
""" Adjustable convention"""
"""Adjustable convention"""
_keywordChecker([("posType", posType, _posTypes)])
if posType == "user":
return self._motor.set(value)
+6 -6
View File
@@ -33,7 +33,7 @@ class User_to_motor:
return motor_pos
def get_current_value(self):
""" Adjustable convention"""
"""Adjustable convention"""
motor_pos = self._stage.get_current_value()
motor_pos -= self.offset
user = motor_pos * self.conv
@@ -44,17 +44,17 @@ class User_to_motor:
self._stage.set_current_value(motor_pos)
return (value, motor_pos)
def changeTo(self, value, hold=False, check=True):
def set_target_value(self, value, hold=False, check=True):
value = self.user_to_motor(value) + self.offset
user = (value - self.offset) * self.conv
return self._stage.changeTo(value, hold, check)
return self._stage.set_target_value(value, hold, check)
def gui(self, guiType="xdm"):
return self._stage.gui()
# spec-inspired convenience methods
def mv(self, value):
self._stage._currentChange = self.changeTo(value)
self._stage._currentChange = self.set_target_value(value)
def wm(self, *args, **kwargs):
return self.get_current_value(*args, **kwargs)
@@ -67,7 +67,7 @@ class User_to_motor:
self._stage._currentChange.wait()
def stop(self):
""" Adjustable convention"""
"""Adjustable convention"""
try:
self._stage._currentChange.stop()
except:
@@ -82,4 +82,4 @@ class User_to_motor:
return self.__str__()
def __call__(self, value):
self._currentChange = self.changeTo(value)
self._currentChange = self.set_target_value(value)
+22 -4
View File
@@ -1,4 +1,5 @@
from threading import Thread
from ..utilities import PropagatingThread
class Changer:
@@ -6,12 +7,17 @@ class Changer:
self.target = target
self._changer = changer
self._stopper = stopper
self._thread = Thread(target=self._changer, args=(target,))
self._thread = PropagatingThread(target=self._changer, args=(target,))
# self._thread = Thread(target=self._changer, args=(target,))
if not hold:
self._thread.start()
def wait(self):
self._thread.join()
def wait(self, timeout=None):
self._thread.join(timeout=timeout)
if self._thread.is_alive():
raise TimeoutError(
f"Changer did not finish within timeout period {timeout}."
)
def start(self):
self._thread.start()
@@ -20,10 +26,22 @@ class Changer:
if self._thread.ident is None:
return "waiting"
else:
if self._thread.isAlive:
if self._thread.is_alive():
return "changing"
else:
return "done"
def is_alive(self):
if self._thread.ident is None:
return True
else:
if self._thread.is_alive():
return True
else:
return False
def isAlive(self):
return self.is_alive()
def stop(self):
self._stopper()
+195
View File
@@ -0,0 +1,195 @@
from eco.elements.assembly import Assembly
from eco.epics.detector import DetectorPvData
from eco.epics.adjustable import (
AdjustablePvString,
AdjustablePv,
spec_convenience,
tweak_option,
)
class AnalogInput(Assembly):
def __init__(self, pvname, name=None):
"""Analog input, which is defined by a PV name. There are linear calibration
options (hidden adjustment and visible linear_calibration values):
value = raw*linear_calibration_slope + linear_calibration_offset"""
super().__init__(name=name)
self.pvname = pvname
self._append(
DetectorPvData, self.pvname, name="value", is_setting=False, is_display=True
)
self._append(
AdjustablePvString,
self.pvname + ".DESC",
name="description",
is_setting=True,
is_display=True,
)
self._append(
AdjustablePvString,
self.pvname + ".EGU",
name="unit",
is_setting=False,
is_display=False,
)
self.value.unit = self.unit
self._append(
DetectorPvData,
self.pvname + ".RVAL",
name="raw",
is_setting=False,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ".AOFF",
name="_adj_offset",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ".ASLO",
name="_adj_slope",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ".EOFF",
name="linear_calibration_offset",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ".ESLO",
name="linear_calibration_slope",
is_setting=True,
is_display=False,
)
def get_current_value(self):
return self.value.get_current_value()
def reset_offset_current_value_to(self,value=0):
self.linear_calibration_offset.set_target_value(
(-1)
* self.raw.get_current_value()
* self.linear_calibration_slope.get_current_value()
* self._adj_slope.get_current_value()
+ value
).wait()
def reset_slope_current_value_to(self,value=1):
oslo = self.linear_calibration_slope.get_current_value()
ooff = self.linear_calibration_offset.get_current_value()
ooff_raw = ooff / oslo / self._adj_slope.get_current_value()
# print(ooff_raw)
nslo = value / (self.raw.get_current_value()+ooff_raw)
self.linear_calibration_slope.set_target_value(nslo).wait()
self.linear_calibration_offset.set_target_value(
ooff_raw
* nslo
* self._adj_slope.get_current_value()
).wait()
class WagoAnalogInputs(Assembly):
def __init__(self, pvbase, name=None):
super().__init__(name=name)
self.pvbase = pvbase
for n in range(1, 9):
self._append(AnalogInput, pvbase + f":ADC{n:02d}", name=f"ch{n:d}")
@spec_convenience
@tweak_option
class AnalogOutput(Assembly):
def __init__(self, pvname, name=None):
super().__init__(name=name)
self.pvname = pvname
self._append(
AdjustablePv,
self.pvname,
name="value",
is_setting=True,
is_display=True,
)
self._append(
AdjustablePvString,
self.pvname + ".DESC",
name="description",
is_setting=True,
is_display=True,
)
self._append(
AdjustablePvString,
self.pvname + ".EGU",
name="unit",
is_setting=False,
is_display=False,
)
self._append(
DetectorPvData,
self.pvname + ".RVAL",
name="raw",
is_setting=False,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ".AOFF",
name="_adj_offset",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ".ASLO",
name="_adj_slope",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ".EOFF",
name="linear_calibration_offset",
is_setting=True,
is_display=False,
)
self._append(
AdjustablePv,
self.pvname + ".ESLO",
name="linear_calibration_slope",
is_setting=True,
is_display=False,
)
def get_current_value(self):
return self.value.get_current_value()
def set_target_value(self, *args, **kwargs):
return self.value.set_target_value(*args, **kwargs)
def __call__(self, *args):
if args:
self.value.set_target_Value(*args).wait()
else:
return self.value.get_current_value()
class WagoAnalogOutputs(Assembly):
def __init__(self, pvbase, name=None):
super().__init__(name=name)
self.pvbase = pvbase
for n in range(1, 9):
self._append(
AnalogOutput,
pvbase + f":DAC{n:02d}",
name=f"ch{n:d}",
is_setting=True,
is_display=True,
)
+6 -3
View File
@@ -5,6 +5,7 @@
"""
basic device object defined
"""
from epics.ca import poll
from epics.pv import get_pv
import time
@@ -134,12 +135,14 @@ class Device(object):
if attrs is not None:
for attr in attrs:
self.PV(attr, connect=False, connection_timeout=timeout)
# self.PV(attr, connect=False, connection_timeout=timeout)
self.PV(attr, connect=False, timeout=timeout)
if aliases:
for attr in aliases.values():
if attrs is None or attr not in attrs:
self.PV(attr, connect=False, connection_timeout=timeout)
# self.PV(attr, connect=False, connection_timeout=timeout)
self.PV(attr, connect=False, timeout=timeout)
if with_poll:
poll()
@@ -215,7 +218,7 @@ class Device(object):
"""write save state to external file.
If state is not provided, the current state is used
Note that this only writes data for PVs with write-access, and count=1 (except CHAR """
Note that this only writes data for PVs with write-access, and count=1 (except CHAR"""
if state is None:
state = self.save_state()
out = [
+112 -112
View File
@@ -50,11 +50,11 @@ import sys
import time
from epics import ca
from . import device
from epics import device
class MotorLimitException(Exception):
""" raised to indicate a motor limit has been reached """
"""raised to indicate a motor limit has been reached"""
def __init__(self, msg, *args):
Exception.__init__(self, *args)
@@ -65,7 +65,7 @@ class MotorLimitException(Exception):
class MotorException(Exception):
""" raised to indicate a problem with a motor"""
"""raised to indicate a problem with a motor"""
def __init__(self, msg, *args):
Exception.__init__(self, *args)
@@ -77,59 +77,59 @@ class MotorException(Exception):
class Motor(device.Device):
"""Epics Motor Class for pyepics3
This module provides a class library for the EPICS motor record.
It uses the epics.Device and epics.PV classese
This module provides a class library for the EPICS motor record.
Virtual attributes:
These attributes do not appear in the dictionary for this class, but
are implemented with the __getattr__ and __setattr__ methods. They
simply get or putthe appropriate motor record fields. All attributes
can be both read and written unless otherwise noted.
It uses the epics.Device and epics.PV classese
Attribute Description Field
--------- ----------------------- -----
drive Motor Drive Value .VAL
readback Motor Readback Value .RBV (read-only)
slew_speed Slew speed or velocity .VELO
base_speed Base or starting speed .VBAS
acceleration Acceleration time (sec) .ACCL
description Description of motor .DESC
resolution Resolution (units/step) .MRES
high_limit High soft limit (user) .HLM
low_limit Low soft limit (user) .LLM
dial_high_limit High soft limit (dial) .DHLM
dial_low_limit Low soft limit (dial) .DLLM
backlash Backlash distance .BDST
offset Offset from dial to user .OFF
done_moving 1=Done, 0=Moving, read-only .DMOV
Exceptions:
The check_limits() method raises an 'MotorLimitException' if a soft limit
or hard limit is detected. The move() method calls
check_limits() unless they are called with the
ignore_limits=True keyword set.
Virtual attributes:
These attributes do not appear in the dictionary for this class, but
are implemented with the __getattr__ and __setattr__ methods. They
simply get or putthe appropriate motor record fields. All attributes
can be both read and written unless otherwise noted.
Example use:
from epics import Motor
m = Motor('13BMD:m38')
m.move(10) # Move to position 10 in user coordinates
m.move(100, dial=True) # Move to position 100 in dial coordinates
m.move(1, step=True, relative=True) # Move 1 step relative to current position
Attribute Description Field
--------- ----------------------- -----
drive Motor Drive Value .VAL
readback Motor Readback Value .RBV (read-only)
slew_speed Slew speed or velocity .VELO
base_speed Base or starting speed .VBAS
acceleration Acceleration time (sec) .ACCL
description Description of motor .DESC
resolution Resolution (units/step) .MRES
high_limit High soft limit (user) .HLM
low_limit Low soft limit (user) .LLM
dial_high_limit High soft limit (dial) .DHLM
dial_low_limit Low soft limit (dial) .DLLM
backlash Backlash distance .BDST
offset Offset from dial to user .OFF
done_moving 1=Done, 0=Moving, read-only .DMOV
m.stop() # Stop moving immediately
high = m.high_limit # Get the high soft limit in user coordinates
m.dial_high_limit = 100 # Set the high limit to 100 in dial coodinates
speed = m.slew_speed # Get the slew speed
m.acceleration = 0.1 # Set the acceleration to 0.1 seconds
p=m.get_position() # Get the desired motor position in user coordinates
p=m.get_position(dial=1) # Get the desired motor position in dial coordinates
p=m.get_position(readback=1) # Get the actual position in user coordinates
p=m.get_position(readback=1, step=1) Get the actual motor position in steps
p=m.set_position(100) # Set the current position to 100 in user coordinates
# Puts motor in Set mode, writes value, puts back in Use mode.
p=m.set_position(10000, step=1) # Set the current position to 10000 steps
Exceptions:
The check_limits() method raises an 'MotorLimitException' if a soft limit
or hard limit is detected. The move() method calls
check_limits() unless they are called with the
ignore_limits=True keyword set.
Example use:
from epics import Motor
m = Motor('13BMD:m38')
m.move(10) # Move to position 10 in user coordinates
m.move(100, dial=True) # Move to position 100 in dial coordinates
m.move(1, step=True, relative=True) # Move 1 step relative to current position
m.stop() # Stop moving immediately
high = m.high_limit # Get the high soft limit in user coordinates
m.dial_high_limit = 100 # Set the high limit to 100 in dial coodinates
speed = m.slew_speed # Get the slew speed
m.acceleration = 0.1 # Set the acceleration to 0.1 seconds
p=m.get_position() # Get the desired motor position in user coordinates
p=m.get_position(dial=1) # Get the desired motor position in dial coordinates
p=m.get_position(readback=1) # Get the actual position in user coordinates
p=m.get_position(readback=1, step=1) Get the actual motor position in steps
p=m.set_position(100) # Set the current position to 100 in user coordinates
# Puts motor in Set mode, writes value, puts back in Use mode.
p=m.set_position(10000, step=1) # Set the current position to 10000 steps
"""
@@ -272,7 +272,7 @@ class Motor(device.Device):
return self.__repr__()
def __getattr__(self, attr):
" internal method "
"internal method"
if attr in self._alias:
attr = self._alias[attr]
if attr in self._pvs:
@@ -315,7 +315,7 @@ class Motor(device.Device):
raise MotorException("EpicsMotor has no attribute %s" % attr)
def check_limits(self):
""" check motor limits:
"""check motor limits:
returns None if no limits are violated
raises expection if a limit is violated"""
for field, msg in (
@@ -328,7 +328,7 @@ class Motor(device.Device):
return
def within_limits(self, val, dial=False):
""" returns whether a value for a motor is within drive limits
"""returns whether a value for a motor is within drive limits
with dial=True dial limits are used (default is user limits)"""
ll_name, hl_name = "LLM", "HLM"
if dial:
@@ -347,7 +347,7 @@ class Motor(device.Device):
ignore_limits=False,
confirm_move=False,
):
""" moves motor drive to position
"""moves motor drive to position
arguments:
==========
@@ -372,7 +372,7 @@ class Motor(device.Device):
-3 : move-with-wait finished, hard limit violation seen
0 : move-with-wait finish OK.
0 : move-without-wait executed, not cpmfirmed
1 : move-without-wait executed, move confirmed
1 : move-without-wait executed, move confirmed
3 : move-without-wait finished, hard limit violation seen
4 : move-without-wait finished, soft limit violation seen
@@ -442,33 +442,33 @@ class Motor(device.Device):
def get_position(self, dial=False, readback=False, step=False, raw=False):
"""
Returns the target or readback motor position in user, dial or step
coordinates.
Keywords:
readback:
Set readback=True to return the readback position in the
desired coordinate system. The default is to return the
drive position of the motor.
dial:
Set dial=True to return the position in dial coordinates.
The default is user coordinates.
raw (or step):
Set raw=True to return the raw position in steps.
The default is user coordinates.
Returns the target or readback motor position in user, dial or step
coordinates.
Notes:
The "raw" or "step" and "dial" keywords are mutually exclusive.
The "readback" keyword can be used in user, dial or step
coordinates.
Examples:
m=epicsMotor('13BMD:m38')
m.move(10) # Move to position 10 in user coordinates
p=m.get_position(dial=True) # Read the target position in dial coordinates
p=m.get_position(readback=True, step=True) # Read the actual position in steps
Keywords:
readback:
Set readback=True to return the readback position in the
desired coordinate system. The default is to return the
drive position of the motor.
dial:
Set dial=True to return the position in dial coordinates.
The default is user coordinates.
raw (or step):
Set raw=True to return the raw position in steps.
The default is user coordinates.
Notes:
The "raw" or "step" and "dial" keywords are mutually exclusive.
The "readback" keyword can be used in user, dial or step
coordinates.
Examples:
m=epicsMotor('13BMD:m38')
m.move(10) # Move to position 10 in user coordinates
p=m.get_position(dial=True) # Read the target position in dial coordinates
p=m.get_position(readback=True, step=True) # Read the actual position in steps
"""
pos, rbv = ("VAL", "RBV")
if dial:
@@ -480,8 +480,8 @@ class Motor(device.Device):
return self.get(pos)
def tweak(self, direction="foreward", wait=False, timeout=300.0):
""" move the motor by the tweak_val
"""move the motor by the tweak_val
takes optional args:
direction direction of motion (forward/reverse) [forward]
must start with 'rev' or 'back' for a reverse tweak.
@@ -507,30 +507,30 @@ class Motor(device.Device):
def set_position(self, position, dial=False, step=False, raw=False):
"""
Sets the motor position in user, dial or step coordinates.
Inputs:
position:
The new motor position
Keywords:
dial:
Set dial=True to set the position in dial coordinates.
The default is user coordinates.
raw:
Set raw=True to set the position in raw steps.
The default is user coordinates.
Notes:
The 'raw' and 'dial' keywords are mutually exclusive.
Examples:
m=epicsMotor('13BMD:m38')
m.set_position(10, dial=True) # Set the motor position to 10 in
# dial coordinates
m.set_position(1000, raw=True) # Set the motor position to 1000 steps
"""
Sets the motor position in user, dial or step coordinates.
Inputs:
position:
The new motor position
Keywords:
dial:
Set dial=True to set the position in dial coordinates.
The default is user coordinates.
raw:
Set raw=True to set the position in raw steps.
The default is user coordinates.
Notes:
The 'raw' and 'dial' keywords are mutually exclusive.
Examples:
m=epicsMotor('13BMD:m38')
m.set_position(10, dial=True) # Set the motor position to 10 in
# dial coordinates
m.set_position(1000, raw=True) # Set the motor position to 1000 steps
"""
# Put the motor in "SET" mode
self.put("SET", 1)
@@ -572,7 +572,7 @@ class Motor(device.Device):
self._callbacks[attr] = index
def refresh(self):
""" refresh all motor parameters currently in use:
"""refresh all motor parameters currently in use:
make sure all used attributes are up-to-date."""
ca.poll()
@@ -585,7 +585,7 @@ class Motor(device.Device):
self.STOP = 1
def make_step_list(self, minstep=0.0, maxstep=None, decades=10):
""" create a reasonable list of motor steps, as for a dropdown menu
"""create a reasonable list of motor steps, as for a dropdown menu
The list is based on motor range Mand precision"""
if maxstep is None:
@@ -617,7 +617,7 @@ class Motor(device.Device):
return out
def show_info(self):
" show basic motor settings "
"show basic motor settings"
ca.poll()
out = []
out.append(repr(self))
@@ -630,7 +630,7 @@ class Motor(device.Device):
ca.write("\n".join(out))
def show_all(self):
""" show all motor attributes"""
"""show all motor attributes"""
out = []
add = out.append
add("# Motor %s" % (self._prefix))
-64
View File
@@ -1,64 +0,0 @@
from epics import PV
class EnumWrapper:
def __init__(self, pvname, elog=None):
self._elog = elog
self._pv = PV(pvname)
self.names = self._pv.enum_strs
# print(self.names)
# if self.names:
self.setters = Positioner([(nam, lambda: self.set(nam)) for nam in self.names])
def set(self, target):
if type(target) is str:
assert target in self.names, (
"set value need to be one of \n %s" % self.names
)
self._pv.put(self.names.index(target))
elif type(target) is int:
assert target >= 0, "set integer needs to be positive"
assert target < len(self.names)
self._pv.put(target)
def get(self):
return self._pv.get()
def get_name(self):
return self.names[self.get()]
def __repr__(self):
return self.get_name()
class MonitorAccumulator:
def __init__(self, pv, attr=None, keywords=["value", "timestamp"]):
self.pv = pv
self.attr = attr
self.values = []
self.keywords = keywords
def _accumulate(self, **kwargs):
self.values.append([kwargs[kw] for kw in self.keywords])
def accumulate(self):
self.pv.add_callback(self._accumulate, self.attr)
def stop(self):
self.pv.remove_callbacks(self.attr)
def cycle(self):
self.stop()
d = self.values.copy()
self.values = []
self.accumulate()
return d
class Positioner:
def __init__(self, list_of_name_func_tuples):
for name, func in list_of_name_func_tuples:
tname = name.replace(" ", "_").replace(".", "p")
if tname[0].isnumeric():
tname = "v" + tname
self.__dict__[tname] = func
+9
View File
@@ -1,6 +1,15 @@
import logging
import json
from pathlib import Path
from .utilities.config import Configuration
startup_lazy = False
scopes = [
{"name": "Alvra", "facility": "SwissFEL", "module": "alvra"},
{"name": "Bernina", "facility": "SwissFEL", "module": "bernina"},
{"name": "SwissMX", "facility": "SwissFEL", "module": "swissmx"},
]
# settings = Configuration(Path.home() / '.ecorc', name='eco_startup_settings')
+3
View File
@@ -0,0 +1,3 @@
from .assembly import Assembly
+46
View File
@@ -0,0 +1,46 @@
# single acquisition class
class Acquisition:
def __init__(
self,
parent=None,
acquire=lambda: None,
acquisition_kwargs={},
hold=True,
stopper=None,
get_result=lambda: None,
):
self.acquisition_kwargs = acquisition_kwargs
for key, val in acquisition_kwargs.items():
self.__dict__[key] = val
self._stopper = stopper
self._get_result = get_result
if acquire:
self.set_acquire_foo(acquire, hold=hold)
def set_acquire_foo(self, acquire, hold=True):
self._acquire = acquire
self._thread = PropagatingThread(target=self._acquire)
if not hold:
self._thread.start()
def wait(self):
self._thread.join()
return self._get_result()
def start(self):
self._thread.start()
def status(self):
if self._thread.ident is None:
return "waiting"
else:
if self._thread.is_alive():
return "acquiring"
else:
return "done"
def stop(self):
self._stopper()
+94
View File
@@ -0,0 +1,94 @@
from eco.elements.detector import DetectorGet
from .assembly import Assembly
from .adjustable import AdjustableGetSet
from functools import partial
class AdjustableObject(Assembly):
def __init__(self, adjustable_dict, is_setting_children=False, name=None):
super().__init__(name=name)
self._append(adjustable_dict, name="_base_dict", is_setting=False)
self.init_object(is_setting_children=is_setting_children)
def set_field(self, fieldname, value):
d = self._base_dict.get_current_value()
if fieldname not in d.keys():
raise Exception(f"{fieldname} is not in dictionary")
d[fieldname] = value
self._base_dict.set_target_value(d)
def get_field(self, fieldname):
d = self._base_dict.get_current_value()
if fieldname not in d.keys():
raise Exception(f"{fieldname} is not in dictionary")
return d[fieldname]
def update_base_dict(self, updatedict):
tmp = self._base_dict.get_current_value()
tmp.update(updatedict)
self._base_dict.set_target_value(tmp)
self.__init__(self._base_dict, name=self.name)
def init_object(self, is_setting_children=False):
# super().__init__(name=self.name)
for k, v in self._base_dict.get_current_value().items():
tadj = AdjustableGetSet(
partial(self.get_field, k), partial(self.set_field, k), name=k
)
if k in self.__dict__.keys():
ln = f"{k}_"
else:
ln = f"{k}"
if type(v) is dict:
self._append(
AdjustableObject(tadj, name=k),
call_obj=False,
is_setting=is_setting_children,
name=ln,
is_display="recursive",
)
else:
self._append(
tadj,
call_obj=False,
is_setting=is_setting_children,
is_display=True,
name=ln,
)
class DetectorObject(Assembly):
def __init__(self, detector_dict, name=None):
super().__init__(name=name)
self._base_dict = detector_dict
self.init_object()
def get_field(self, fieldname):
d = self._base_dict.get_current_value()
if fieldname not in d.keys():
raise Exception(f"{fieldname} is not in dictionary")
return d[fieldname]
def init_object(self):
# super().__init__(name=self.name)
for k, v in self._base_dict.get_current_value().items():
tdet = DetectorGet(partial(self.get_field, k), name=k)
if k in self.__dict__.keys():
ln = f"{k}_"
else:
ln = f"{k}"
if type(v) is dict:
self._append(
DetectorObject(tdet, name=k),
call_obj=False,
is_setting=False,
name=ln,
is_display="recursive",
)
else:
self._append(
tdet, call_obj=False, is_setting=False, is_display=True, name=ln
)
File diff suppressed because it is too large Load Diff
+638
View File
@@ -0,0 +1,638 @@
from concurrent.futures import ThreadPoolExecutor
import copy
from datetime import datetime
from inspect import isclass
import json
from pathlib import Path
from tkinter import W
import weakref
from markdown import markdown
from numpy import isin
import numpy as np
from eco.acquisition.scan_data import run_status_convenience
from eco.elements.protocols import Detector, InitialisationWaitable
from eco.epics import get_from_archive
from ..aliases import Alias
from tabulate import tabulate
import colorama
from . import memory
from enum import Enum
import os
import subprocess
from rich.progress import track
from eco import Adjustable, Detector
import eco
_initializing_assemblies = []
class StatusCollection:
def __init__(self, parent, name="status_collection"):
self.parent = weakref.ref(parent)
self.selections = {}
if name is None:
raise Exception("A name of collection is required")
self.name = name
self._list = []
def get_list(self, selection=None, **kwargs):
rec_list_items = kwargs.get("rec_list_items", [])
ls = []
for witem in self._list:
item = witem()
if item is None:
continue
if item is self.parent:
continue
if item in ls:
continue
if selection is not None:
if selection not in self.selections.keys():
continue
item_name = item.alias.get_full_name(base=self.parent())
if item_name not in self.selections[selection].keys():
continue
recurse = self.selections[selection][item_name]["recurse"]
else:
recurse = True
ls.append(item)
# important to get field in case no recursion is defined.
if item is self.parent():
recurse = False
if hasattr(item, f"{self.name}") and isinstance(
item.__dict__[self.name], self.__class__
):
if recurse:
# if hasattr(item, "recursing") and item.recursing:
# print(
# f"recursing detected loop at {item.alias.get_full_name()}"
# )
# item.recursing = True
for titem in item.__dict__[self.name].get_list(
selection=selection, ls=[]
):
if titem not in ls:
ls.append(titem)
else:
if item not in ls:
ls.append(item)
else:
if item not in ls:
ls.append(item)
return ls
def get_names(self, selection=None):
return [
item.alias.get_full_name(base=self.parent())
for item in self.get_list(selection=selection)
]
def get_selections_names(self):
return self.selections.keys()
def append(self, obj, selection=None, recursive=True):
if selection is not None:
if selection not in self.selections:
self.selections[selection] = {}
obj_name = obj.alias.get_full_name(base=self.parent())
self.selections[selection][obj_name] = {"recurse": recursive}
if obj not in [tl() for tl in self._list]:
self._list.append(weakref.ref(obj))
def remove(self, obj, selection=None):
"""Remove an object from the collection. If selection is given, only remove from that selection."""
if obj in [wobj() for wobj in self._list]:
obj_name = obj.alias.get_full_name(base=self.parent())
if selection is None:
ix = [wobj() for wobj in self._list].index(obj)
self._list.remove(self._list[ix])
else:
raise ValueError("Item not in list")
if selection is not None:
selections = [selection]
else:
selections = self.selections.keys()
for selection in selections:
if obj_name in self.selections[selection]:
del self.selections[selection][obj_name]
def __call__(self):
return self.get_list()
class NumpyEncoder(json.JSONEncoder):
"""Special json encoder for numpy types"""
def default(self, obj):
if isinstance(obj, np.integer):
return int(obj)
elif isinstance(obj, np.floating):
return float(obj)
elif isinstance(obj, np.ndarray):
return obj.tolist()
return json.JSONEncoder.default(self, obj)
@get_from_archive
@run_status_convenience
class Assembly:
def __init__(self, name=None, parent=None, is_alias=True, elog=None):
self.name = name
self.alias = Alias(name, parent=parent)
# self.settings = []
# self.status_indicators = []
self.status_collection = StatusCollection(self, name="status_collection")
if memory.global_memory_dir:
self.memory = memory.Memory(self)
if elog:
self.__elog = elog
# else:
# self.__class__.__elog = property(lambda dum: ELOG)
# TODO: Lazy an threaded append! (for PVs, should be quite a speedup).
def _append(
self,
foo_obj_init,
*args,
name=None,
is_setting=False,
is_display=True,
is_status=True,
# recursive=None,
call_obj=True,
overwrite=False,
**kwargs,
):
"""This hidden method appends an object to the assembly. It can take either an object instance, or a class (in which case it will be called with the provided args and kwargs).
Parameters
----------
foo_obj_init : Adjustable, Detector, Assembly, class, callable
The object to append, or a class/callable to instantiate.
name : str, optional
The name of the object within the assembly. If None, the name attribute of the object will be used.
is_setting : bool or str "recursive", optional"""
if overwrite:
if name in self.__dict__:
old = self.__dict__[name]
self.status_collection.remove(old)
self.alias.pop_object(old.alias)
del old
if isinstance(foo_obj_init, Adjustable) and not isclass(foo_obj_init):
# adj_copy = copy.copy(foo_obj_init)
adj_copy = foo_obj_init
self.__dict__[name] = adj_copy
elif isinstance(foo_obj_init, Detector) and not isclass(foo_obj_init):
self.__dict__[name] = foo_obj_init
elif isinstance(foo_obj_init, Assembly) and not isclass(foo_obj_init):
self.__dict__[name] = foo_obj_init
elif call_obj and callable(foo_obj_init):
self.__dict__[name] = foo_obj_init(*args, **kwargs, name=name)
else:
self.__dict__[name] = foo_obj_init
self.alias.append(self.__dict__[name].alias)
self.status_collection.append(self.__dict__[name])
# if is_status == "auto":
# is_status = isinstance(self.__dict__[name], Detector)
if is_setting:
if isinstance(is_setting, str):
recursive = is_setting.lower() == "recursive"
else:
recursive = True
self.status_collection.append(
self.__dict__[name], selection="settings", recursive=recursive
)
# self.status_collection.append(
# self.__dict__[name], selection="settings", recursive=True
# )
if is_display:
if isinstance(is_display, str):
recursive = is_display.lower() == "recursive"
else:
recursive = False
self.status_collection.append(
self.__dict__[name], selection="display", recursive=recursive
)
def get_status(
self,
base="self",
verbose=False,
print_times=False,
channeltypes=None,
selections=[],
threads=False,
max_workers=10,
# print_name=False,
):
if base == "self":
base = self
# settings = {}
# settings_channels = {}
# settings_times = {}
status = {}
status_channels = {}
status_times = {}
nodet = []
geterror = []
# with ThreadPoolExecutor(max_workers=max_workers) as exc:
# list(
# progress.track(
# exc.map(
# lambda name: self.init_name(
# name, verbose=verbose, raise_errors=raise_errors
# ),
# self.all_names
# - self.initialized_names
# - set(exclude_names),
# ),
# description="Initializing ...",
# total=len(
# self.all_names - self.initialized_names - set(exclude_names)
# ),
# transient=True,
# )
# )
def get_stat_one_detector(ts):
tstart = time.time()
try:
if (not channeltypes) or (ts.alias.channeltype in channeltypes):
status[ts.alias.get_full_name(base=base)] = ts.get_current_value()
try:
status_channels[ts.alias.get_full_name(base=base)] = (
ts.alias.channel
)
except:
pass
except:
geterror.append(ts.alias.get_full_name(base=base))
status_times[ts.alias.get_full_name(base=base)] = time.time() - tstart
ts_t = []
for ts in track(
self.status_collection.get_list(),
transient=True,
description="Reading status indicators ...",
):
if isinstance(ts, Detector):
if threads:
ts_t.append(ts)
else:
tstart = time.time()
try:
if (not channeltypes) or (ts.alias.channeltype in channeltypes):
status[ts.alias.get_full_name(base=base)] = (
ts.get_current_value()
)
try:
status_channels[ts.alias.get_full_name(base=base)] = (
ts.alias.channel
)
except:
pass
except:
geterror.append(ts.alias.get_full_name(base=base))
status_times[ts.alias.get_full_name(base=base)] = (
time.time() - tstart
)
else:
nodet.append(ts.alias.get_full_name(base=base))
if threads:
with ThreadPoolExecutor(max_workers=max_workers) as exc:
list(
track(
exc.map(get_stat_one_detector, ts_t),
description="Getting status...",
total=len(ts_t),
)
)
if verbose:
if nodet:
print("Could not retrieve status from:\n " + ",\n ".join(nodet))
if geterror:
print(
"Retrieved error while running get_current_value from:\n "
+ ",\n ".join(geterror)
)
if print_times:
from ascii_graph import Pyasciigraph
gr = Pyasciigraph()
for line in gr.graph(
"Times required to get status",
sorted(status_times.items(), key=lambda w: w[1]),
):
print(line)
sel_dict = {}
for selection_name in selections:
sel = self.status_collection.get_names(selection=selection_name)
sel_dict[selection_name] = {
tname: status[tname] for tname in sel if tname in status.keys()
}
return {
# "settings": settings,
"status": status,
# "settings_channels": settings_channels,
"status_channels": status_channels,
# "settings_times": settings_times,
"status_times": status_times,
"selections": sel_dict,
}
def get_tree(self, level=1, print_tree=True):
"""Return nested status_collection member names as a dictionary."""
def build_tree(node, depth):
result = {}
for wref in node.status_collection._list:
item = wref()
if item is None or item is node:
continue
try:
name = item.alias.get_full_name(base=node)
except Exception:
name = getattr(item, 'name', str(item))
if hasattr(item, 'status_collection') and (depth > 1 or depth <= 0):
next_depth = depth - 1 if depth > 0 else depth
result[name] = build_tree(item, next_depth)
else:
result[name] = {}
return result
def format_tree(tree, prefix=''):
lines = []
items = list(tree.items())
for index, (name, subtree) in enumerate(items):
connector = '└── ' if index == len(items) - 1 else '├── '
lines.append(f"{prefix}{connector}{name}")
if subtree:
extension = ' ' if index == len(items) - 1 else ''
lines.extend(format_tree(subtree, prefix + extension))
return lines
tree = build_tree(self, level)
if print_tree:
if tree:
for line in format_tree(tree):
print(line)
else:
print('(empty)')
return tree
def status(self, get_string=False):
stat = self.get_status()
s = tabulate([[name, value] for name, value in stat["status"].items()])
if get_string:
return s
else:
print(s)
def settings(self, get_string=False):
stat = self.get_status()
s = tabulate(
[
[colorama.Style.BRIGHT + name + colorama.Style.RESET_ALL, value]
for name, value in stat["settings"].items()
]
)
if get_string:
return s
else:
print(s)
def get_status_str(self, base=None, stat_fields=["settings"]):
stat = self.get_status(base=base)
stat_filt = {}
for stat_field in stat_fields:
tstat = stat[stat_field]
for to in self.view_toplevel_only:
tname = to.alias.get_full_name(base=base)
tstat = filter_names(tname, tstat)
stat_filt[stat_field] = tstat
s = tabulate([[name, value] for name, value in stat_filt[stat_field].items()])
return s
def get_display_str(
self,
tablefmt="simple",
with_base_name=False,
maxcolwidths=[None, 50, None, None, None],
):
main_name = self.name
stats = self.status_collection.get_list(selection="display")
# stats_dict = {}
tab = []
for to in stats:
name = to.alias.get_full_name(base=self)
is_adjustable = isinstance(to, Adjustable)
is_detector = isinstance(to, Detector)
typechar = ""
if is_adjustable:
typechar += "✏️"
elif is_detector:
typechar += "👁️"
if hasattr(to, "status_collection"):
typechar += ""
try:
value = to.get_current_value()
except AttributeError:
if hasattr(to, "status_collection"):
value = "\x1b[3mhas lower level items\x1b[0m"
if isinstance(value, Enum):
value = f"{value.value} ({value.name})"
try:
unit = to.unit.get_current_value()
except:
unit = ""
try:
description = to.description.get_current_value()
except:
description = ""
if value is None:
value = ""
if with_base_name:
tab.append(
[".".join([main_name, name]), value, unit, typechar, description]
)
else:
tab.append([name, value, unit, typechar, description])
if tab:
s = tabulate(tab, tablefmt=tablefmt, maxcolwidths=maxcolwidths)
else:
s = ""
return s
def status_to_elog(
self,
text="",
elog=None,
files=None,
text_encoding="markdown",
auto_title=True,
attach_display=True,
attach_status_file=True,
):
if elog is None:
elog = self._get_elog()
message = ""
if auto_title:
message += markdown(f"#### Status {self.alias.get_full_name()}")
if text:
if text_encoding == "markdown":
message += markdown(text)
if attach_display:
message += self.get_display_str(tablefmt="html")
if files is None:
files = []
if attach_status_file:
stat = self.get_status()
tmppath = Path("/tmp")
filepath = tmppath / Path(
f"status_{self.alias.get_full_name}_{datetime.now().isoformat()}.json"
)
with open(filepath, "w") as f:
# json.dump(stat, f, cls=NumpyEncoder, indent=4)
json.dump(stat, f, indent=4, cls=NumpyEncoder)
files.append(filepath)
if len(files) > 1:
print(files)
return elog.post(
message,
*files,
text_encoding="html",
)
# tags=[],
def __repr__(self):
label = self.alias.get_full_name() + " display\n"
return label + self.get_display_str()
# def _wait_for_initialisation(self, timeout=2):
# for ton, to in self.__dict__.items():
# try:
# iswaitable = isinstance(to, InitialisationWaitable)
# if iswaitable:
# to._wait_for_initialisation()
# except:
# pass
def _wait_for_initialisation(self):
for item in self.status_collection.get_list():
if isinstance(item, Assembly) and (item in _initializing_assemblies):
continue
if isinstance(item, InitialisationWaitable):
if isinstance(item, Assembly):
_initializing_assemblies.append(item)
item._wait_for_initialisation()
def _run_cmd(self, line, silent=True):
if silent:
print(f"Starting following commandline silently:\n" + line)
with open(os.devnull, "w") as FNULL:
subprocess.Popen(
line, shell=True, stdout=FNULL, stderr=subprocess.STDOUT
)
else:
subprocess.Popen(line, shell=True)
def _get_elog(self):
if hasattr(self, "_elog") and self._elog:
return self._elog
elif hasattr(self, "__elog") and self.__elog:
return self.__elog
elif eco.defaults.ELOG:
return eco.defaults.ELOG
else:
return None
def widget(self):
from eco.widgets.display_widget import make_assembly_widget
return make_assembly_widget(self)
import epics.pv
import time
class Monitor:
def __init__(self, assembly):
self.assembly = assembly
self.data = {}
self.callbacks = {}
self.pvs = {}
def start_monitoring(self):
o = self.assembly.get_status(channeltypes=["CA"])
# self.data = {k: [v] for k, v in o["status"].items()}
self.channelkeys = {v: k for k, v in o["status_channels"].items()}
self.pvs = {k: epics.pv.PV(v) for k, v in o["status_channels"].items()}
# for cik, civ in epics.pv._PVcache_.items():
# if cik[0] in o["status_channels"].keys():
# tname = self.channelkeys[cik[0]]
# tpv = civ
for tname, tpv in self.pvs.items():
self.callbacks[tname] = tpv.add_callback(self.append)
def stop_monitoring(self):
for tname in self.pvs:
self.pvs[tname].remove_callback(index=self.callbacks[tname])
def append(self, pvname=None, value=None, timestamp=None, **kwargs):
if not (self.channelkeys[pvname] in self.data):
self.data[self.channelkeys[pvname]] = []
ts_local = time.time()
self.data[self.channelkeys[pvname]].append(
{"value": value, "timestamp": timestamp, "timestamp_local": ts_local}
)
def filter_names(name, stat_dict):
out = {}
for key, value in stat_dict.items():
keys = key.split(".")
if keys[0] == name:
if len(keys) == 1:
out[key] = value
else:
out[key] = value
return out
+173
View File
@@ -0,0 +1,173 @@
from copy import deepcopy
from threading import Thread
from eco.acquisition.decorators import scannable
from eco.elements.adjustable import (
AdjustableMemory,
default_representation,
spec_convenience,
)
from eco.elements.assembly import Assembly
from eco.aliases import Alias
import time
def value_property(Det, value_name="_value"):
setattr(
Det,
value_name,
property(
Det.get_current_value,
),
)
return Det
def call_convenience(Det, value=None):
# spec-inspired convenience methods
def wm(self, *args, **kwargs):
return self.get_current_value(*args, **kwargs)
Det.wm = wm
def call(self, value=value):
if value is None:
return self.wm()
else:
raise ValueError(f"{self.name} is just a readback, which cannot be set.")
Det.__call__ = call
return Det
@call_convenience
@value_property
@default_representation
class DetectorVirtual(Assembly):
def __init__(
self,
detectors,
foo_get_current_value,
append_aliases=False,
name=None,
unit=None,
):
super().__init__(name=name)
if append_aliases:
for det in detectors:
try:
self.alias.append(det.alias)
except Exception as e:
print(f"could not find alias in {det}")
print(str(e))
self._detectors = detectors
self._foo_get_current_value = foo_get_current_value
if unit:
self.unit = AdjustableMemory(unit, name="unit")
self.status_collection.append(self)
self.status_collection.append(self, selection="settings", recursive=False)
self.status_collection.append(self, selection="display", recursive=False)
def get_current_value(self):
return self._foo_get_current_value(
*[det.get_current_value() for det in self._detectors]
)
@call_convenience
@value_property
@default_representation
@scannable
class DetectorGet:
def __init__(
self, foo_get, cache_get_seconds=None, monitor_frequency=None, name=None
):
""" """
self.alias = Alias(name)
self.name = name
self._get = foo_get
self._cache_get_seconds = cache_get_seconds
self._accumulate_frequency = monitor_frequency
if monitor_frequency:
self.set_current_value_callback = self._set_current_value_callback
def get_current_value(self):
ts = time.time()
if self._cache_get_seconds and hasattr(self, "_get_cache"):
if ts - self._get_cache[0] < self._cache_get_seconds:
value = self._get_cache[1]
else:
value = self._get()
else:
value = self._get()
if self._cache_get_seconds:
self._get_cache = (ts, value)
return value
def _set_current_value_callback(self):
return CallbackTimedelta(
self, frequency=self._accumulate_frequency, func="accumulate"
)
@call_convenience
@value_property
class DetectorMemory:
def __init__(self, value=0, name="detector_memory", return_deep_copy=True):
self.name = name
self.alias = Alias(name)
self.current_value = value
self._return_deep_copy = return_deep_copy
def get_current_value(self):
if self._return_deep_copy:
return deepcopy(self.current_value)
else:
return self.current_value
def __repr__(self):
name = self.name
cv = self.get_current_value()
s = f"{name} at value: {cv}" + "\n"
return s
class CallbackTimedelta:
def __init__(
self, detector, frequency=10, func="accumulate", collector=None, run_once=True
):
self.detector = detector
self.frequency = frequency
# self.data = collector
if func == "accumulate":
func = self.accumulate_values
if collector is None:
collector = {"timestamps": [], "values": []}
self.data = (
collector # {"timestamps": [], "values": [], "timestamps_ioc": []}
)
self.foo = func
self.run_once = run_once
self.running = False
self.thread = None
def start(self, add_current_value=True):
if add_current_value:
ts_local = time.time()
self.data["timestamps"].append(ts_local)
self.data["values"].append(self.detector.get_current_value())
self.running = True
self.thread = Thread(target=self.accumulate_values)
self.thread.start()
def accumulate_values(self, *args, **kwargs):
while self.running:
ts_local = time.time()
self.data["timestamps"].append(ts_local)
self.data["values"].append(self.detector.get_current_value())
time.sleep(1 / self.frequency)
def stop(self):
self.running = False
self.thread.join()
+558
View File
@@ -0,0 +1,558 @@
import itertools
from pathlib import Path
from datetime import datetime
import weakref
from .adjustable import AdjustableFS
from ..utilities.keypress import KeyPress
from tabulate import tabulate
import sys, colorama
try:
from inspect import getargspec
except: # for python 3.12
from inspect import getfullargspec as getargspec
import eco
from ansi2html import Ansi2HTMLConverter
from simple_term_menu import TerminalMenu
conv = Ansi2HTMLConverter()
global_memory_dir = None
def set_global_memory_dir(dirpath, mode="w"):
globals()["global_memory_dir"] = Path(dirpath).expanduser()
def get_memory(name):
if not (global_memory_dir is None):
return Memory(name)
class Memory:
def __init__(
self,
obj,
memory_dir=global_memory_dir,
categories={"recall": ["settings"], "track": ["display"]},
):
self.obj_parent = weakref.ref(obj)
self.categories = categories
if not memory_dir:
memory_dir = global_memory_dir
self.base_dir = Path(memory_dir)
self.obj_parent().presets = Presets(self)
def setup_path(self):
name = self.obj_parent().alias.get_full_name(joiner=None)
self.dir = Path(self.base_dir) / Path("/".join(reversed(name)))
try:
self.dir.mkdir(exist_ok=True)
try:
self.dir.chmod(0o775)
except:
pass
except:
print("Could not create memory directory")
self._memories = AdjustableFS(
self.dir / Path("memories.json"), default_value={}
)
self._presets = AdjustableFS(self.dir / Path("presets.json"), default_value={})
def memories(self, indices=None, search_key=None):
self.setup_path()
mem = self._memories()
memkeys = list(mem.keys())
if indices is None:
indices = range(len(mem))
mems = []
for index in indices:
tkey = memkeys[index]
tmem = mem[tkey]
cats = list(itertools.chain.from_iterable(tmem["categories"].values()))
tmem_all = self.get_memory(key=tkey)
if search_key is not None:
tmem_sel = {
tk: {ttk: ttv for ttk, ttv in tv.items() if search_key in ttk}
for tk, tv in tmem_all.items()
if tk in cats
}
else:
tmem_sel = tmem_all
tmem.update(tmem_sel)
mems.append(tmem)
return mems
def plot_parameter(self, parameter_name, group_name="settings"):
mem = self.memories(search_key=parameter_name)
date = []
value = []
message = []
for tmem in mem:
try:
tdate = datetime.fromisoformat(tmem["date"])
tval = tmem[group_name][parameter_name]
tmess = tmem["message"]
date.append(tdate)
value.append(tval)
message.append(tmess)
except:
pass
return date, value, message
def __str__(self):
self.setup_path()
mem = self._memories()
a = []
for n, (key, content) in enumerate(mem.items()):
row = [n]
t = datetime.fromisoformat(key)
row.append(t.strftime("%Y-%m-%d: %a %-H:%M"))
row.append(content["message"])
a.append(row)
return tabulate(a, headers=["Index", "Time", "Message"])
def __call__(self, index=None, **kwargs):
# print(self.get_memory_difference_str(index))
if index is None:
self.setup_path()
mem = self._memories()
a = []
for n, (key, content) in enumerate(mem.items()):
row = ""
t = datetime.fromisoformat(key)
row += t.strftime("%Y-%m-%d: %a %H:%M")
row += " "
row += content["message"]
a.append(row)
ind_cancel = len(a)
a.append("--> do nothing")
menu = TerminalMenu(a, cursor_index=ind_cancel)
print("Select memory to recall")
index = menu.show()
if index == ind_cancel:
return
self.recall(memory_index=index, **kwargs)
def _get_elog(self):
if hasattr(self, "_elog") and self._elog:
return self._elog
elif hasattr(self, "__elog") and self.__elog:
return self.__elog
elif eco.defaults.ELOG:
return eco.defaults.ELOG
else:
return None
def memorize(
self,
message=None,
attributes={},
force_message=True,
preset_varname=None,
to_elog=True,
):
self.setup_path()
cats = list(itertools.chain.from_iterable(self.categories.values()))
stat_now = {}
allstat = self.obj_parent().get_status(base=self.obj_parent(), selections=cats)
stat_now["status"] = allstat["status"]
for trec in self.categories["recall"]:
stat_now[trec] = allstat["selections"][trec]
for trec in self.categories["track"]:
stat_now[trec] = allstat["selections"][trec]
stat_now["memorized_attributes"] = attributes
key = datetime.now().isoformat()
stat_now["date"] = key
mem = self._memories()
if force_message:
while not message:
message = input(
"Please enter a message associated to this memory entry:\n>>> "
)
mem[key] = {
"message": message,
"categories": self.categories,
"date": key,
}
if preset_varname:
mem[key].update({"presetname": preset_varname})
tmp = AdjustableFS(self.dir / Path(key + ".json"))
tmp(stat_now)
self._memories(mem)
print(f"Saved memory for {self.obj_parent().alias.get_full_name()}: {message}")
print(f"memory file: {tmp.file_path.as_posix()}")
if to_elog:
elog = self._get_elog()
elog.post(
f"Saved memory for {self.obj_parent().alias.get_full_name()}: {message}",
tmp.file_path,
text_encoding="markdown",
)
def get_memory(self, input_obj=None, index=None, key=None, filter_existing=True):
if not input_obj is None:
if type(input_obj) is dict:
mem_full = input_obj
else:
tmp = AdjustableFS(Path(input_obj))
mem_full = tmp()
else:
self.setup_path()
if not (index is None):
key = list(self._memories().keys())[index]
tmp = AdjustableFS(self.dir / Path(key + ".json"))
mem_full = tmp()
if filter_existing:
mem_filt = {}
for tkey, tval in mem_full.items():
if tkey in ["settings", "status_indicators"]:
mem_filt[tkey] = {}
for ttkey, ttval in tval.items():
try:
name2obj(self.obj_parent(), ttkey)
mem_filt[tkey][ttkey] = ttval
except KeyError:
...
else:
mem_filt[tkey] = tval
return mem_filt
else:
return mem_full
def clear_memory(self, index=None, key=None):
if not (index is None):
key = list(self._memories().keys())[index]
if key is None:
raise Exception("memory key or index to be deleted needs to be specified!")
mem = self._memories.get_current_value()
mem.pop(key)
self._memories.set_target_value(mem).wait()
def recall(
self,
memory_index=None,
input_obj=None,
key=None,
wait=True,
show_changes_only=True,
set_changes_only=True,
check_limits=True,
change_serially=False,
force=False,
):
"""Recall a memory_index, from an index in the default meory list, from a
dictionary containing the memory information, or from a path to a file containing the memory.
Args:
memory_index (integer, optional): index in memory list. Defaults to None.
input_obj (dictionary or string, optional): direct passing memory as dict or s filepath (string) to the memory file. Defaults to None.
key (string, optional): key of memory in memory list (if not defined by the index). Defaults to None.
wait (bool, optional): Wait for the memory recall changes to complete. Defaults to True.
show_changes_only (bool, optional): in rpreview show only changes that are different to present setting. Defaults to True.
set_changes_only (bool, optional): setting only the changes that changed. Defaults to True.
check_limits (bool, optional): check limits before changing. Defaults to True.
change_serially (bool, optional): change and wait each change after each other, not simultaneously. Defaults to False.
force (bool, optional): force the change without previous preview. Defaults to False.
Returns:
_type_: _description_
"""
# if input_obj:
mem = self.get_memory(
index=memory_index,
key=key,
input_obj=input_obj,
)
rec = {}
for trec in self.categories["recall"]:
rec.update(mem[trec])
if force:
select = [True] * len(rec.items())
else:
select = self.select_from_memory(
rec,
show_changes_only=show_changes_only,
)
if not select:
return
if not input("would you really like to do the change? (y/n):") == "y":
return
changes = []
for sel, (key, val) in zip(select, rec.items()):
if sel:
to = name2obj(self.obj_parent(), key)
if set_changes_only:
if to.get_current_value() == val:
continue
print(f"Changing {key} from {to.get_current_value()} to {val}")
if "check" in getargspec(to.set_target_value).args:
changes.append(to.set_target_value(val, check=check_limits))
else:
changes.append(to.set_target_value(val))
if change_serially:
changes[-1].wait()
if wait:
for change in changes:
change.wait()
return
else:
return changes
def recall_from_runtable(self): ...
def get_memory_difference_str(
self,
recall_dict,
select=None,
ask_select=True,
show_changes_only=False,
tablefmt="plain",
):
if not select:
select = [True] * len(recall_dict)
table = []
for n, (tsel, (key, recall_value)) in enumerate(
zip(select, recall_dict.items())
):
present_value = name2obj(self.obj_parent(), key).get_current_value()
if tsel:
tselstr = "x"
else:
tselstr = " "
if present_value == recall_value:
changed = False
if tablefmt == "html":
comp_indicator = "=="
else:
comp_indicator = (
colorama.Fore.GREEN
+ colorama.Style.BRIGHT
+ "=="
+ colorama.Style.RESET_ALL
)
else:
changed = True
if not tsel:
try:
comp_indicator = (
f"not changed ({recall_value-present_value:+g})"
)
except:
comp_indicator = f"not changed"
else:
try:
tdiff = f"{recall_value - present_value:+g}"
except TypeError:
tdiff = "special"
if tablefmt == "html":
comp_indicator = f"{tdiff:s}"
else:
comp_indicator = (
colorama.Fore.RED
+ colorama.Style.BRIGHT
+ f"{tdiff:s}"
+ colorama.Style.RESET_ALL
)
if show_changes_only and (not changed):
continue
table.append([n, tselstr, key, present_value, comp_indicator, recall_value])
if len(table) == 0:
return "No changes compared to memory!"
return tabulate(
table,
headers=[
"",
"",
"name",
"present",
"difference",
"memory",
],
colalign=("decimal", "center", "left", "decimal", "center", "decimal"),
tablefmt=tablefmt,
)
def select_from_memory(self, recall_dict, show_changes_only=True):
# mem = self.get_memory(input_obj=input_obj, key=key, index=memory_index)
k = KeyPress()
# cll = colorama.ansi.clear_line()
help = "Change selection pressing keys followed by numbered seelection \n"
help += " o : Select only (enter comma-separated row numbers)\n"
help += " a : Select additionally (enter comma-separated row numbers)\n"
help += " e : Exclude from selection (enter comma-separated row numbers)\n"
help += " r : recall selected memory\n"
help += " q : quit\n"
class Printer:
def __init__(self, o=self):
self.o = o
self.len = len(recall_dict)
self.select = [True] * self.len
def print(self, **kwargs):
print(
self.o.get_memory_difference_str(
recall_dict,
select=self.select,
show_changes_only=show_changes_only,
)
)
print(help)
def select_only(self):
v = self.get_array()
self.select = [False] * self.len
for tv in v:
self.select[tv] = True
def select_additional(self):
v = self.get_array()
for tv in v:
self.select[tv] = True
def exclude(self):
v = self.get_array()
for tv in v:
self.select[tv] = False
def get_array(self):
sys.stdout.flush()
v = sys.stdin.readline()
try:
v = v.split(",")
v = [int(tv) for tv in v]
print(v)
return v
except:
print(
"value cannot be converted to listed integers, please try again!"
)
sys.stdout.flush()
return self.get_array()
p = Printer()
while k.isq() is False:
p.print()
k.waitkey()
if k.iskey("o"):
print("Select only: ")
p.select_only()
elif k.iskey("a"):
print("Append to selection: ")
p.select_additional()
elif k.iskey("e"):
print("Exclude from selection: ")
p.exclude()
elif k.isq():
return
elif k.iskey("r"):
return p.select
else:
# print(help)
pass
# stat_now = self.obj_parent.get_status()
# for mem
def __repr__(self):
return self.__str__()
class Presets:
def __init__(
self,
memory,
):
self._memory = memory
self._setup_presets()
def __dir__(self):
return self._setup_presets()
def __getattr__(self, name):
self._setup_presets()
if not name in self.__dict__.keys():
raise AttributeError
return self.__dict__[name]
def _setup_presets(self):
self._memory.setup_path()
mem = self._memory._memories()
presets = []
for key, dat in mem.items():
if "presetname" in dat.keys():
self.__dict__[dat["presetname"]] = Preset(
self._memory, key, name=dat["presetname"]
)
presets.append(dat["presetname"])
return presets
def __str__(self):
self._memory.setup_path()
mem = self._memory._memories()
table = []
for key, dat in mem.items():
if "presetname" in dat.keys():
table.append([dat["presetname"], key, dat["message"]])
return tabulate(
table,
headers=[
"Preset",
"Date",
"Message",
],
colalign=("left", "left", "left"),
)
def __repr__(self):
return self.__str__()
class Preset:
def __init__(self, memory, key, name=None):
self._memory = memory
self._key = key
self._name = name
def get_memory(self):
return self._memory.get_memory(key=self._key)
def __call__(self, force=True):
self._memory.recall(key=self._key, force=force)
def __str__(self):
s = f"Preset {self._name} - saved values compared to the present status\n"
tmem = self._memory.get_memory(key=self._key)
s += self._memory.get_memory_difference_str(tmem)
return s
def __repr__(self):
return self.__str__()
def name2obj(obj_parent, name, delimiter="."):
if type(name) is str:
name = name.split(delimiter)
obj = obj_parent
for tn in name:
if not tn or tn == "self":
obj = obj
else:
obj = obj.__dict__[tn]
return obj
+66
View File
@@ -0,0 +1,66 @@
from typing import Protocol, runtime_checkable
@runtime_checkable
class Adjustable(Protocol):
def get_current_value(self):
...
def set_target_value(self, value):
...
# def set_target_value(self,value) -> Changer:...
@runtime_checkable
class Detector(Protocol):
def get_current_value(self):
...
@runtime_checkable
class MonitorableValueUpdate(Protocol):
def set_current_value_callback(self):
...
@runtime_checkable
class InitialisationWaitable(Protocol):
def _wait_for_initialisation(self):
...
@runtime_checkable
class Counter(Protocol):
def acquire(self):
...
def start(self):
...
def stop(self):
...
def __enter__(self):
self.start()
def __exit__(self, type, value, traceback):
self.stop()
# file_name=fina, Npulses=self.pulses_per_step[0], acq_pars=acq_pars):
# class Callback:
# self.__init__(self, func=None, *args, **kwargs):
# self.func = func
# self.args = args
# self.kwargs = kwargs
# def start(self,func=None):
# if func is not None:
# self.func = func
# def
@@ -0,0 +1,9 @@
MIT License
Copyright (c) 2023 [Your Name]
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
2. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,64 @@
# textual-status-editor
This project implements a textual user interface for displaying and managing the status of various items in an assembly. It provides a tabular view of current values and allows users to set target values for these items.
## Features
- Display current values of status items in a tabular format.
- Entry fields for setting target values of status items.
- Interactive user interface built with the Textual library.
## Project Structure
```
textual-status-editor
├── src
│ └── textual_status_editor
│ ├── __init__.py
│ ├── app.py
│ ├── main.py
│ ├── config.py
│ ├── adapters
│ │ └── assembly_adapter.py
│ ├── models
│ │ └── status_item.py
│ └── widgets
│ ├── __init__.py
│ └── status_table.py
├── tests
│ ├── test_status_table.py
│ └── test_assembly_adapter.py
├── pyproject.toml
├── requirements.txt
├── README.md
├── .gitignore
└── LICENSE
```
## Installation
1. Clone the repository:
```
git clone https://github.com/yourusername/textual-status-editor.git
cd textual-status-editor
```
2. Install the required dependencies:
```
pip install -r requirements.txt
```
## Usage
To run the application, execute the following command:
```
python -m textual_status_editor.main
```
## Contributing
Contributions are welcome! Please feel free to submit a pull request or open an issue for any suggestions or improvements.
## License
This project is licensed under the MIT License. See the LICENSE file for more details.
@@ -0,0 +1,23 @@
[tool.poetry]
name = "textual-status-editor"
version = "0.1.0"
description = "A textual tabular widget to display and edit status values."
authors = ["Your Name <youremail@example.com>"]
license = "MIT"
readme = "README.md"
homepage = "https://github.com/yourusername/textual-status-editor"
repository = "https://github.com/yourusername/textual-status-editor"
keywords = ["textual", "status", "editor", "widget"]
[tool.poetry.dependencies]
python = "^3.8"
textual = "^0.1.0" # Replace with the actual version you want to use
rich = "^10.0.0" # For rich text formatting in the terminal
[tool.poetry.dev-dependencies]
pytest = "^6.0"
pytest-cov = "^2.10"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,5 @@
textual
rich
pytest
pytest-asyncio
textual-widget
@@ -0,0 +1 @@
# This file is intentionally left blank.
@@ -0,0 +1,61 @@
from eco.elements.protocols import Assembly
from textual.app import App
from textual.widgets import Table, Input
from textual.reactive import Reactive
from textual.containers import Container
class StatusItem:
def __init__(self, name, current_value):
self.name = name
self.current_value = current_value
def set_target_value(self, value):
# Placeholder for setting the target value
self.current_value = value
class AssemblyAdapter:
def __init__(self, assembly: Assembly):
self.assembly = assembly
def get_status_items(self):
status_items = []
for item in self.assembly.status_collection.get_list():
current_value = item.get_current_value() if hasattr(item, 'get_current_value') else None
status_items.append(StatusItem(item.alias.get_full_name(), current_value))
return status_items
class StatusTable(App):
def __init__(self, assembly_adapter: AssemblyAdapter):
super().__init__()
self.assembly_adapter = assembly_adapter
self.status_items = self.assembly_adapter.get_status_items()
async def on_mount(self):
self.table = Table()
self.table.add_column("Name")
self.table.add_column("Current Value")
self.table.add_column("Set Target Value")
for item in self.status_items:
input_field = Input(placeholder="Enter value")
input_field.on_submit(lambda value, item=item: self.set_target_value(item, value))
self.table.add_row(item.name, str(item.current_value), input_field)
await self.view.dock(self.table)
def set_target_value(self, item: StatusItem, value: str):
item.set_target_value(value)
self.refresh_table()
def refresh_table(self):
self.table.clear()
for item in self.status_items:
input_field = Input(placeholder="Enter value")
input_field.on_submit(lambda value, item=item: self.set_target_value(item, value))
self.table.add_row(item.name, str(item.current_value), input_field)
if __name__ == "__main__":
assembly = Assembly() # Replace with actual assembly initialization
adapter = AssemblyAdapter(assembly)
app = StatusTable(adapter)
app.run()
@@ -0,0 +1,45 @@
from textual.app import App
from textual.widgets import Static, Input, Table
from textual.reactive import Reactive
from textual import events
from .adapters.assembly_adapter import AssemblyAdapter
class StatusItem:
def __init__(self, name, current_value):
self.name = name
self.current_value = current_value
def set_target_value(self, value):
# Logic to set the target value
pass
class StatusTable(Table):
def __init__(self, items):
super().__init__()
self.items = items
self.add_column("Name", min_width=20)
self.add_column("Current Value", min_width=20)
self.add_column("Set Target Value", min_width=20)
for item in self.items:
self.add_row(item.name, str(item.current_value), Input(placeholder="Set value"))
async def on_input_changed(self, event: events.InputChanged):
# Logic to handle input changes
row_index = self.get_row_index(event.sender)
if row_index is not None:
item = self.items[row_index]
item.set_target_value(event.sender.value)
class StatusEditorApp(App):
def __init__(self):
super().__init__()
self.adapter = AssemblyAdapter()
self.status_items = self.adapter.get_status_items()
async def on_mount(self):
self.table = StatusTable(self.status_items)
await self.view.dock(self.table)
if __name__ == "__main__":
StatusEditorApp.run()
@@ -0,0 +1,10 @@
# Configuration settings for the textual status editor application
# Layout parameters
TABLE_WIDTH = 80
TABLE_HEIGHT = 20
ENTRY_WIDTH = 10
# Constants
APP_TITLE = "Textual Status Editor"
STATUS_COLLECTION_NAME = "status_collection"
@@ -0,0 +1,52 @@
from textual.app import App
from textual.widgets import Static, Input, Table
from textual.reactive import Reactive
from textual import events
from .adapters.assembly_adapter import AssemblyAdapter
class StatusItem:
def __init__(self, name, current_value):
self.name = name
self.current_value = current_value
def set_target_value(self, value):
# Logic to set the target value
self.current_value = value
class StatusTable(Table):
def __init__(self, items):
super().__init__()
self.items = items
self.add_column("Name", min_width=20)
self.add_column("Current Value", min_width=20)
self.add_column("Set Target Value", min_width=20)
for item in self.items:
self.add_row(item.name, str(item.current_value), "")
async def on_input_changed(self, event: events.InputChanged):
row_index = event.row_index
column_index = event.column_index
if column_index == 2: # Assuming the third column is for setting target values
target_value = event.value
self.items[row_index].set_target_value(target_value)
self.update_row(row_index)
def update_row(self, row_index):
item = self.items[row_index]
self.update_row(row_index, item.name, str(item.current_value), "")
class StatusEditorApp(App):
def __init__(self):
super().__init__()
self.adapter = AssemblyAdapter()
self.status_items = self.adapter.get_status_items()
self.table = StatusTable(self.status_items)
async def on_mount(self):
await self.view.dock(self.table)
if __name__ == "__main__":
app = StatusEditorApp()
app.run()
@@ -0,0 +1,60 @@
from textual import events
from textual.widget import Widget
from textual.reactive import Reactive
from textual.containers import Container
from textual.widgets import Input, Table
class StatusItem:
def __init__(self, name, current_value):
self.name = name
self.current_value = current_value
def set_target_value(self, value):
# Logic to set the target value
self.current_value = value
class StatusItemWidget(Widget):
name: str
current_value: Reactive[str] = Reactive("")
def __init__(self, status_item: StatusItem):
super().__init__()
self.status_item = status_item
self.name = status_item.name
self.current_value = str(status_item.current_value)
def render(self):
return f"{self.name}: {self.current_value}"
async def on_input_changed(self, event: events.InputChanged):
if event.input.value:
self.status_item.set_target_value(event.input.value)
self.current_value = event.input.value
await self.refresh()
class StatusItemTable(Widget):
def __init__(self, status_items):
super().__init__()
self.status_items = status_items
self.table = Table()
def render(self):
self.table.clear()
self.table.add_column("Item Name")
self.table.add_column("Current Value")
self.table.add_column("Set Value")
for item in self.status_items:
row = [item.name, str(item.current_value), Input(placeholder="Set value")]
self.table.add_row(*row)
return Container(self.table)
async def on_input_changed(self, event: events.InputChanged):
for item in self.status_items:
if event.input.value:
item.set_target_value(event.input.value)
await self.refresh()
@@ -0,0 +1 @@
# This file is intentionally left blank.
@@ -0,0 +1,50 @@
from textual.app import App
from textual.widgets import Table, Input
from textual.reactive import Reactive
from textual.containers import Container
from eco.elements.protocols import Detector # Assuming Detector is imported from the correct module
from .assembly_adapter import AssemblyAdapter # Import the AssemblyAdapter
class StatusItem:
def __init__(self, name, current_value):
self.name = name
self.current_value = current_value
def set_target_value(self, value):
# Logic to set the target value
pass
class StatusTable(App):
status_items: Reactive[list[StatusItem]] = Reactive([])
def __init__(self, assembly_adapter: AssemblyAdapter):
super().__init__()
self.assembly_adapter = assembly_adapter
async def on_mount(self):
self.status_items = await self.assembly_adapter.get_status_items()
self.render_table()
def render_table(self):
table = Table(title="Status Table")
table.add_column("Name", justify="left")
table.add_column("Current Value", justify="right")
table.add_column("Set Target Value", justify="right")
for item in self.status_items:
input_field = Input(placeholder="Enter value", on_submit=self.set_value(item))
table.add_row(item.name, str(item.current_value), input_field)
self.set_widget(table)
async def set_value(self, item: StatusItem, value: str):
item.set_target_value(value)
await self.assembly_adapter.update_status_item(item)
def set_widget(self, widget):
container = Container(widget)
self.set_root(container)
if __name__ == "__main__":
assembly_adapter = AssemblyAdapter() # Initialize your adapter here
StatusTable(assembly_adapter).run()
@@ -0,0 +1,56 @@
import pytest
from textual_status_editor.adapters.assembly_adapter import AssemblyAdapter
from textual_status_editor.models.status_item import StatusItem
@pytest.fixture
def assembly_adapter():
return AssemblyAdapter()
def test_get_current_values(assembly_adapter):
# Mock the status collection to return predefined values
assembly_adapter.status_collection = [
StatusItem(name="Item1", current_value=10),
StatusItem(name="Item2", current_value=20),
]
current_values = assembly_adapter.get_current_values()
assert current_values == {
"Item1": 10,
"Item2": 20,
}
def test_set_target_value(assembly_adapter):
# Mock the status item
item = StatusItem(name="Item1", current_value=10)
assembly_adapter.status_collection = [item]
assembly_adapter.set_target_value("Item1", 15)
assert item.target_value == 15
def test_set_target_value_nonexistent_item(assembly_adapter):
# Mock the status collection
assembly_adapter.status_collection = [
StatusItem(name="Item1", current_value=10),
]
result = assembly_adapter.set_target_value("NonexistentItem", 15)
assert result is False # Expecting failure when item does not exist
def test_update_status_item(assembly_adapter):
item = StatusItem(name="Item1", current_value=10)
assembly_adapter.status_collection = [item]
assembly_adapter.update_status_item("Item1", 20)
assert item.current_value == 20
def test_update_status_item_nonexistent(assembly_adapter):
item = StatusItem(name="Item1", current_value=10)
assembly_adapter.status_collection = [item]
result = assembly_adapter.update_status_item("NonexistentItem", 20)
assert result is False # Expecting failure when item does not exist
@@ -0,0 +1,36 @@
import pytest
from textual_status_editor.widgets.status_table import StatusTable
from textual_status_editor.models.status_item import StatusItem
@pytest.fixture
def status_items():
return [
StatusItem(name="Item 1", current_value=10),
StatusItem(name="Item 2", current_value=20),
StatusItem(name="Item 3", current_value=30),
]
def test_status_table_display(status_items):
table = StatusTable(status_items)
rendered = table.render()
assert "Item 1" in rendered
assert "10" in rendered
assert "Item 2" in rendered
assert "20" in rendered
assert "Item 3" in rendered
assert "30" in rendered
def test_status_table_set_target_value(status_items):
table = StatusTable(status_items)
table.set_target_value("Item 1", 15)
assert status_items[0].current_value == 15
def test_status_table_invalid_target_value(status_items):
table = StatusTable(status_items)
table.set_target_value("Item 4", 25) # Non-existent item
assert status_items[0].current_value == 10 # Should remain unchanged
assert status_items[1].current_value == 20
assert status_items[2].current_value == 30
Binary file not shown.
+16 -22
View File
@@ -65,31 +65,25 @@ class table:
self.status = PV(Id + ":SS_STATUS")
def __str__(self):
return (
"Prime Table position\nx: %s mm\ny: %s mm\nz: %s\npitch: %s mrad\nyaw: %s mrad\nmode SP: %s \nstatus: %s"
% (
self.x.wm(),
self.y.wm(),
self.z.wm(),
self.pitch.wm(),
self.yaw.wm(),
self.modeSP.get(as_string=True),
self.status.get(),
)
return "Prime Table position\nx: %s mm\ny: %s mm\nz: %s\npitch: %s mrad\nyaw: %s mrad\nmode SP: %s \nstatus: %s" % (
self.x.wm(),
self.y.wm(),
self.z.wm(),
self.pitch.wm(),
self.yaw.wm(),
self.modeSP.get(as_string=True),
self.status.get(),
)
def __repr__(self):
return (
"{'x': %s, 'y': %s,'z': %s,'pitch': %s, 'yaw': %s, 'mode set point': %s,'status': %s}"
% (
self.x,
self.y,
self.z,
self.pitch,
self.yaw,
self.modeSP.get(as_string=True),
self.status.get(),
)
return "{'x': %s, 'y': %s,'z': %s,'pitch': %s, 'yaw': %s, 'mode set point': %s,'status': %s}" % (
self.x,
self.y,
self.z,
self.pitch,
self.yaw,
self.modeSP.get(as_string=True),
self.status.get(),
)
+108
View File
@@ -0,0 +1,108 @@
from ..devices_general.motors import MotorRecord
from ..devices_general.detectors import CameraCA, CameraBS
from ..epics.adjustable import AdjustablePv
from ..aliases import Alias, append_object_to_object
# from ..devices_general.epics_wrappers import EnumSelector
def addMotorRecordToSelf(self, Id=None, name=None):
self.__dict__[name] = MotorRecord(Id, name=name)
self.alias.append(self.__dict__[name].alias)
def addPvRecordToSelf(
self, name=None, pvsetname=None, pvreadbackname=None, accuracy=None
):
self.__dict__[name] = AdjustablePv(
name=name, pvsetname=pvsetname, pvreadbackname=pvreadbackname, accuracy=accuracy
)
self.alias.append(self.__dict__[name].alias)
class Sigma:
def __init__(
self,
camera_pv=None,
zoomstage_pvs={},
bshost=None,
bsport=None,
name=None,
):
self.alias = Alias(name)
# zoomstage_pvs={
# "set_value": "SARES20-OPSI:MOT_SP",
# "readback": "SEARES20-OPSI:MOT_RB",
# }
self.name = name
append_object_to_object
if zoomstage_pvs:
append_object_to_object(
self,
AdjustablePv,
name="zoom",
pvsetname=zoomstage_pvs["set_value"],
pvreadbackname=zoomstage_pvs["readback"],
)
try:
self.cam = CameraCA(camera_pv)
except:
print("Sigma Cam not found")
pass
if bshost:
self.camBS = CameraBS(host=bshost, port=bsport)
def get_adjustable_positions_str(self):
ostr = "*****Qioptic motor positions******\n"
for tkey, item in self.__dict__.items():
if hasattr(item, "get_current_value"):
pos = item.get_current_value()
ostr += " " + tkey.ljust(17) + " : % 14g\n" % pos
return ostr
def __repr__(self):
return self.get_adjustable_positions_str()
class Qioptiq:
def __init__(
self, camera_pv=None, zoomstage_pv=None, bshost=None, bsport=None, name=None
):
self.alias = Alias(name)
self.name = name
if zoomstage_pv:
append_object_to_object(self, MotorRecord, zoomstage_pv, name="zoom")
try:
addMotorRecordToSelf(self, Id="SARES20-EXP:MOT_QIOPT_F", name="focus")
except:
print("Qioptic focus motor not found")
pass
try:
self.cam = CameraCA(camera_pv)
except:
print("Qioptic Cam not found")
pass
if bshost:
self.camBS = CameraBS(host=bshost, port=bsport)
def get_adjustable_positions_str(self):
ostr = "*****Qioptic motor positions******\n"
for tkey, item in self.__dict__.items():
if hasattr(item, "get_current_value"):
pos = item.get_current_value()
ostr += " " + tkey.ljust(17) + " : % 14g\n" % pos
return ostr
def __repr__(self):
return self.get_adjustable_positions_str()
File diff suppressed because it is too large Load Diff
-64
View File
@@ -1,64 +0,0 @@
import sys
sys.path.append("..")
from ..devices_general.motors import MotorRecord
from epics import PV
class GPS:
def __init__(self, Id, alias_namespace=None):
self.Id = Id
### motors heavy load gps table ###
self.xhl = MotorRecord(Id + ":MOT_TBL_TX")
self.zhl = MotorRecord(Id + ":MOT_TBL_TZ")
self.yhl = MotorRecord(Id + ":MOT_TBL_TY")
self.th = MotorRecord(Id + ":MOT_MY_RYTH")
try:
self.rxhl = MotorRecord(Id + ":MOT_TBL_RX")
except:
print("GPS.pitch not found")
pass
try:
self.ryhl = MotorRecord(Id + ":MOT_TBL_RY")
except:
print("GPS.roll not found")
pass
### motors heavy load gonio base ###
self.xmu = MotorRecord(Id + ":MOT_HEX_TX")
self.mu = MotorRecord(Id + ":MOT_HEX_RX")
self.tth = MotorRecord(Id + ":MOT_NY_RY2TH")
self.xbase = MotorRecord(Id + ":MOT_TX")
self.ybase = MotorRecord(Id + ":MOT_TY")
self.hex_x = PV("SARES20-HEX_PI:POSI-X")
self.hex_y = PV("SARES20-HEX_PI:POSI-Y")
self.hex_z = PV("SARES20-HEX_PI:POSI-Z")
self.hex_u = PV("SARES20-HEX_PI:POSI-U")
self.hex_v = PV("SARES20-HEX_PI:POSI-V")
self.hex_w = PV("SARES20-HEX_PI:POSI-W")
def __repr__(self):
s = "**Heavy Load**\n"
motors = "xmu mu tth xbase ybase".split()
for motor in motors:
s += " - %s %.4f\n" % (motor, getattr(self, motor).wm())
s += " - HLX %.4f\n" % (self.xhl.wm())
s += " - HLY %.4f\n" % (self.yhl.wm())
s += " - HLZ %.4f\n" % (self.zhl.wm())
s += " - HLTheta %.4f\n" % (self.th.wm())
s += "\n"
s += "**Gonio**\n"
motors = "xmu mu tth xbase ybase".split()
for motor in motors:
s += " - %s %.4f\n" % (motor, getattr(self, motor).wm())
s += "\n"
s += "**Hexapod**\n"
motors = "x y z u v w".split()
for motor in motors:
s += " - hex_%s %.4f\n" % (motor, getattr(self, "hex_" + motor).get())
return s

Some files were not shown because too many files have changed in this diff Show More