From 0010b95554af24a796437338aaeb3e0f17e1a093 Mon Sep 17 00:00:00 2001 From: Sam Rowe Date: Fri, 26 Jun 2026 11:27:01 +0100 Subject: [PATCH 1/2] Change souk_readout_tools recommended branch from dev/1.3.0 to dev/1.5.0 Updated README to reflect the new branch version. --- socs/agents/ukkid_controller/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/socs/agents/ukkid_controller/README.md b/socs/agents/ukkid_controller/README.md index 3bca88ecd..81babeb09 100644 --- a/socs/agents/ukkid_controller/README.md +++ b/socs/agents/ukkid_controller/README.md @@ -53,7 +53,7 @@ on the RFSoC's ARM cores. 2. souk_readout_tools. This is the python code for the readout client described above (note this repository also contains the code for the -readout server software that runs on the RFSoCs). **Use branch dev/1.3.0** +readout server software that runs on the RFSoCs). **Use branch dev/1.5.0** for compatibility with the ukkid_controller agent. 3. The ukkid_controller agent itself - i.e. everything within the @@ -107,7 +107,7 @@ One should follow the "quick-start" installation instructions here... https://github.com/sr-cdf/souk_readout_tools -**Be sure to select branch dev/1.3.0 from the pull down menu on github before cloning this repository.** +**Be sure to select branch dev/1.5.0 from the pull down menu on github before cloning this repository.** ### ukkid_controller agent @@ -546,4 +546,4 @@ config_file_dict = {'ufm_kid1':'my_config_bun_p0.yaml','ufm_kid2':'my_config_bun Are correctly set up with the ip address of the RFSoc etc. You should hopefully have already gone through this process when installing and verifying your souk_readout_tools installation before you set up the -OCS system and the ukkid_controller agent. \ No newline at end of file +OCS system and the ukkid_controller agent. From 276560467c1795f220ff41aea9074ce5d3049cf3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 10:29:45 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- socs/agents/ukkid_controller/README.md | 22 +- .../ukkid_controller/ukkid_controller.py | 1395 ++++++++--------- 2 files changed, 702 insertions(+), 715 deletions(-) diff --git a/socs/agents/ukkid_controller/README.md b/socs/agents/ukkid_controller/README.md index 81babeb09..6e4fb52c0 100644 --- a/socs/agents/ukkid_controller/README.md +++ b/socs/agents/ukkid_controller/README.md @@ -49,7 +49,7 @@ on the RFSoC's ARM cores. On a standard Ubuntu linux installation (at the time of writing Ubuntu 22.04 LTS) the following prerequisites are required. -1. An Observatory Control System (OCS) installation. +1. An Observatory Control System (OCS) installation. 2. souk_readout_tools. This is the python code for the readout client described above (note this repository also contains the code for the @@ -282,7 +282,7 @@ services: - INSTANCE_ID=influxagent volumes: - ${OCS_CONFIG_DIR}:/config:ro - # New barebones agent + # New barebones agent ocs-barebones-agent: image: ocs-barebones-agent hostname: aslxdaq35-docker @@ -311,8 +311,8 @@ Go to the Simons Observatory socs repository here... https://github.com/simonsobs/socs -Select the branch "ukkid_agent" from the pull down branch menu. -Clone this branch onto the same machine as you have souk_readout_tools and your ocs system installed. The agent can be +Select the branch "ukkid_agent" from the pull down branch menu. +Clone this branch onto the same machine as you have souk_readout_tools and your ocs system installed. The agent can be found via ``` @@ -324,7 +324,7 @@ This will show the python code ukkid_controller.py, this README.md and various s ## Making the UKKID_controller.py agent available to your OCS install -The way I am doing this (there may be a more elegant way) is to create a soflink from the default "agents" directory in your default OCS installed to point at the +The way I am doing this (there may be a more elegant way) is to create a soflink from the default "agents" directory in your default OCS installed to point at the ukkid_controller directory within the SOCS directory tree. On my system I had installed ocs in /home/leechj/ocs and socs in /home/leechj/socs, so I did. ``` @@ -361,7 +361,7 @@ Change directory to where you have installed the ukkid_controller agent above. E cd /home/leechj/socs/socs/agents/ukkid_controller ``` -Open in a text editor and change the following variables to the appropriate locations on your computer. You should only need to edit the variables +Open in a text editor and change the following variables to the appropriate locations on your computer. You should only need to edit the variables souk_readout_tools_install_dir and config_file_dict. The latter should be set up to match that set up in the section "Configuration of souk_readout_tools" above. ``` @@ -379,7 +379,7 @@ stream_script_path = os.path.join(souk_readout_tools_install_dir,'src/souk_read # Output directory where you want your data files to get written to data_out_dir = os.path.join(souk_readout_tools_install_dir,'src/souk_readout_tools/client/client_scripts/tmp/') # Directory where the config files specified in the config_file_dict dictionary below are located. -config_file_root_dir = souk_readout_tools_install_dir +config_file_root_dir = souk_readout_tools_install_dir # This next variable tells the agent which config file to push to the server depending on which kid_stream_id the agent was started with on the command line. # This will eventually have 28 entries for the 28 individual reduction pipelines. # For single pipline running, with one ukkid_controller.py agent running at a time, we only need the first entry, and it should the filename of @@ -388,7 +388,7 @@ config_file_dict = {'ufm_kid1':'my_config_bun_p0.yaml','ufm_kid2':'my_config_bun ################## ``` -If you set the mock variable to be True, the agent will run in simulated mode and you do not need a connected hardware RFSoC. Set to False if you want to run +If you set the mock variable to be True, the agent will run in simulated mode and you do not need a connected hardware RFSoC. Set to False if you want to run real hardware. In that case the IP address etc. should be set up in souk_readout_tools config.yaml file specfied in config_file_dict. ## Running the UKKID controller agent without additional hardware (mocked mode) @@ -434,7 +434,7 @@ Note that "process_name": "mock_readout_server" in the above dictionary confirms ### 4) Activate virtual environment for souk_readout_tools, open a python interpreter, create a client object. ``` -cd souk_readout_tools +cd souk_readout_tools source client_venv/bin/activate python3 ``` @@ -474,7 +474,7 @@ When the "check_state" process is running, this should just repeatedly display t 2026-06-25T15:28:12+0100 [observatory.UKKIDController1.feeds.UKKID_feed] [{'status_string': {'block_name': 'status_string', 'data': {'value': ['[{"ready": true, "process_name": "mock_readout_server", "ip_addresses": "10.179.136.4", "pipeline_id": 0, "pipeline_dirs": {}, "pwd": "/home/leechj/socs/socs/agents/ukkid_controller", "sys_executable": "/usr/bin/python3", "sys_argv": ["/home/leechj/.local/bin/ocs-agent-cli", "--agent", "ukkid_controller.py", "--entrypoint", "main", "--instance-id", "UKKIDController1"], "uname": "aslxdaq35 Linux 6.8.0-94-generic #96~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Fri Jan 16 13:19:05 UTC 2 x86_64", "python_version": "3.10.12 (main, Mar 3 2026, 11:56:32) [GCC 11.4.0]", "server_version": "1.3.0", "config_file": "/home/leechj/souk_readout_tools/my_config_bun_p0.yaml", "initialisation_level": "pipeline", "current_time_unix_s": 1782397692.04318, "current_time_iso": "2026-06-25T14:28:12.043180+00:00", "server_start_unix_s": 1782397422.099668, "server_start_iso": "2026-06-25T14:23:42.099668+00:00", "server_uptime_s": 269.9435119628906, "board_uptime_s": null, "request_clients": 1, "request_client_addrs": [], "stream_clients": 0, "stream_client_addrs": [], "streaming": false, "triggered_streaming": false, "sweeping": false, "task_count": 0, "latest_sweep_data_valid": false, "firmware_interface_exists": true, "firmware_interface_ready": true, "firmware_fast_interface_exists": true, "firmware_fast_interface_ready": true, "mock": true}, {"ready": true, "source": "internal", "all_locked": true, "chips": []}]']}, 'timestamps': [1782397692.0431514]}}, {'agent_address': 'observatory.UKKIDController1', 'agg_params': {'frame_length': 600}, 'feed_name': 'UKKID_feed', 'address': 'observatory.UKKIDController1.feeds.UKKID_feed', 'record': True, 'session_id': '1782397422.0926147', 'agent_class': 'UKKIDController'}] ``` -### 6) Start tailing the log file. +### 6) Start tailing the log file. ``` cd ~/log/ocs/ @@ -487,7 +487,7 @@ Can use ```grep -v``` (invert match) to remove things you don't want e.g. tail -f observatory.UKKIDController1.log |grep -v heartbeat |grep -v autobahn ``` -### 7) Go back to the python terminal, try a standard "cold-start" observing procedure. +### 7) Go back to the python terminal, try a standard "cold-start" observing procedure. From the python terminal in (4) above, do... diff --git a/socs/agents/ukkid_controller/ukkid_controller.py b/socs/agents/ukkid_controller/ukkid_controller.py index a07284dca..fa4ff084a 100644 --- a/socs/agents/ukkid_controller/ukkid_controller.py +++ b/socs/agents/ukkid_controller/ukkid_controller.py @@ -1,16 +1,17 @@ -import time -import txaio +import glob import json -import sys -import subprocess import os -import signal import pdb -import numpy as np -import glob import re import shutil +import signal +import subprocess +import sys +import time from os import environ + +import numpy as np +import txaio from ocs import ocs_agent, site_config from ocs.ocs_twisted import TimeoutLock @@ -22,24 +23,27 @@ mock = True ############# # Where the site packages have been installed by the python virtual enivroment - should contain a dir called souk_readout_tools -souk_readout_tools_path = os.path.join(souk_readout_tools_install_dir,'client_venv/lib/python3.10/site-packages') +souk_readout_tools_path = os.path.join(souk_readout_tools_install_dir, 'client_venv/lib/python3.10/site-packages') # Should contain the script client_scripts/receive_stream_g3.py -stream_script_path = os.path.join(souk_readout_tools_install_dir,'src/souk_readout_tools/client/') +stream_script_path = os.path.join(souk_readout_tools_install_dir, 'src/souk_readout_tools/client/') # Output directory where you want your data files to get written to -data_out_dir = os.path.join(souk_readout_tools_install_dir,'src/souk_readout_tools/client/client_scripts/tmp/') +data_out_dir = os.path.join(souk_readout_tools_install_dir, 'src/souk_readout_tools/client/client_scripts/tmp/') # Directory where the config files specified in the config_file_dict dictionary below are located. -config_file_root_dir = souk_readout_tools_install_dir +config_file_root_dir = souk_readout_tools_install_dir # This next variable tells the agent which config file to push to the server depending on which kid_stream_id the agent was started with on the command line. # This will eventually have 28 entries for the 28 individual reduction pipelines. # For single pipline running, with one ukkid_controller.py agent running at a time, we only need the first entry, and it should the filename of # your souk_readout_tools config .yaml file which lives in config_file_root_dir specified above. -config_file_dict = {'ufm_kid1':'my_config_bun_p0.yaml','ufm_kid2':'my_config_bun_p1.yaml'} +config_file_dict = {'ufm_kid1': 'my_config_bun_p0.yaml', 'ufm_kid2': 'my_config_bun_p1.yaml'} ################## sys.path.append(souk_readout_tools_path) -from souk_readout_tools.client.readout_client import ReadoutClient import res_fns -from resonator_fitter import interactive_fit_viewer, fit_summary_table, fit_summary_plot, fit_resonator,fit_summary_histograms,fit_summary_write_json +from resonator_fitter import (fit_resonator, fit_summary_histograms, + fit_summary_plot, fit_summary_table, + fit_summary_write_json, interactive_fit_viewer) +from souk_readout_tools.client.readout_client import ReadoutClient + class UKKIDController: """Controller object for streaming data from and sending commands to UK KID RFSoc readout boards. @@ -56,14 +60,14 @@ class UKKIDController: JL: Add attribute documentation here. """ - def __init__(self, agent,args): + def __init__(self, agent, args): self.agent = agent self.log = agent.log self.lock = TimeoutLock() self._check_state = False self.kid_stream_id = args.kid_stream_id - self.config_file = config_file_root_dir+'/'+config_file_dict[self.kid_stream_id] - self.client = ReadoutClient(config_file=self.config_file,mock=mock) + self.config_file = config_file_root_dir + '/' + config_file_dict[self.kid_stream_id] + self.client = ReadoutClient(config_file=self.config_file, mock=mock) self.client.push_config() # JL: This needs to eventually come from @@ -71,12 +75,12 @@ def __init__(self, agent,args): # or be set in some config file somewhere. # Output data files and directories will be written below this directory. self.top_level_output_dir = data_out_dir - # Create this dir if it doesn't exist. + # Create this dir if it doesn't exist. isExist = os.path.exists(self.top_level_output_dir) if not isExist: - os.makedirs(self.top_level_output_dir) + os.makedirs(self.top_level_output_dir) - # Register OCS feed + # Register OCS feed # JL: Need to register feed names here. # Not sure what, if anything, needs to go here # just make one called UKKID_feed for the time being @@ -92,30 +96,30 @@ def __init__(self, agent,args): # the supplied "now" time object (returned from time.time() and self.kid_stream_id # Returns a tuple with the full path to the working directory and also the path relative to # self.top_level_output_dir - def _create_return_working_directory(self,time_object): - now_string = str(int(time_object)) - five_digit_ctime = now_string[0:5] - # Create output directory called five_digit_ctime if it does not already exist - out_dir = self.top_level_output_dir+'/'+five_digit_ctime - try: - os.mkdir(out_dir) - except FileExistsError: - pass - else: - self.log.info("Created dir "+out_dir ) - - # Create output directory five_digit_ctime/kid_stream_id, if it does not already exist - out_dir = self.top_level_output_dir+'/'+five_digit_ctime+'/'+self.kid_stream_id - try: - os.mkdir(out_dir) - except FileExistsError: - pass - else: - self.log.info("Created dir "+out_dir ) - out_rel_path = five_digit_ctime+'/'+self.kid_stream_id - return out_dir, out_rel_path - - # Tested at v1.1.1 + def _create_return_working_directory(self, time_object): + now_string = str(int(time_object)) + five_digit_ctime = now_string[0:5] + # Create output directory called five_digit_ctime if it does not already exist + out_dir = self.top_level_output_dir + '/' + five_digit_ctime + try: + os.mkdir(out_dir) + except FileExistsError: + pass + else: + self.log.info("Created dir " + out_dir) + + # Create output directory five_digit_ctime/kid_stream_id, if it does not already exist + out_dir = self.top_level_output_dir + '/' + five_digit_ctime + '/' + self.kid_stream_id + try: + os.mkdir(out_dir) + except FileExistsError: + pass + else: + self.log.info("Created dir " + out_dir) + out_rel_path = five_digit_ctime + '/' + self.kid_stream_id + return out_dir, out_rel_path + + # Tested at v1.1.1 def check_state(self, session, params): """ @@ -123,14 +127,14 @@ def check_state(self, session, params): will not modify the RFSoC state, so this task can be run in conjunction with other RFSoC operations. This will continuously poll UKKID metadata and update the ``session.data`` object. - - Gets the state by calling client.get_server_status() - + + Gets the state by calling client.get_server_status() + Args ---- None. - + Notes ------- The following data will be written to the session.data object:: @@ -160,14 +164,14 @@ def check_state(self, session, params): # So set the longest_subtask_duration to greater than the longest ever overiding task # e.g. likely to be the stream task. # No stream should be longer than, say, 12 hours so set to this - longest_subtask_duration = 12*60*60 + longest_subtask_duration = 12 * 60 * 60 while self._check_state: if time.time() - last_release > 1.: last_release = time.time() if not self.lock.release_and_acquire(timeout=longest_subtask_duration): print(f"Could not re-acquire lock now held by {self.lock.job}.") return False - + self.log.info("Checking status...") # JL: Below we add the same json status string to both @@ -189,19 +193,19 @@ def check_state(self, session, params): now = time.time() # JL: Need to decide what fiedls are best to send here, thinning out by hand if necessaery. - full_status = self.client.get_info(['server','clock']) + full_status = self.client.get_info(['server', 'clock']) json_status = json.dumps(full_status) - json_status_stripped = json_status.replace('{','').replace('}','') + json_status_stripped = json_status.replace('{', '').replace('}', '') self.log.info(json_status_stripped) session.data = {"value": json_status, - "timestamp": now} + "timestamp": now} # Format message for publishing to Feed message = {'block_name': 'status_string', - 'timestamp': now, - 'data': {'value': json_status}} + 'timestamp': now, + 'data': {'value': json_status}} self.agent.publish_to_feed('UKKID_feed', message) time.sleep(1) @@ -215,9 +219,9 @@ def _stop_check_state(self, session, params): return True, 'requested to stop checking state.' else: return False, 'check_state is not currently running.' - + # Sends a self.client.get_system information() call to the RF Socs. - # Tested at v1.1.1 + # Tested at v1.1.1 def get_system_information(self, session, params=None): """ @@ -227,7 +231,7 @@ def get_system_information(self, session, params=None): None Notes: - Will call self.client.get_info() to get full system information. + Will call self.client.get_info() to get full system information. Should be a quick task so does not needs its own aborter. """ @@ -236,7 +240,7 @@ def get_system_information(self, session, params=None): self.log.warn("Lock could not be acquired because it " + f"is held by {self.lock.job}") return False - + self.log.info("Getting RFSoc system information...") now = time.time() @@ -245,12 +249,12 @@ def get_system_information(self, session, params=None): # Write result string to session.data, the feed and the log. session.data = {"value": json_status, - "timestamp": now} + "timestamp": now} # Format message for publishing to Feed message = {'block_name': 'status_string', - 'timestamp': now, - 'data': {'value': json_status}} + 'timestamp': now, + 'data': {'value': json_status}} self.agent.publish_to_feed('UKKID_feed', message) self.log.info(full_status) @@ -270,7 +274,7 @@ def initialise_server(self, session, params=None): Notes: Will call self.client.initialise_server() - (or send mock string(s)). + (or send mock string(s)). Should be a quick task so does not needs its own aborter. """ @@ -279,7 +283,7 @@ def initialise_server(self, session, params=None): self.log.warn("Lock could not be acquired because it " + f"is held by {self.lock.job}") return False - + self.log.info("Sending RFSoc initialise_server...") now = time.time() @@ -287,38 +291,35 @@ def initialise_server(self, session, params=None): json_status = json.dumps(full_status) # JL: This returns a string that looks like this # '{"status": "success"}' - # However, we get errors like this + # However, we get errors like this # 2025-10-10T15-49-40.369218 Unable to format event {'log_logger': , 'log_level': , 'log_namespace': 'ocs.ocs_agent.OCSAgent', 'log_source': None, 'log_format': 'INITIALISE SERVER RETURNED JSON2:{"status": "success"}', 'log_time': 1760111380.36913, ' # message': (), 'time': 1760111380.36913, 'system': '-', 'format': '%(log_legacy)s', 'log_legacy': , 'isError': 0}: '"status"' # if we send either { or } characters in the string for the log. # So create this stripped version - json_status_stripped = json_status.replace('{','').replace('}','') + json_status_stripped = json_status.replace('{', '').replace('}', '') # Seems to be okay (I assume) for the feed and the session data though - # Write result string to session.data, the feed and the log. # Note, this also seems to turn up in the log. session.data = {"value": json_status, - "timestamp": now} + "timestamp": now} # Format message for publishing to Feed message = {'block_name': 'status_string', - 'timestamp': now, - 'data': {'value': json_status}} + 'timestamp': now, + 'data': {'value': json_status}} self.agent.publish_to_feed('UKKID_feed', message) - # Send stripped version to log. - self.log.info('initialise_server returned: '+ json_status_stripped) + # Send stripped version to log. + self.log.info('initialise_server returned: ' + json_status_stripped) time.sleep(1) - + return True, 'Sent initialise_server message to RFSocs' - @ocs_agent.param("filename", default=None, type=str) - @ocs_agent.param("f_guess_filename", default=None, type=str) + @ocs_agent.param("f_guess_filename", default=None, type=str) @ocs_agent.param("f_guess_list", default=None, type=list) @ocs_agent.param("window_size", default=2.0, type=float) - def det_res_freq_from_sweep_data(self, session, params=None): """ @@ -328,102 +329,102 @@ def det_res_freq_from_sweep_data(self, session, params=None): "filename", default=None, type=str # Filename for the input wideband sweep .csv. file. If none supplied, will use latest available in the working dir. f_guess_filename", default=None, type=str # Filename for .json file containing the resonator frequency gueses. If none supplied, will use latest available in the working dir. "f_guess_list", default=None, type=list # Optionally supply a list of resonant frequency guesses, rather than picking up from a file - "window_size", default=2.0, type=float # Optional window size in MHz - + "window_size", default=2.0, type=float # Optional window size in MHz + Notes: - Determines accurate resonant frquencies from wideband sweep data + freq. guesses. + Determines accurate resonant frquencies from wideband sweep data + freq. guesses. Takes output file from a wideband sweep, together with a resonator frequency guess file (or direct array) and does a more accurate determination of the current resonator frequencies. Resonator frequency guess files have file formats like res_freq_guess_ufm_kid1_1760716304.json And can be determined from previous RFSoC sweeps are hand determined by e.g. VNA measurements. Accurate resonances will be found if there is clear resonance minimum in a window of full width - window_width (set below) around the individual resonator frequency guesses. + window_width (set below) around the individual resonator frequency guesses. Will use the res_fns.py module for the time being to find the more accurate resonant frequency minima. - + """ with self.lock.acquire_timeout(timeout=3.0, job='det_res_freq_from_sweep_data') as acquired: if not acquired: self.log.warn("Lock could not be acquired because it " + f"is held by {self.lock.job}") return False - + self.log.info("Determining accurate resonant frequencies from wideband sweep data.") - window_size = params['window_size']*1.0e6 # Full width of window in Hz. Will need to tweak depending on reosnaotr seperation in test array. + window_size = params['window_size'] * 1.0e6 # Full width of window in Hz. Will need to tweak depending on reosnaotr seperation in test array. now = time.time() (out_full_path, out_rel_path) = self._create_return_working_directory(time.time()) # If no filename (or value array is available, fail gracefully). - f_guess_list = None + f_guess_list = None if params['f_guess_list'] is not None: - # Check if an explicit guess array has been supplied in the arguments. If so, use this, don't look for files elsewhere + # Check if an explicit guess array has been supplied in the arguments. If so, use this, don't look for files elsewhere f_guess_list = params['f_guess_list'] elif params['f_guess_filename'] is not None: # Check if a guess filename has been has been supplied. # If yes, Use user supplied filename - f_guess_filename = self.top_level_output_dir+ params['f_guess_filename'] + f_guess_filename = self.top_level_output_dir + params['f_guess_filename'] else: # if not, find latest available filename in all in the top level directory. # Filename should have correct naming scheme i.e. beginning res_freq_guess__<10-digit-unix-time>.json - prefix = 'res_freq_guess_'+self.kid_stream_id - found_files = glob.glob(self.top_level_output_dir+prefix+'*.json') + prefix = 'res_freq_guess_' + self.kid_stream_id + found_files = glob.glob(self.top_level_output_dir + prefix + '*.json') if not found_files: - f_guess_filename = None + f_guess_filename = None else: - found_files.sort() - f_guess_filename= found_files[-1] + found_files.sort() + f_guess_filename = found_files[-1] # If we have found no suitable filename, throw error and return at this point. - if f_guess_filename is None: - error_msg = 'No .json file with filename '+prefix+'*.json found within directory '+ self.top_level_output_dir - self.log.error(error_msg) - session.data = {"value": error_msg , - "timestamp": now} - message = {'block_name': 'det_res_freq_from_sweep_data_string', - 'timestamp': now, - 'data': {'value': error_msg }} - return False, error_msg + if f_guess_filename is None: + error_msg = 'No .json file with filename ' + prefix + '*.json found within directory ' + self.top_level_output_dir + self.log.error(error_msg) + session.data = {"value": error_msg, + "timestamp": now} + message = {'block_name': 'det_res_freq_from_sweep_data_string', + 'timestamp': now, + 'data': {'value': error_msg}} + return False, error_msg if f_guess_list is None: - # We are not using a user supplied list, so - # try opening the file f_guess_filename - try: + # We are not using a user supplied list, so + # try opening the file f_guess_filename + try: with open(f_guess_filename) as f: - d = json.load(f) - f_guess_list = d['f_guess_list'] - except json.decoder.JSONDecodeError: + d = json.load(f) + f_guess_list = d['f_guess_list'] + except json.decoder.JSONDecodeError: error_msg = 'Problem with decoding json file: ' + params['f_guess_filename'] self.log.error(error_msg) - session.data = {"value": error_msg , - "timestamp": now} + session.data = {"value": error_msg, + "timestamp": now} message = {'block_name': 'det_res_freq_from_sweep_data_string', - 'timestamp': now, - 'data': {'value': error_msg }} + 'timestamp': now, + 'data': {'value': error_msg}} return False, error_msg - except KeyError: + except KeyError: error_msg = 'Expected key f_guess_list not found in json file: ' + params['f_guess_filename'] self.log.error(error_msg) - session.data = {"value": error_msg , - "timestamp": now} + session.data = {"value": error_msg, + "timestamp": now} message = {'block_name': 'det_res_freq_from_sweep_data_string', - 'timestamp': now, - 'data': {'value': error_msg }} + 'timestamp': now, + 'data': {'value': error_msg}} return False, error_msg - - self.log.info('Guess Frequencies extracted from '+f_guess_filename) + + self.log.info('Guess Frequencies extracted from ' + f_guess_filename) # At this point. f_guess_list should defined - send to log. - self.log.info('Found guess frequencies of ' +str(f_guess_list)) + self.log.info('Found guess frequencies of ' + str(f_guess_list)) if params['filename'] is None: # We need to find the most recent file matching <10-digit-unix-time>_full_band_sweep.csv # (File format may (or may noe) get changed from .csv to .g3 at some point # files = [os.path.join(dirpath, filename) - for (dirpath, dirs, files) in os.walk(self.top_level_output_dir) - for filename in (dirs + files)] + for (dirpath, dirs, files) in os.walk(self.top_level_output_dir) + for filename in (dirs + files)] # Get only the files which have full_band_sweep.csv as a substring the_csv_full_paths = [x for x in files if 'full_band_sweep.csv' in x] @@ -436,23 +437,23 @@ def det_res_freq_from_sweep_data(self, session, params=None): self.log.info('Will use full band sweep filename ' + sweep_filename) # The column descriptor line begins with the word "sweep", so use 's' as well as '#' as a comment character. - x, i, q = np.loadtxt(sweep_filename, delimiter =',', usecols =(0, 1, 2), comments=['#','s'], unpack = True) - accurate_res = res_fns.find_all_resonances(x,i,q,f_guess_list,window_size) - self.log.info('Using guess frequencies of ' +str(f_guess_list)) - self.log.info('Found more accurate frequencies of ' +str(accurate_res.tolist())) + x, i, q = np.loadtxt(sweep_filename, delimiter=',', usecols=(0, 1, 2), comments=['#', 's'], unpack=True) + accurate_res = res_fns.find_all_resonances(x, i, q, f_guess_list, window_size) + self.log.info('Using guess frequencies of ' + str(f_guess_list)) + self.log.info('Found more accurate frequencies of ' + str(accurate_res.tolist())) # Write out to a json formatted file with filename res_freq_accurate_<10-digit-unix-time>.json - f_acc_dict = {'f_accurate_list': accurate_res.tolist() } + f_acc_dict = {'f_accurate_list': accurate_res.tolist()} now = time.time() - out_json_fname = out_full_path+'/'+'res_freq_accurate_'+self.kid_stream_id+'_'+str(int(now))+'.json' - self.log.info('Accurate resonant frequencies will be written out out to file '+ out_json_fname) + out_json_fname = out_full_path + '/' + 'res_freq_accurate_' + self.kid_stream_id + '_' + str(int(now)) + '.json' + self.log.info('Accurate resonant frequencies will be written out out to file ' + out_json_fname) with open(out_json_fname, 'w', encoding='utf-8') as f: - json.dump(f_acc_dict, f, ensure_ascii=False, indent=4) - self.log.info('Accurate resonant frequencies written out to file '+ out_json_fname) + json.dump(f_acc_dict, f, ensure_ascii=False, indent=4) + self.log.info('Accurate resonant frequencies written out to file ' + out_json_fname) # Finally send data out to feed - feed_string = 'Guess freqs: ' + str(f_guess_list)+' Accurate Freqs: '+ str(accurate_res.tolist()) + feed_string = 'Guess freqs: ' + str(f_guess_list) + ' Accurate Freqs: ' + str(accurate_res.tolist()) now = time.time() session.data = {"value": feed_string, "timestamp": now} @@ -460,15 +461,15 @@ def det_res_freq_from_sweep_data(self, session, params=None): # Format message for publishing to Feed message = {'block_name': 'det_res_freq_from_sweep_data_string', 'timestamp': now, - 'data': {'value': feed_string}} + 'data': {'value': feed_string}} self.agent.publish_to_feed('UKKID_feed', message) time.sleep(4) self.agent.feeds['UKKID_feed'].flush_buffer() - + return True, '"Determined accurate resonant frequencies from wideband sweep data.' - - @ocs_agent.param("filename", default=None, type=str) + + @ocs_agent.param("filename", default=None, type=str) def fit_from_narrow_sweep(self, session, params=None): """ @@ -476,20 +477,20 @@ def fit_from_narrow_sweep(self, session, params=None): from the fit_resonator.py module. Args: - + filename: Filename for the narrow_band sweep .csv file. If none supplied, will use latest available. Notes: - + """ with self.lock.acquire_timeout(timeout=3.0, job='fit_from_narrow_sweep') as acquired: if not acquired: self.log.warn("Lock could not be acquired because it " + f"is held by {self.lock.job}") return False - + self.log.info("Fitting resonator parameters from narrow band sweep data.") - + now = time.time() (out_full_path, out_rel_path) = self._create_return_working_directory(time.time()) @@ -497,8 +498,8 @@ def fit_from_narrow_sweep(self, session, params=None): # We need to find the most recent file matching <10-digit-unix-time>_full_band_sweep.csv # (File format may get changed to .g3 at some point files = [os.path.join(dirpath, filename) - for (dirpath, dirs, files) in os.walk(self.top_level_output_dir) - for filename in (dirs + files)] + for (dirpath, dirs, files) in os.walk(self.top_level_output_dir) + for filename in (dirs + files)] # Get only the files which have full_band_sweep.csv as a substring the_csv_full_paths = [x for x in files if 'narrow_band_sweep.csv' in x] @@ -507,50 +508,48 @@ def fit_from_narrow_sweep(self, session, params=None): # Get the last element, which will be the most recently taken full_band_sweep sweep_filename = the_csv_full_paths[-1] else: - sweep_filename = out_full_path+'/'+ params['filename'] - + sweep_filename = out_full_path + '/' + params['filename'] self.log.info('Will use narrow band sweep filename ' + sweep_filename) self.log.info('Importing sweep data...') - s=self.client.import_sweep(sweep_filename) + s = self.client.import_sweep(sweep_filename) self.log.info('...done.') - f,z,e = s['sweep_f'], s['sweep_i']+1j*s['sweep_q'], s['sweep_ei']+1j*s['sweep_eq'] + f, z, e = s['sweep_f'], s['sweep_i'] + 1j * s['sweep_q'], s['sweep_ei'] + 1j * s['sweep_eq'] self.log.info('Fitting resonator parameters...') - fit_results = [fit_resonator(f[:,i],z[:,i], + fit_results = [fit_resonator(f[:, i], z[:, i], sweep_direction='up', - mag_order=1,phase_order=1, - use_preconditioning=1,verbose=False) for i in range(len(f[0]))] + mag_order=1, phase_order=1, + use_preconditioning=1, verbose=False) for i in range(len(f[0]))] self.log.info('...done.') now_string = str(int(now)) self.log.info('Generating plots...') - fit_results = interactive_fit_viewer(f.T, z.T, fit_results=fit_results,show_preconditioned=1,interactive =False,output_dir=out_full_path,now_string=now_string) + fit_results = interactive_fit_viewer(f.T, z.T, fit_results=fit_results, show_preconditioned=1, interactive=False, output_dir=out_full_path, now_string=now_string) self.log.info('...done.') # JL TODO - makes this a json file. - self.log.info('Generating summary table...') - outfilename = out_full_path+'/'+'fit_summary_table_'+self.kid_stream_id+'_'+now_string +'.txt' + self.log.info('Generating summary table...') + outfilename = out_full_path + '/' + 'fit_summary_table_' + self.kid_stream_id + '_' + now_string + '.txt' table_str, summaries, filename = fit_summary_table( - fit_results, + fit_results, outfilename=outfilename ) self.log.info('...done.') # JL TODO - makes this a json file. - self.log.info('Generating summary .json file...') - outfilename = out_full_path+'/'+'fit_summary_json_'+self.kid_stream_id+'_'+now_string +'.json' + self.log.info('Generating summary .json file...') + outfilename = out_full_path + '/' + 'fit_summary_json_' + self.kid_stream_id + '_' + now_string + '.json' fit_summary_write_json( - fit_results, + fit_results, outfilename=outfilename ) self.log.info('...done.') - - self.log.info('Generating summary plot...') - outfilename = out_full_path+'/'+'fit_summary_plot_'+self.kid_stream_id+'_'+now_string+'.png' + self.log.info('Generating summary plot...') + outfilename = out_full_path + '/' + 'fit_summary_plot_' + self.kid_stream_id + '_' + now_string + '.png' fig, axes, plotfile = fit_summary_plot( fit_results, outfilename=outfilename @@ -558,21 +557,19 @@ def fit_from_narrow_sweep(self, session, params=None): self.log.info('...done.') self.log.info('Generating histogram plots...') - outfilename = out_full_path+'/'+'fit_histogram_plot_'+self.kid_stream_id+'_'+now_string+'.png' + outfilename = out_full_path + '/' + 'fit_histogram_plot_' + self.kid_stream_id + '_' + now_string + '.png' fig_hist, axes_hist, histfile = fit_summary_histograms( fit_results, - xscale_for={'Qi':'log', 'Qc':'log', 'Nonlinearity':'log'}, + xscale_for={'Qi': 'log', 'Qc': 'log', 'Nonlinearity': 'log'}, outfilename=outfilename ) self.log.info('...done.') time.sleep(4) self.agent.feeds['UKKID_feed'].flush_buffer() - + return True, '"Determined resonator parameters from narrow band sweep data.' - - - @ocs_agent.param("duration", default=30, type=int) # JL 30 seconds is default stream time - useful for test/debugging. In practice, will be manually set in co-ordination with expected ACU scan time. + @ocs_agent.param("duration", default=30, type=int) # JL 30 seconds is default stream time - useful for test/debugging. In practice, will be manually set in co-ordination with expected ACU scan time. def stream(self, session, params=None): """ @@ -581,9 +578,9 @@ def stream(self, session, params=None): Args ----- duration : int, optional - If set, determines how many seconds to stream data. Default is 30 seconds (for debug). + If set, determines how many seconds to stream data. Default is 30 seconds (for debug). """ - + with self.lock.acquire_timeout(timeout=3.0, job='stream') as acquired: if not acquired: self.log.warn("Lock could not be acquired because it " @@ -593,144 +590,141 @@ def stream(self, session, params=None): TERMINATE_GRACE_PERIOD_SECONDS = 4 self.log.info("Streaming RFSoc system information...") self.log.info("kid_stream_id: " + self.kid_stream_id) - + stream_start_time = time.time() - + # Check how many tone frequencies are currently set, to pass to the receive_stream_g3.py below. tone_freqs = self.client.get_tone_frequencies() num_freqs = len(tone_freqs) - + self.log.info("Enabling stream on RFSoc board.") - self.client.enable_stream() + self.client.enable_stream() self.log.info("Starting stream receive process.") - (out_full_path, out_rel_path) = self._create_return_working_directory(time.time()) + (out_full_path, out_rel_path) = self._create_return_working_directory(time.time()) now_string = str(int(stream_start_time)) # Make an output filename string # JL: Not exactly sure what the observatory policy on setting this is - hardwiring to '000' for the time being. - three_digit_file_count ='000' - output_stream_filename = now_string+'_'+three_digit_file_count+'.g3' - output_stream_relative_path = out_rel_path + '/'+output_stream_filename - output_stream_full_path = out_full_path + '/'+output_stream_filename - self.log.info("Will stream data to file: "+output_stream_relative_path) - - if mock: - mock_flag = "-m" - executable_list = [sys.executable, "receive_stream_g3.py",mock_flag,"-n",str(num_freqs),"-f",output_stream_relative_path,"-i",self.kid_stream_id,"-t",str(params['duration']),'-C',self.config_file] + three_digit_file_count = '000' + output_stream_filename = now_string + '_' + three_digit_file_count + '.g3' + output_stream_relative_path = out_rel_path + '/' + output_stream_filename + output_stream_full_path = out_full_path + '/' + output_stream_filename + self.log.info("Will stream data to file: " + output_stream_relative_path) + + if mock: + mock_flag = "-m" + executable_list = [sys.executable, "receive_stream_g3.py", mock_flag, "-n", str(num_freqs), "-f", output_stream_relative_path, "-i", self.kid_stream_id, "-t", str(params['duration']), '-C', self.config_file] else: - executable_list = [sys.executable, "receive_stream_g3.py","-n",str(num_freqs),"-f",output_stream_relative_path,"-i",self.kid_stream_id,"-t",str(params['duration']),'-C', self.config_file] + executable_list = [sys.executable, "receive_stream_g3.py", "-n", str(num_freqs), "-f", output_stream_relative_path, "-i", self.kid_stream_id, "-t", str(params['duration']), '-C', self.config_file] - # executable_list = [sys.executable, "receive_stream_g3.py",mock_flag,"-n",str(num_freqs),"-f",output_stream_relative_path,"-i",self.kid_stream_id,"-t",str(params['duration'])] executable_string = " ".join(executable_list) self.log.info("Launching stream task " + executable_string) - - p=subprocess.Popen(executable_list,cwd=stream_script_path+'/client_scripts/',stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=False) + + p = subprocess.Popen(executable_list, cwd=stream_script_path + '/client_scripts/', stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=False) self.log.info("stream receive process pid is " + str(p.pid)) - + # Communicate with the process for 2 seconds # long enough for the process to send any obvious errors to stderr / stdout try: - output, errors = p.communicate(timeout=2) - except: - self.log.info("Stream receive process communication reached timeout limit, as expected, continuing...") - # JL: Best way to do this? - if 'output' in locals(): - self.log.info("stream receive process STDOUT: " + output) - if 'errors' in locals(): - self.log.info("stream receive process STDERR: "+ errors) - + output, errors = p.communicate(timeout=2) + except BaseException: + self.log.info("Stream receive process communication reached timeout limit, as expected, continuing...") + # JL: Best way to do this? + if 'output' in locals(): + self.log.info("stream receive process STDOUT: " + output) + if 'errors' in locals(): + self.log.info("stream receive process STDERR: " + errors) + now = time.time() - + # Now just go into a loop to wait out the streaming duration timme - while((now - stream_start_time) < params['duration']): - now = time.time() - steam_active_seconds = (now - stream_start_time) - - # Check it is still running - # p.poll() returns None if the process is still alive, return code otherwise. - process_died = False - ret_code = p.poll() - if ret_code is None: - if os.path.exists(output_stream_full_path): - file_size = os.path.getsize(output_stream_full_path) + while ((now - stream_start_time) < params['duration']): + now = time.time() + steam_active_seconds = (now - stream_start_time) + + # Check it is still running + # p.poll() returns None if the process is still alive, return code otherwise. + process_died = False + ret_code = p.poll() + if ret_code is None: + if os.path.exists(output_stream_full_path): + file_size = os.path.getsize(output_stream_full_path) + else: + file_size = -1 + + if file_size != -1: + stream_status_string = "Data has been streaming for %.1f seconds, output file size = %i KB" % ((now - stream_start_time), file_size / 1024) + else: + stream_status_string = "Data has been streaming for %.1f seconds, no output file exists yet." % (now - stream_start_time) + else: - file_size = -1 + stream_status_string = "WARNING streaming process finished unexpectedly at %.1f seconds with return code %i" % ((now - stream_start_time), ret_code) + process_died = True + + session.data = {"value": stream_status_string, + "timestamp": now} - if file_size !=-1: - stream_status_string = "Data has been streaming for %.1f seconds, output file size = %i KB" % ((now - stream_start_time),file_size/1024) + # Format message for publishing to Feed + message = {'block_name': 'stream_string', + 'timestamp': now, + 'data': {'value': stream_status_string}} + self.agent.publish_to_feed('UKKID_feed', message) + + # Also send this string to the log - discuss wether appropriate. + if not process_died: + self.log.info(stream_status_string) else: - stream_status_string = "Data has been streaming for %.1f seconds, no output file exists yet." % (now - stream_start_time) - - else: - stream_status_string = "WARNING streaming process finished unexpectedly at %.1f seconds with return code %i" % ((now - stream_start_time),ret_code) - process_died = True - - session.data = {"value": stream_status_string, - "timestamp": now} - - # Format message for publishing to Feed - message = {'block_name': 'stream_string', - 'timestamp': now, - 'data': {'value': stream_status_string}} - self.agent.publish_to_feed('UKKID_feed', message) - - - # Also send this string to the log - discuss wether appropriate. - if not process_died: - self.log.info(stream_status_string) - else: - self.log.error(stream_status_string) - time.sleep(2) - now = time.time() - + self.log.error(stream_status_string) + time.sleep(2) + now = time.time() + # End of time duration loop, disable the stream, and terminate the streaming process. self.log.info(f"Stream duration {params['duration']} seconds expired.") # Wait a few extra seconds to allow the streaming process to terminate/ time.sleep(TERMINATE_GRACE_PERIOD_SECONDS) - - # The streaming process should have concluded of its own accord after params['duration'] + TERMINATE_GRACE_PERIOD_SECONDS seconds. + + # The streaming process should have concluded of its own accord after params['duration'] + TERMINATE_GRACE_PERIOD_SECONDS seconds. # Check whether this is the case. If not, send SIGTERM to the process - # and send a warning that the streaming process needed to be manually SIGTERMed + # and send a warning that the streaming process needed to be manually SIGTERMed self.log.info(f"Stream duration of {params['duration']} seconds has expired.") ret_code = p.poll() if ret_code is None: self.log.warn("Streaming process unexpectedly still alive after the streaming duration has expired.") self.log.warn("Will attempt to terminate - sending SIGTERM to the process...") try: - os.kill(p.pid, signal.SIGTERM) + os.kill(p.pid, signal.SIGTERM) except OSError as e: - self.log.error("Error received from os.kill:") - self.log.error(f"Failed to send signal: {os.strerror(e.errno)}") + self.log.error("Error received from os.kill:") + self.log.error(f"Failed to send signal: {os.strerror(e.errno)}") self.log.warn("... terminated.") else: self.log.info(f'Streaming process appears to have terminated cleanly with return code {ret_code}.') - - self.log.info('Disabling TCP/IP data streaming on RFSoc board.') + + self.log.info('Disabling TCP/IP data streaming on RFSoc board.') self.client.disable_stream() - + self.log.info("Returning from stream function.") message = {'block_name': 'stream_string', - 'timestamp': now, - 'data': {'value': "Streaming complete"}} + 'timestamp': now, + 'data': {'value': "Streaming complete"}} self.agent.publish_to_feed('UKKID_feed', message) - + # JL: Presumably,I should return False here if process_died error or Error received from os.kill? return True, 'Streamed RFSoc data.' def _abort_stream(self, session, params): - if session.status == 'running': - session.set_status('stopping') - - if session.status != 'running': - return False, 'Aborted streaming RFSoc data.' - + if session.status == 'running': + session.set_status('stopping') + if session.status != 'running': + return False, 'Aborted streaming RFSoc data.' # Task to perform a wideband sweep across the full RF bandwidth. + @ocs_agent.param("filename", default=None, type=str) @ocs_agent.param("bandwidth_hz", default=None, type=float) @ocs_agent.param("center_freq_hz", default=None, type=float) @@ -738,7 +732,7 @@ def _abort_stream(self, session, params): @ocs_agent.param("num_tones", default=1024, type=int) @ocs_agent.param("samples_per_point", default=10, type=int) @ocs_agent.param("apply_phase_correction", default=False, type=bool) - @ocs_agent.param("plot_data", default = True, type=bool) + @ocs_agent.param("plot_data", default=True, type=bool) @ocs_agent.param("tone_powers_dbm", default='auto', type=str) @ocs_agent.param("reference_plane", default='detector', type=str) @ocs_agent.param("optimise_tx_dynamic_range", default=True, type=bool) @@ -756,106 +750,106 @@ def full_band_sweep(self, session, params=None): "center_freq_hz", default=None, type=float -- Centre frquency for sweep in Hz. If none supplied, will use the full RF band centre (rfmax+rfmin)/2 "step_size_hz", default=10000, type=int, -- Step size for the scan, in Hz. Default is 10 kHz. "num_tones", default=1024, type=int -- Number of simultaneous probe tones that will be used in the scan. Default is 1024. - "samples_per_point", default=10, type=int, -- Number of samples taken at each frequency point. Default is 10. Adding more may reduce noise, but lead to + "samples_per_point", default=10, type=int, -- Number of samples taken at each frequency point. Default is 10. Adding more may reduce noise, but lead to longer scans (with the defaults, current takes about 43 seconds to scan). "ignore_phase_correction", default=False, type=bool -- Set to true to ignore the phase correction. - "plot_data", default = True, type = bool -- Make a .png plot of the S21 amplitude and phase of the scan. Default = True. + "plot_data", default = True, type = bool -- Make a .png plot of the S21 amplitude and phase of the scan. Default = True. "tone_amplitude", default=0.2, type=float -- Amplitude of each tone used on the scan, relative to RFSoCs DAC full-scale-deflection. """ ################################# # TO DO - # + # # Amplitiude setting - hardwired at the moment # # Think about whether we need to send more stuff to the ukkidcontroller feed as opposed to just the log. # - + with self.lock.acquire_timeout(timeout=3.0, job='full_band_sweep') as acquired: if not acquired: self.log.warn("Lock could not be acquired because it " + f"is held by {self.lock.job}") return False - + self.log.info("full_band_sweep: Preparing to perform a full band sweep for: kid_stream_id: " + self.kid_stream_id) - + plot_data = params['plot_data'] sweep_start_time = time.time() - filetype='csv' - + filetype = 'csv' + (out_full_path, out_rel_path) = self._create_return_working_directory(time.time()) now_string = str(int(sweep_start_time)) # Make an output filename string - if params['filename'] is None: - output_sweep_filename = now_string+'_'+'full_band_sweep'+'.'+filetype + if params['filename'] is None: + output_sweep_filename = now_string + '_' + 'full_band_sweep' + '.' + filetype else: - output_sweep_filename = params['filename'] - - output_sweep_full_path = out_full_path + '/'+output_sweep_filename - + output_sweep_filename = params['filename'] + + output_sweep_full_path = out_full_path + '/' + output_sweep_filename + if not mock: - self.log.info("full_band_sweep: Will write sweep data to " + output_sweep_full_path) + self.log.info("full_band_sweep: Will write sweep data to " + output_sweep_full_path) else: - # Copy an example sweep file over to the output destination, then return from the function, without doing any real scan on the RFSoC. - self.log.info("MOCK: full_band_sweep: Will write sweep data to " + output_sweep_full_path) - loop_time_seconds = 10.0 - - shutil.copy('./example_full_band_sweep.csv',output_sweep_full_path) - self.log.info("MOCK: full_band_sweep: sweep data written to " + output_sweep_full_path) - if plot_data: - plot_filename = output_sweep_full_path.replace(filetype,'')+'png' - shutil.copy('./example_full_band_sweep.png',plot_filename) - self.log.info("MOCK: full_band_sweep: sweep data PNG file written to " + plot_filename) - - now = time.time() - # Now just go into a loop to wait out the sweep time - while((now - sweep_start_time) < loop_time_seconds ): - now = time.time() - sweep_active_seconds = (now - sweep_start_time) - sweep_status_string = "MOCK: Data has been sweeping for %.1f seconds." % (now - sweep_start_time) - - session.data = {"value": 'SESSION ' + sweep_status_string, - "timestamp": now} + # Copy an example sweep file over to the output destination, then return from the function, without doing any real scan on the RFSoC. + self.log.info("MOCK: full_band_sweep: Will write sweep data to " + output_sweep_full_path) + loop_time_seconds = 10.0 - # Format message for publishing to Feed - message = {'block_name': 'sweep_string', - 'timestamp': now, - 'data': {'value': 'FEED '+ sweep_status_string}} - self.agent.publish_to_feed('UKKID_feed', message) - - # Also send this string to the log - discuss whether appropriate. - self.log.info(sweep_status_string) - time.sleep(1) - now = time.time() - self.log.info('MOCK: full_band_sweep: Sweep complete.') - return True, 'MOCK: full_band_sweep: Sweep complete.' + shutil.copy('./example_full_band_sweep.csv', output_sweep_full_path) + self.log.info("MOCK: full_band_sweep: sweep data written to " + output_sweep_full_path) + if plot_data: + plot_filename = output_sweep_full_path.replace(filetype, '') + 'png' + shutil.copy('./example_full_band_sweep.png', plot_filename) + self.log.info("MOCK: full_band_sweep: sweep data PNG file written to " + plot_filename) + + now = time.time() + # Now just go into a loop to wait out the sweep time + while ((now - sweep_start_time) < loop_time_seconds): + now = time.time() + sweep_active_seconds = (now - sweep_start_time) + sweep_status_string = "MOCK: Data has been sweeping for %.1f seconds." % (now - sweep_start_time) + + session.data = {"value": 'SESSION ' + sweep_status_string, + "timestamp": now} + + # Format message for publishing to Feed + message = {'block_name': 'sweep_string', + 'timestamp': now, + 'data': {'value': 'FEED ' + sweep_status_string}} + self.agent.publish_to_feed('UKKID_feed', message) + + # Also send this string to the log - discuss whether appropriate. + self.log.info(sweep_status_string) + time.sleep(1) + now = time.time() + self.log.info('MOCK: full_band_sweep: Sweep complete.') + return True, 'MOCK: full_band_sweep: Sweep complete.' ''' ################################# # REPLACE FROM HERE ################################# - + # Rest is for REAL, non-mocked case. # Firstly, check if a sweep is already in progress on the RFSoC. # It shouldn't be, but throw an error and return from function if there is. - now = time.time() + now = time.time() p = self.client.get_sweep_progress() if p==0.0: pass elif p != 1.0: - # Send error message to feed, and log and return from function with error state. + # Send error message to feed, and log and return from function with error state. stream_in_progress_error_msg = f'full_band_sweep: There is already a sweep in progress ({p*100:.3f}%), please wait for it to finish before starting a new one.' self.log.error(stream_in_progress_error_msg) session.data = {"value": stream_in_progress_error_msg , - "timestamp": now} + "timestamp": now} message = {'block_name': 'status_string', 'timestamp': now, 'data': {'value': stream_in_progress_error_msg }} - self.agent.publish_to_feed('UKKID_feed', message) + self.agent.publish_to_feed('UKKID_feed', message) self.agent.feeds['UKKID_feed'].flush_buffer() - - return False, stream_in_progress_error_msg - + + return False, stream_in_progress_error_msg + # Gather certain parameters from the RFSoc in order to calculate # sweep parameters. @@ -864,22 +858,22 @@ def full_band_sweep(self, session, params=None): adcclk = info['adc_clk_hz'] dacclk = adcclk dacduc = info['dac_duc_mixer_frequency_hz'] - dacnyq = info['nyquist_zone_dac0'] + dacnyq = info['nyquist_zone_dac0'] udc = self.client.config['rf_frontend']['connected'] lo = self.client.config['rf_frontend']['tx_mixer_lo_frequency_hz'] - sb = self.client.config['rf_frontend']['tx_mixer_sideband'] + sb = self.client.config['rf_frontend']['tx_mixer_sideband'] # Hardwired vales - numbe rof bytes per dac data point and fft window sizes. - + dacint = 2 txnfft = 8192 rxnfft = 8192 - + dbbmin = -dacclk/2 dbbmax = +dacclk/2 - dacmin = min([abs(dbbmin+dacduc),abs(dbbmax+dacduc)]) + dacmin = min([abs(dbbmin+dacduc),abs(dbbmax+dacduc)]) dacmax = max([abs(dbbmin+dacduc),abs(dbbmax+dacduc)]) - + rfmin = dacmin rfmax = dacmax @@ -888,7 +882,7 @@ def full_band_sweep(self, session, params=None): samples_per_point = params['samples_per_point'] ignore_phase_correction = params['ignore_phase_correction'] tone_amplitude = params['tone_amplitude'] - + try: if udc: if sb==1: @@ -903,11 +897,11 @@ def full_band_sweep(self, session, params=None): if params['bandwidth_hz'] is None: bandwidth_hz=rfmax-rfmin else: - bandwidth_hz=params['bandwidth_hz'] + bandwidth_hz=params['bandwidth_hz'] if params['center_freq_hz'] is None: center_freq_hz = (rfmax+rfmin)/2 - else: + else: center_freq_hz = params['center_freq_hz'] fmin = center_freq_hz - bandwidth_hz/2 @@ -920,10 +914,10 @@ def full_band_sweep(self, session, params=None): freqs,spacings = np.linspace(fmin, fmax, num_tones, endpoint=False, retstep=True) if spacings <= dacclk/txnfft: - raise ValueError(f'full_band_sweep: Tone spacing must be greater than {dacclk/txnfft} Hz but it is {spacings}. Try fewer tones or wider bandwidth.') + raise ValueError(f'full_band_sweep: Tone spacing must be greater than {dacclk/txnfft} Hz but it is {spacings}. Try fewer tones or wider bandwidth.') # sweep_points = 41 # not too many as its currently quite slow - sweep_points = int(bandwidth_hz / step_size_hz / num_tones) + sweep_points = int(bandwidth_hz / step_size_hz / num_tones) sweep_span = spacings * (sweep_points-1)/(sweep_points) # np.random.seed(0) @@ -940,37 +934,37 @@ def full_band_sweep(self, session, params=None): param_report_msg = f'full_band_sweep: Supplied parameters center_freq_hz {center_freq_hz} step_size_hz {step_size_hz} num_tones {num_tones} samples_per_point {samples_per_point} ignore_phase_correction {ignore_phase_correction} plot_data {plot_data}' self.log.info(param_report_msg) session.data = {"value": param_report_msg , - "timestamp": now} + "timestamp": now} message = {'block_name': 'status_string', 'timestamp': now, 'data': {'value': param_report_msg }} - self.agent.publish_to_feed('UKKID_feed', message) + self.agent.publish_to_feed('UKKID_feed', message) self.agent.feeds['UKKID_feed'].flush_buffer() param_report_msg = f'full_band_sweep: Full sweep parameters determined as: udc {udc}, lo {lo}, sb {sb}, dacduc {dacduc}, dacclk {dacclk}, dacnyq {dacnyq}, txnfft {txnfft} rxnfft {rxnfft} dacmin {dacmin} dacmax {dacmax} rfmin {rfmin} rfmax {rfmax} bandwidth_hz {bandwidth_hz} freqs {freqs} center_freqs {center_freqs} center_freqs.min() {center_freqs.min()} center_freqs.max() {center_freqs.max()}' self.log.info(param_report_msg) session.data = {"value": param_report_msg , - "timestamp": now} + "timestamp": now} message = {'block_name': 'status_string', 'timestamp': now, 'data': {'value': param_report_msg }} - self.agent.publish_to_feed('UKKID_feed', message) + self.agent.publish_to_feed('UKKID_feed', message) self.agent.feeds['UKKID_feed'].flush_buffer() - + except ValueError as e: - # Send error message to feed, and log and return from function with error state. - value_error_msg = str(e) + # Send error message to feed, and log and return from function with error state. + value_error_msg = str(e) self.log.error(value_error_msg) session.data = {"value": value_error_msg , - "timestamp": now} + "timestamp": now} message = {'block_name': 'status_string', 'timestamp': now, 'data': {'value': value_error_msg }} - self.agent.publish_to_feed('UKKID_feed', message) + self.agent.publish_to_feed('UKKID_feed', message) self.agent.feeds['UKKID_feed'].flush_buffer() - + return False, value_error_msg - + self.log.info('full_band_sweep: Setting tone frequences, ampltiudes and phases on RFSoc.') self.client.set_tone_frequencies(center_freqs) @@ -978,12 +972,12 @@ def full_band_sweep(self, session, params=None): self.client.set_tone_phases(tone_phases) self.log.info('full_band_sweep: Checking for input/output ADC/DAC saturation and DSP overflow on RFSoc.') - + try: outps = self.client.check_output_saturation() inps = self.client.check_input_saturation() dspof = self.client.check_dsp_overflow() - + if outps['result']: raise RuntimeError(f"full_band_sweep: Output saturation detected: {outps['details']}") if inps['result']: @@ -994,20 +988,20 @@ def full_band_sweep(self, session, params=None): except RuntimeError as e: # Send error message to feed, and log and return from function with error state. # Sending with error strings with curly brackets in them seems to cause errors like this. - # Unable to format event {'log_logger': , 'log_level': , 'log_namespace': 'ocs.ocs_agent.OCSAgent', 'log_source': None, 'log_format': '"full_band_sweep: Output saturation detected: {\'i0max_fs\': 0.99676513671875, \'i0min_fs\': -0.996124267578125, \'q0max_fs\': 0.99639892578125, \'q0min_fs\': -0.99737548828125, \'i1max_fs\': 0.0, \'i1min_fs\': 0.0, \'q1max_fs\': 0.0, \'q1min_fs\': 0.0, \'integration_time\': 2e-05, \'threshold\': 0.95}"', 'log_time': 1772129919.5274072, 'message': (), 'time': 1772129919.5274072, 'system': '-', 'format': '%(log_legacy)s', 'log_legacy': , 'isError': 1}: "'i0max_fs'" - # Following fixes this. + # Unable to format event {'log_logger': , 'log_level': , 'log_namespace': 'ocs.ocs_agent.OCSAgent', 'log_source': None, 'log_format': '"full_band_sweep: Output saturation detected: {\'i0max_fs\': 0.99676513671875, \'i0min_fs\': -0.996124267578125, \'q0max_fs\': 0.99639892578125, \'q0min_fs\': -0.99737548828125, \'i1max_fs\': 0.0, \'i1min_fs\': 0.0, \'q1max_fs\': 0.0, \'q1min_fs\': 0.0, \'integration_time\': 2e-05, \'threshold\': 0.95}"', 'log_time': 1772129919.5274072, 'message': (), 'time': 1772129919.5274072, 'system': '-', 'format': '%(log_legacy)s', 'log_legacy': , 'isError': 1}: "'i0max_fs'" + # Following fixes this. runtime_error_msg = repr(str(e)) runtime_error_msg =runtime_error_msg.replace('{','').replace('}','') - + self.log.error(runtime_error_msg) session.data = {"value": runtime_error_msg , - "timestamp": now} + "timestamp": now} message = {'block_name': 'status_string', 'timestamp': now, 'data': {'value': runtime_error_msg }} - self.agent.publish_to_feed('UKKID_feed', message) + self.agent.publish_to_feed('UKKID_feed', message) self.agent.feeds['UKKID_feed'].flush_buffer() - + return False, runtime_error_msg self.log.info('full_band_sweep: Performing sweep...') @@ -1018,41 +1012,41 @@ def full_band_sweep(self, session, params=None): points = sweep_points, samples_per_point = samples_per_point, direction = 'up') - + if response['status'] != 'success': raise RuntimeError(f"full_band_sweep: Sweep failed with message: {response['message']}") - except RuntimeError as e: - # Send error message to feed, and log and return from function with error state. + except RuntimeError as e: + # Send error message to feed, and log and return from function with error state. runtime_error_msg = str(e) self.log.error(runtime_error_msg) session.data = {"value": runtime_error_msg , - "timestamp": now} + "timestamp": now} message = {'block_name': 'status_string', 'timestamp': now, 'data': {'value': runtime_error_msg }} - self.agent.publish_to_feed('UKKID_feed', message) + self.agent.publish_to_feed('UKKID_feed', message) self.agent.feeds['UKKID_feed'].flush_buffer() - + return False, runtime_error_msg - + while True: p=self.client.get_sweep_progress() self.log.info(f'full_band_sweep: Sweep progress: {100*p:.3f}%') if p==1.0: break else: time.sleep(1.0) - + ################################# # NEW VER # TO HERE ################################# ''' self.log.info('full_band_sweep: Starting sweep...') - + if params['tone_powers_dbm'] != 'auto': - params['tone_powers_dbm'] = float(params['tone_powers_dbm']) - + params['tone_powers_dbm'] = float(params['tone_powers_dbm']) + sweep_data = self.client.wideband_sweep( bandwidth_hz=params['bandwidth_hz'], # None = defaults to 90% of full bandwidth center_freq_hz=params['center_freq_hz'], # None = defaults to center of band @@ -1067,10 +1061,9 @@ def full_band_sweep(self, session, params=None): verbose=params['verbose'] ) - # sweep_data = self.client.wideband_sweep(apply_phase_correction=params['apply_phase_correction']) # - + ''' self.log.info('full_band_sweep: Parsing sweep data ...') s = self.client.parse_sweep_data(self.client.get_sweep_data(),apply_phase_correction=not ignore_phase_correction) @@ -1091,31 +1084,32 @@ def full_band_sweep(self, session, params=None): s['sweep_eq'] = [np.ravel(s['sweep_eq'].T)] ''' - ## SUSPECT??? + # SUSPECT??? self.log.info('full_band_sweep: Saving sweep data to disk.') - self.log.info('full_band_sweep: sweep_full_path '+ output_sweep_full_path) - no_ext_output_sweep_full_path = output_sweep_full_path.replace('.'+filetype,'') + self.log.info('full_band_sweep: sweep_full_path ' + output_sweep_full_path) + no_ext_output_sweep_full_path = output_sweep_full_path.replace('.' + filetype, '') self.client.export_sweep(no_ext_output_sweep_full_path, sweep_data, filetype) - self.log.info('full_band_sweep: Fullband sweep exported to:'+output_sweep_full_path) + self.log.info('full_band_sweep: Fullband sweep exported to:' + output_sweep_full_path) ################################## - # NEW VER + # NEW VER # Something like this could replace this plot data bit here. # from souk_readout_tools.plotting import plot_sweep # fig = plot_sweep(data, format='magphase',multitone='overlay', title='Frequency Sweep') # plt.show() ################################## - + if plot_data: import matplotlib.pyplot as plt from souk_readout_tools.plotting import plot_sweep - #fig = plot_sweep(sweep_data, format='magphase',multitone='overlay', title='Frequency Sweep') - plot_filename = output_sweep_full_path.replace(filetype,'')+'png' + # fig = plot_sweep(sweep_data, format='magphase',multitone='overlay', title='Frequency Sweep') + + plot_filename = output_sweep_full_path.replace(filetype, '') + 'png' self.log.info('full_band_sweep: Generating quick look plot, writing to ' + plot_filename) - - fig = plot_sweep(sweep_data, format='magphase', title='Frequency Sweep') - plt.savefig(plot_filename,dpi=300) + + fig = plot_sweep(sweep_data, format='magphase', title='Frequency Sweep') + plt.savefig(plot_filename, dpi=300) self.log.info('full_band_sweep: Plot saved to ' + plot_filename) ''' ####################### @@ -1123,7 +1117,7 @@ def full_band_sweep(self, session, params=None): f = sweep_data['sweep_f'][0] z = sweep_data['sweep_i'][0] + 1j * sweep_data['sweep_q'][0] plt.plot(f / 1e9, 20 * np.log10(np.abs(z))) - plt.xlabel('Frequency (GHz)') + plt.xlabel('Frequency (GHz)') plt.ylabel('|S21| (dB)') plt.show() ###################### @@ -1131,7 +1125,7 @@ def full_band_sweep(self, session, params=None): error_bars = False plot_filename = output_sweep_full_path.replace(filetype,'')+'png' - self.log.info('full_band_sweep: Generating quick look plot, writing to ' + plot_filename) + self.log.info('full_band_sweep: Generating quick look plot, writing to ' + plot_filename) sf = s['sweep_f'][0] si = s['sweep_i'][0] sq = s['sweep_q'][0] @@ -1154,151 +1148,151 @@ def full_band_sweep(self, session, params=None): s2.errorbar(sf/1e6, uphase, yerr=ephi, fmt='.', ecolor='red') else: s1.plot(sf/1e6, logmag,linewidth=0.5) - s2.plot(sf/1e6, uphase,linewidth=0.5) + s2.plot(sf/1e6, uphase,linewidth=0.5) fig.supxlabel('Frequency (MHz)') s1.set_ylabel('Power (dB)') s2.set_ylabel('Phase (rad)') s1.set_ylim(np.min(logmag),np.max(logmag)) s2.set_ylim(np.min(uphase),np.max(uphase)) - plt.savefig(plot_filename,dpi=300) + plt.savefig(plot_filename,dpi=300) self.log.info('full_band_sweep: Plot saved to ' + plot_filename) ''' - + ################################################ - + self.log.info('full_band_sweep: Sweep complete.') - #JL: write something to the feed here? + # JL: write something to the feed here? return True, 'full_band_sweep: Sweep complete.' def _abort_full_band_sweep(self, session, params): - if session.status == 'running': - session.set_status('stopping') + if session.status == 'running': + session.set_status('stopping') + + if session.status != 'running': + return False, 'Aborted full band sweep data.' - if session.status != 'running': - return False, 'Aborted full band sweep data.' - @ocs_agent.param("filename", default=None, type=str) @ocs_agent.param("f_accurate_filename", default=None, type=str) - @ocs_agent.param("plot_data", default = True, type = bool) - @ocs_agent.param("tone_amplitude", default=0.2, type=float) - @ocs_agent.param("freq_span", default=1.0e6, type=float) - @ocs_agent.param("f_accurate_list", default=None, type=list) - @ocs_agent.param("num_points", default=2001, type=int) - @ocs_agent.param("samples_per_point", default=20, type=int) + @ocs_agent.param("plot_data", default=True, type=bool) + @ocs_agent.param("tone_amplitude", default=0.2, type=float) + @ocs_agent.param("freq_span", default=1.0e6, type=float) + @ocs_agent.param("f_accurate_list", default=None, type=list) + @ocs_agent.param("num_points", default=2001, type=int) + @ocs_agent.param("samples_per_point", default=20, type=int) def narrow_band_sweep(self, session, params=None): """ - **Task** - + **Task** - Task to perform a narrow band sweep with tones centred on a supplied list (either in supplied list or a default file) across the narrow RF bandwidth sufficient to characterise the profile of each resonator Args ---- - ---- + ---- "filename", default=None, type=str, -- Output filename for sweep data. If none supplied, will auto-generate a filename. "f_accurate_filename", default=None, type=str -- Input filename for the .json file containing the previously determined accurate resonator freqeuncies. Default behaviour (i.e. with None supplied) is to search for the working directory for the latest version of such a file. - "plot_data", default = True, type = bool -- Make a .png plot of the S21 amplitude and phase of the scan. Default = True. + "plot_data", default = True, type = bool -- Make a .png plot of the S21 amplitude and phase of the scan. Default = True. "tone_amplitude", default=0.2, type=float -- Amplitude of each tone used on the scan, relative to RFSoCs DAC full-scale-deflection. - "freq_span", default=1.0e6, type=float) -- Sweep span in Hz. - "f_accurate_list", default=None, type=list) -- List of supplied (accurate) resonator frequencies, if you want to specify these as a python list rather than reading in from a file. "num_points", default=2001, type=int) -- Number of frequency points within the sweep span window - "samples_per_point", default=20, type=int) -- Number of samples to take per frequency point + "freq_span", default=1.0e6, type=float) -- Sweep span in Hz. + "f_accurate_list", default=None, type=list) -- List of supplied (accurate) resonator frequencies, if you want to specify these as a python list rather than reading in from a file. "num_points", default=2001, type=int) -- Number of frequency points within the sweep span window + "samples_per_point", default=20, type=int) -- Number of samples to take per frequency point """ ################################# # TO DO # JL: Amplitiude setting - hardwired at the moment # JL: Think about whether we need to send more stuff to the ukkidcontroller feed as opposed to just the log. - + with self.lock.acquire_timeout(timeout=3.0, job='narrow_band_sweep') as acquired: if not acquired: self.log.warn("Lock could not be acquired because it " + f"is held by {self.lock.job}") return False - + self.log.info("narrow_band_sweep: Preparing to perform a narrow band sweep for: kid_stream_id: " + self.kid_stream_id) - + plot_data = params['plot_data'] - filetype='csv' + filetype = 'csv' (out_full_path, out_rel_path) = self._create_return_working_directory(time.time()) sweep_start_time = time.time() now_string = str(int(sweep_start_time)) - + # Make an output filename string - if params['filename'] is None: - output_sweep_filename = now_string+'_'+'narrow_band_sweep'+'.csv' - output_sweep_plot_filename = now_string+'_'+'narrow_band_sweep'+'.png' + if params['filename'] is None: + output_sweep_filename = now_string + '_' + 'narrow_band_sweep' + '.csv' + output_sweep_plot_filename = now_string + '_' + 'narrow_band_sweep' + '.png' else: - output_sweep_filename = params['filename'] - output_sweep_plot_filename = params['filename'] +'.png' - - output_sweep_narrow_path = out_full_path + '/'+output_sweep_filename - output_sweep_narrow_plot_path = out_full_path + '/'+output_sweep_plot_filename - + output_sweep_filename = params['filename'] + output_sweep_plot_filename = params['filename'] + '.png' + + output_sweep_narrow_path = out_full_path + '/' + output_sweep_filename + output_sweep_narrow_plot_path = out_full_path + '/' + output_sweep_plot_filename + if not mock: - self.log.info("narrow_band_sweep: Will write sweep data to " + output_sweep_narrow_path) - else: - self.log.info("narrow_band_sweep: Will write sweep data to " + output_sweep_narrow_path) - # Mocked behaviour - test open a file at the file location, send some progress data in a loop_time_seconds - # Then exit. - - loop_time_seconds = 10.0 - # Copy an example sweep file over to the output destination - # These example files derive from - # 1763660769_narrow_band_sweep.png - # and - # 1763660769_narrow_band_sweep.csv - shutil.copy('./example_narrow_band_sweep.csv',output_sweep_narrow_path) - self.log.info("narrow_band_sweep: sweep data written to " + output_sweep_narrow_path) - if plot_data: - plot_filename = output_sweep_narrow_path.replace(filetype,'')+'png' - shutil.copy('./example_narrow_band_sweep.png',plot_filename) - self.log.info("full_narrow_sweep: sweep data PNG file written to " + plot_filename) - - now = time.time() - # Now just go into a loop to wait out the sweep time - while((now - sweep_start_time) < loop_time_seconds ): - now = time.time() - sweep_active_seconds = (now - sweep_start_time) - sweep_status_string = "Data has been sweeping for %.1f seconds." % (now - sweep_start_time) - - session.data = {"value": 'SESSION ' + sweep_status_string, - "timestamp": now} + self.log.info("narrow_band_sweep: Will write sweep data to " + output_sweep_narrow_path) + else: + self.log.info("narrow_band_sweep: Will write sweep data to " + output_sweep_narrow_path) + # Mocked behaviour - test open a file at the file location, send some progress data in a loop_time_seconds + # Then exit. + + loop_time_seconds = 10.0 + # Copy an example sweep file over to the output destination + # These example files derive from + # 1763660769_narrow_band_sweep.png + # and + # 1763660769_narrow_band_sweep.csv + shutil.copy('./example_narrow_band_sweep.csv', output_sweep_narrow_path) + self.log.info("narrow_band_sweep: sweep data written to " + output_sweep_narrow_path) + if plot_data: + plot_filename = output_sweep_narrow_path.replace(filetype, '') + 'png' + shutil.copy('./example_narrow_band_sweep.png', plot_filename) + self.log.info("full_narrow_sweep: sweep data PNG file written to " + plot_filename) - # Format message for publishing to Feed - message = {'block_name': 'sweep_string', - 'timestamp': now, - 'data': {'value': 'FEED '+ sweep_status_string}} - self.agent.publish_to_feed('UKKID_feed', message) - - # Also send this string to the log - discuss wther appropriate. - self.log.info('LOG' + sweep_status_string) - time.sleep(1) - now = time.time() - self.log.info('narrow_band_sweep: Sweep complete.') - return True, 'narrow_band_sweep: Sweep complete.' + now = time.time() + # Now just go into a loop to wait out the sweep time + while ((now - sweep_start_time) < loop_time_seconds): + now = time.time() + sweep_active_seconds = (now - sweep_start_time) + sweep_status_string = "Data has been sweeping for %.1f seconds." % (now - sweep_start_time) + + session.data = {"value": 'SESSION ' + sweep_status_string, + "timestamp": now} + + # Format message for publishing to Feed + message = {'block_name': 'sweep_string', + 'timestamp': now, + 'data': {'value': 'FEED ' + sweep_status_string}} + self.agent.publish_to_feed('UKKID_feed', message) + + # Also send this string to the log - discuss wther appropriate. + self.log.info('LOG' + sweep_status_string) + time.sleep(1) + now = time.time() + self.log.info('narrow_band_sweep: Sweep complete.') + return True, 'narrow_band_sweep: Sweep complete.' ######################################################## # Rest is for REAL case # Firstly, check if a sweep is already in progress on the RFSoC. # It shouldn't be, but throw an error and return from function if there is. - now = time.time() + now = time.time() p = self.client.get_sweep_progress() - if p==0.0: - pass + if p == 0.0: + pass elif p != 1.0: - # Send error message to feed, and log and return from function with error state. - stream_in_progress_error_msg = f'narrow_band_sweep: There is already a sweep in progress ({p*100:.3f}%), please wait for it to finish before starting a new one.' - self.log.error(stream_in_progress_error_msg) - session.data = {"value": stream_in_progress_error_msg , - "timestamp": now} - message = {'block_name': 'status_string', - 'timestamp': now, - 'data': {'value': stream_in_progress_error_msg }} - self.agent.publish_to_feed('UKKID_feed', message) - self.agent.feeds['UKKID_feed'].flush_buffer() - - return False, stream_in_progress_error_msg - + # Send error message to feed, and log and return from function with error state. + stream_in_progress_error_msg = f'narrow_band_sweep: There is already a sweep in progress ({p * 100:.3f}%), please wait for it to finish before starting a new one.' + self.log.error(stream_in_progress_error_msg) + session.data = {"value": stream_in_progress_error_msg, + "timestamp": now} + message = {'block_name': 'status_string', + 'timestamp': now, + 'data': {'value': stream_in_progress_error_msg}} + self.agent.publish_to_feed('UKKID_feed', message) + self.agent.feeds['UKKID_feed'].flush_buffer() + + return False, stream_in_progress_error_msg + ######################################################## # Check to see if resonator list has been provided # on function call, if not, @@ -1307,68 +1301,68 @@ def narrow_band_sweep(self, session, params=None): now = time.time() f_accurate_list = None if params['f_accurate_list'] is not None: - # Use user-supplied python list of freqeuncies - f_accurate_list = params['f_accurate_list'] + # Use user-supplied python list of freqeuncies + f_accurate_list = params['f_accurate_list'] elif params['f_accurate_filename'] is not None: - # Use a user supplied json filename containing a json hash of the resonator frequencies - f_accurate_filename = self.top_level_output_dir+ params['f_accurate_filename'] + # Use a user supplied json filename containing a json hash of the resonator frequencies + f_accurate_filename = self.top_level_output_dir + params['f_accurate_filename'] else: - # Determine latest filename with the correct - # naming scheme i.e. beginning res_freq_accurate_<10-digit-unix-time>.json - prefix = 'res_freq_accurate' - found_files = glob.glob(self.top_level_output_dir+'/**/'+prefix+'*.json', recursive = True) - - if not found_files: - f_accurate_filename = None - else: - # This splits the filename by _ or . and then sorts based on the 2nd last item which is the 10 digit unix string - found_files.sort(key=lambda x: re.split('_|\.',x)[-2]) - f_accurate_filename= found_files[-1] - - # If we have found no suitable filename, throw error and return at this point. - if f_accurate_filename is None: - error_msg = 'No .json file with filename '+prefix+'*.json found within directory '+ self.top_level_output_dir - self.log.error(error_msg) - session.data = {"value": error_msg , - "timestamp": now} - message = {'block_name': 'narrow_band_sweep_string', - 'timestamp': now, - 'data': {'value': error_msg }} - return False, error_msg + # Determine latest filename with the correct + # naming scheme i.e. beginning res_freq_accurate_<10-digit-unix-time>.json + prefix = 'res_freq_accurate' + found_files = glob.glob(self.top_level_output_dir + '/**/' + prefix + '*.json', recursive=True) + + if not found_files: + f_accurate_filename = None + else: + # This splits the filename by _ or . and then sorts based on the 2nd last item which is the 10 digit unix string + found_files.sort(key=lambda x: re.split('_|\\.', x)[-2]) + f_accurate_filename = found_files[-1] + + # If we have found no suitable filename, throw error and return at this point. + if f_accurate_filename is None: + error_msg = 'No .json file with filename ' + prefix + '*.json found within directory ' + self.top_level_output_dir + self.log.error(error_msg) + session.data = {"value": error_msg, + "timestamp": now} + message = {'block_name': 'narrow_band_sweep_string', + 'timestamp': now, + 'data': {'value': error_msg}} + return False, error_msg if f_accurate_list is None: - # We are not using a user supplied list, so - # try opening the file f_accurate_filename - try: - with open(f_accurate_filename) as f: - d = json.load(f) - f_accurate_list = d['f_accurate_list'] - except json.decoder.JSONDecodeError: - error_msg = 'Problem with decoding json file: ' + params['f_accurate_filename'] - self.log.error(error_msg) - session.data = {"value": error_msg , - "timestamp": now} - message = {'block_name': 'narrow_band_sweep_string', - 'timestamp': now, - 'data': {'value': error_msg }} - return False, error_msg - except KeyError: - error_msg = 'Expected key f_accurate_list not found in json file: ' + params['f_accurate_filename'] - self.log.error(error_msg) - session.data = {"value": error_msg , - "timestamp": now} - message = {'block_name': 'narrow_band_sweep_string', - 'timestamp': now, - 'data': {'value': error_msg }} - return False, error_msg - self.log.info('Accurate Frequencies extracted from '+f_accurate_filename) - + # We are not using a user supplied list, so + # try opening the file f_accurate_filename + try: + with open(f_accurate_filename) as f: + d = json.load(f) + f_accurate_list = d['f_accurate_list'] + except json.decoder.JSONDecodeError: + error_msg = 'Problem with decoding json file: ' + params['f_accurate_filename'] + self.log.error(error_msg) + session.data = {"value": error_msg, + "timestamp": now} + message = {'block_name': 'narrow_band_sweep_string', + 'timestamp': now, + 'data': {'value': error_msg}} + return False, error_msg + except KeyError: + error_msg = 'Expected key f_accurate_list not found in json file: ' + params['f_accurate_filename'] + self.log.error(error_msg) + session.data = {"value": error_msg, + "timestamp": now} + message = {'block_name': 'narrow_band_sweep_string', + 'timestamp': now, + 'data': {'value': error_msg}} + return False, error_msg + self.log.info('Accurate Frequencies extracted from ' + f_accurate_filename) + # At this point. f_accurate_list should defined. - self.log.info('Found accurate frequencies of ' +str(f_accurate_list)) - + self.log.info('Found accurate frequencies of ' + str(f_accurate_list)) + # JL Set the all equal to supplied tone_amplitude parameter for the time being # This may get modified in the future. - tone_amplitudes = [params['tone_amplitude']]*len(f_accurate_list) + tone_amplitudes = [params['tone_amplitude']] * len(f_accurate_list) tone_phases = self.client.generate_newman_phases(f_accurate_list) self.log.info('narrow_band_sweep: Setting tone frequences, ampltiudes and phases on RFSoc.') @@ -1376,66 +1370,66 @@ def narrow_band_sweep(self, session, params=None): self.client.set_tone_amplitudes(tone_amplitudes) self.client.set_tone_phases(tone_phases) self.log.info('narrow_band_sweep: Checking for input/output ADC/DAC saturation and DSP overflow on RFSoc.') - + try: - outps = self.client.check_output_saturation() - inps = self.client.check_input_saturation() - dspof = self.client.check_dsp_overflow() - - if outps['result']: - raise RuntimeError(f"narrow_band_sweep: Output saturation detected: {outps['details']}") - if inps['result']: - raise RuntimeError(f"narrow_band_sweep: Input saturation detected: {inps['details']}") - if dspof['result']: - raise RuntimeError(f"narrow_band_sweep: DSP overflow detected: {dspof['details']}") + outps = self.client.check_output_saturation() + inps = self.client.check_input_saturation() + dspof = self.client.check_dsp_overflow() + + if outps['result']: + raise RuntimeError(f"narrow_band_sweep: Output saturation detected: {outps['details']}") + if inps['result']: + raise RuntimeError(f"narrow_band_sweep: Input saturation detected: {inps['details']}") + if dspof['result']: + raise RuntimeError(f"narrow_band_sweep: DSP overflow detected: {dspof['details']}") except RuntimeError as e: - # Send error message to feed, and log and return from function with error state. - runtime_error_msg = str(e) - self.log.error(runtime_error_msg) - session.data = {"value": runtime_error_msg , - "timestamp": now} - message = {'block_name': 'status_string', - 'timestamp': now, - 'data': {'value': runtime_error_msg }} - self.agent.publish_to_feed('UKKID_feed', message) - self.agent.feeds['UKKID_feed'].flush_buffer() - - return False, runtime_error_msg + # Send error message to feed, and log and return from function with error state. + runtime_error_msg = str(e) + self.log.error(runtime_error_msg) + session.data = {"value": runtime_error_msg, + "timestamp": now} + message = {'block_name': 'status_string', + 'timestamp': now, + 'data': {'value': runtime_error_msg}} + self.agent.publish_to_feed('UKKID_feed', message) + self.agent.feeds['UKKID_feed'].flush_buffer() - spans = [params['freq_span']]*len(f_accurate_list) + return False, runtime_error_msg + + spans = [params['freq_span']] * len(f_accurate_list) num_points = params['num_points'] samples_per_point = params['samples_per_point'] self.client.perform_sweep(f_accurate_list, spans, num_points, samples_per_point, 'up') - - #while self.client.get_server_status()['message']['latest_sweep_data_valid'] == False: - #while self.client.get_info(['server'])['server']['latest_sweep_data_valid'] == False: + + # while self.client.get_server_status()['message']['latest_sweep_data_valid'] == False: + # while self.client.get_info(['server'])['server']['latest_sweep_data_valid'] == False: while self.client.get_info(['server'])[0]['latest_sweep_data_valid'] == False: - self.log.info('Waiting for scan to complete, sleeping for 1 second.') - time.sleep(1) - - self.log.info('Getting sweep data...') + self.log.info('Waiting for scan to complete, sleeping for 1 second.') + time.sleep(1) + + self.log.info('Getting sweep data...') raw_sweep = self.client.get_sweep_data() self.log.info('...done') - - self.log.info('Parsing sweep data...') + + self.log.info('Parsing sweep data...') s = self.client.parse_sweep_data(raw_sweep) self.log.info('...done') - self.log.info('Exporting sweep data to ' + output_sweep_narrow_path +' ...') - no_ext_output_sweep_narrow_path = output_sweep_narrow_path.replace('.'+filetype,'') - self.client.export_sweep(no_ext_output_sweep_narrow_path, s, filetype) - self.log.info('...done') + self.log.info('Exporting sweep data to ' + output_sweep_narrow_path + ' ...') + no_ext_output_sweep_narrow_path = output_sweep_narrow_path.replace('.' + filetype, '') + self.client.export_sweep(no_ext_output_sweep_narrow_path, s, filetype) + self.log.info('...done') - #remove slope from phase + # remove slope from phase f = s['sweep_f'] - z = s['sweep_i']+1j*s['sweep_q'] + z = s['sweep_i'] + 1j * s['sweep_q'] fcat = np.ravel(f.T) zcat = np.ravel(z.T) phicat = np.angle(zcat) - slope = np.nanmedian(np.gradient(phicat,fcat)) - zcat *= np.exp(-1j*(slope*fcat)) + slope = np.nanmedian(np.gradient(phicat, fcat)) + zcat *= np.exp(-1j * (slope * fcat)) s['sweep_f'] = fcat s['sweep_i'] = np.real(zcat) @@ -1446,274 +1440,268 @@ def narrow_band_sweep(self, session, params=None): if params['plot_data']: self.log.info('Generating plots...') import matplotlib.pyplot as plt - fig,(s1,s2) = plt.subplots(2,1,sharex=True) - s1.scatter(s['sweep_f']/1e6, 20*np.log10(abs(s['sweep_i']+1j*s['sweep_q'])),marker='.',s=0.2) - s2.scatter(s['sweep_f']/1e6, np.atan(s['sweep_q']/s['sweep_i']),marker='.',s=0.2) - output_sweep_narrow_path = out_full_path + '/'+output_sweep_filename - plt.savefig(output_sweep_narrow_plot_path,dpi=600) + fig, (s1, s2) = plt.subplots(2, 1, sharex=True) + s1.scatter(s['sweep_f'] / 1e6, 20 * np.log10(abs(s['sweep_i'] + 1j * s['sweep_q'])), marker='.', s=0.2) + s2.scatter(s['sweep_f'] / 1e6, np.atan(s['sweep_q'] / s['sweep_i']), marker='.', s=0.2) + output_sweep_narrow_path = out_full_path + '/' + output_sweep_filename + plt.savefig(output_sweep_narrow_plot_path, dpi=600) self.log.info('...done.') - + self.log.info('narrow_band_sweep: Sweep complete.') # JL write something to the feed here? return True, 'narrow_band_sweep: Sweep complete.' def _abort_narrow_band_sweep(self, session, params): - if session.status == 'running': - session.set_status('stopping') - - if session.status != 'running': - return False, 'Aborted narrow band sweep data.' + if session.status == 'running': + session.set_status('stopping') + if session.status != 'running': + return False, 'Aborted narrow band sweep data.' - @ocs_agent.param("f_accurate_filename", default=None, type=str) @ocs_agent.param("f_accurate_list", default=None, type=list) def set_tone_frequencies(self, session, params=None): """ - **Task** - - Task to set resonator tone frequncies. + **Task** - + Task to set resonator tone frequncies. If called with no supplied parameters: Will look for a use the mot recently created .json file whch contains tone frequencies determined either from a wideband or narrowband sweep. Alternatively supply a specific .json filename with previously fitted tone frequencies from a full band or narrow band sweep. Alternatively supply a list of resonator frequencies. - + Args ---- f_accurate_filename: str, optional. Filename for file containing tone frequencies to set. f_accurate_list: list, optiinal. List of frequencies to set the tone frequencies to. """ - + with self.lock.acquire_timeout(timeout=3.0, job='set_tone_frequencies') as acquired: if not acquired: self.log.warn("Lock could not be acquired because it " + f"is held by {self.lock.job}") return False - + self.log.info("set_tone_frequencies: Preparing to set tone frequencies for: kid_stream_id: " + self.kid_stream_id) - ######################################################## - # Check to see if resonator list has been provided - # on function call, if not, - # see if an f_accurate_filename has been provided, and use that - # else use the most recent file that has a filename of format e.g. res_freq_accurate_1761061802.json + ######################################################## + # Check to see if resonator list has been provided + # on function call, if not, + # see if an f_accurate_filename has been provided, and use that + # else use the most recent file that has a filename of format e.g. res_freq_accurate_1761061802.json now = time.time() f_accurate_list = None if params['f_accurate_list'] is not None: - # Use user-supplied python list of freqeuncies - f_accurate_list = params['f_accurate_list'] + # Use user-supplied python list of freqeuncies + f_accurate_list = params['f_accurate_list'] elif params['f_accurate_filename'] is not None: - # Use a user supplied json filename containing a list (acutally record) of frequencies - f_accurate_filename = self.top_level_output_dir+ params['f_accurate_filename'] + # Use a user supplied json filename containing a list (acutally record) of frequencies + f_accurate_filename = self.top_level_output_dir + params['f_accurate_filename'] else: - # Determine latest filename with the correct - # naming scheme i.e. beginning res_freq_accurate__<10-digit-unix-time>.json - # For frequencies determined from a wideband sweep - # Or naming scheme beginning fit_summary_json__<10-digit-unix-time>.json - # For frequencies determined from a narrowband sweep - # N.B. The two types of json files have different formats, so need to be parsed differently. - - prefix = 'res_freq_accurate' - found_files = glob.glob(self.top_level_output_dir+'/**/'+prefix+'*.json', recursive = True) - prefix = 'fit_summary_json' - found_files += glob.glob(self.top_level_output_dir+'/**/'+prefix+'*.json', recursive = True) - - if not found_files: - f_accurate_filename = None - else: - # This splits the filename by _ or . and then sorts based on the 2nd last item which is the 10 digit unix string - found_files.sort(key=lambda x: re.split('_|\.',x)[-2]) - #found_files.sort() - f_accurate_filename= found_files[-1] - - # If we have found no suitable filename, throw error and return at this point. - if f_accurate_filename is None: - error_msg = 'No .json file with filename '+prefix+'*.json found within directory '+ self.top_level_output_dir - self.log.error(error_msg) - session.data = {"value": error_msg , - "timestamp": now} - message = {'block_name': 'narrow_band_sweep_string', - 'timestamp': now, - 'data': {'value': error_msg }} - return False, error_msg + # Determine latest filename with the correct + # naming scheme i.e. beginning res_freq_accurate__<10-digit-unix-time>.json + # For frequencies determined from a wideband sweep + # Or naming scheme beginning fit_summary_json__<10-digit-unix-time>.json + # For frequencies determined from a narrowband sweep + # N.B. The two types of json files have different formats, so need to be parsed differently. + + prefix = 'res_freq_accurate' + found_files = glob.glob(self.top_level_output_dir + '/**/' + prefix + '*.json', recursive=True) + prefix = 'fit_summary_json' + found_files += glob.glob(self.top_level_output_dir + '/**/' + prefix + '*.json', recursive=True) + + if not found_files: + f_accurate_filename = None + else: + # This splits the filename by _ or . and then sorts based on the 2nd last item which is the 10 digit unix string + found_files.sort(key=lambda x: re.split('_|\\.', x)[-2]) + # found_files.sort() + f_accurate_filename = found_files[-1] + + # If we have found no suitable filename, throw error and return at this point. + if f_accurate_filename is None: + error_msg = 'No .json file with filename ' + prefix + '*.json found within directory ' + self.top_level_output_dir + self.log.error(error_msg) + session.data = {"value": error_msg, + "timestamp": now} + message = {'block_name': 'narrow_band_sweep_string', + 'timestamp': now, + 'data': {'value': error_msg}} + return False, error_msg if f_accurate_list is None: - # We are not using a user supplied list, so - # try opening the file f_accurate_filename - try: - with open(f_accurate_filename) as f: - d = json.load(f) - if type(d) is dict: # res_freq_accurate__<10-digit-unix-time>.json type files contain a dict - f_accurate_list = d['f_accurate_list'] - elif type(d) is list: # fit_summary_json__<10-digit-unix-time>.json files contain a list of dicts, freqs stored in key 'f0' - f_accurate_list = [x['f0'] for x in d] - except json.decoder.JSONDecodeError: - error_msg = 'Problem with decoding json file: ' + params['f_accurate_filename'] - self.log.error(error_msg) - session.data = {"value": error_msg , - "timestamp": now} - message = {'block_name': 'narrow_band_sweep_string', - 'timestamp': now, - 'data': {'value': error_msg }} - return False, error_msg - except KeyError: - error_msg = 'Expected key f_accurate_list not found in json file: ' + params['f_accurate_filename'] - self.log.error(error_msg) - session.data = {"value": error_msg , - "timestamp": now} - message = {'block_name': 'narrow_band_sweep_string', - 'timestamp': now, - 'data': {'value': error_msg }} - return False, error_msg - self.log.info('Accurate Frequencies extracted from '+f_accurate_filename) - + # We are not using a user supplied list, so + # try opening the file f_accurate_filename + try: + with open(f_accurate_filename) as f: + d = json.load(f) + if type(d) is dict: # res_freq_accurate__<10-digit-unix-time>.json type files contain a dict + f_accurate_list = d['f_accurate_list'] + elif type(d) is list: # fit_summary_json__<10-digit-unix-time>.json files contain a list of dicts, freqs stored in key 'f0' + f_accurate_list = [x['f0'] for x in d] + except json.decoder.JSONDecodeError: + error_msg = 'Problem with decoding json file: ' + params['f_accurate_filename'] + self.log.error(error_msg) + session.data = {"value": error_msg, + "timestamp": now} + message = {'block_name': 'narrow_band_sweep_string', + 'timestamp': now, + 'data': {'value': error_msg}} + return False, error_msg + except KeyError: + error_msg = 'Expected key f_accurate_list not found in json file: ' + params['f_accurate_filename'] + self.log.error(error_msg) + session.data = {"value": error_msg, + "timestamp": now} + message = {'block_name': 'narrow_band_sweep_string', + 'timestamp': now, + 'data': {'value': error_msg}} + return False, error_msg + self.log.info('Accurate Frequencies extracted from ' + f_accurate_filename) + # At this point. f_accurate_list should defined. - self.log.info('set_tone_frequencies: Will use tone frequencies of ' +str(f_accurate_list)) + self.log.info('set_tone_frequencies: Will use tone frequencies of ' + str(f_accurate_list)) self.log.info('set_tone_frequencies: Setting tone frequences on RFSoc.') self.client.set_tone_frequencies(f_accurate_list) - + self.log.info('set_tone_frequencies: complete.') # JL write something to the feed here? return True, 'set_tone_frequencies: complete.' - def get_tone_frequencies(self, session, params=None): """ - **Task** - - Task to get resonator tone frequencies, which are written to the log and the feed. + **Task** - + Task to get resonator tone frequencies, which are written to the log and the feed. Args ---- None: """ - + with self.lock.acquire_timeout(timeout=3.0, job='get_tone_frequencies') as acquired: if not acquired: self.log.warn("Lock could not be acquired because it " + f"is held by {self.lock.job}") return False - + self.log.info("get_tone_frequencies: Preparing to get tone frequencies for: kid_stream_id: " + self.kid_stream_id) tone_freqs = self.client.get_tone_frequencies() self.log.info("get_tone_frequencies: Number of tones = %i " % len(tone_freqs)) self.log.info("get_tone_frequencies: Tone frequencies " + str(tone_freqs)) - - # JL TODO write something to the feed. + + # JL TODO write something to the feed. return True, 'get_tone_frequencies: complete.' @ocs_agent.param("amplitude_list", default=None, type=list) def set_tone_amplitudes(self, session, params=None): """ - **Task** - - Task to set resonator tone amplitudes - + **Task** - + Task to set resonator tone amplitudes + Args ---- - "amplitude_list", default=None, type=list -- List of tone amplitudes (1.0 = ADC Full Scale Deflection). + "amplitude_list", default=None, type=list -- List of tone amplitudes (1.0 = ADC Full Scale Deflection). """ - + with self.lock.acquire_timeout(timeout=3.0, job='set_tone_amplitudes') as acquired: if not acquired: self.log.warn("Lock could not be acquired because it " + f"is held by {self.lock.job}") return False - + self.log.info("set_tone_amplitudes: Preparing to set tone ampliudes for: kid_stream_id: " + self.kid_stream_id) # JL Might need to add code here that checks the number of tone frequences will match # the length of the supplied tone amplitude array amp_list = params['amplitude_list'] - - self.log.info('set_tone_amplitudes: Will use tone amplitudes of ' +str(amp_list)) + + self.log.info('set_tone_amplitudes: Will use tone amplitudes of ' + str(amp_list)) self.log.info('set_tone_amplitudes: Setting tone amplitudes on RFSoc.') self.client.set_tone_amplitudes(amp_list) - + self.log.info('set_tone_amplitudes: complete.') # JL write something to the feed here? return True, 'set_tone_amplitudes complete.' - def get_tone_amplitudes(self, session, params=None): """ - **Task** - - Task to get resonator tone amplitudes - + **Task** - + Task to get resonator tone amplitudes + Args ---- - None + None """ ################################# # TO DO - # - + # + with self.lock.acquire_timeout(timeout=3.0, job='get_tone_amplitudes') as acquired: if not acquired: self.log.warn("Lock could not be acquired because it " + f"is held by {self.lock.job}") return False - + self.log.info("get_tone_amplitudes: Preparing to get tone ampliudes for: kid_stream_id: " + self.kid_stream_id) - # JL Might need to add code here that checks the number of tone frequences will match # the length of the supplied tone amplitude array self.log.info('get_tone_amplitudes: Getting tone amplitudes on RFSoc.') amp_list = self.client.get_tone_amplitudes() - self.log.info('get_tone_amplitudes: Got tone amplitudes of ' +str(amp_list)) - + self.log.info('get_tone_amplitudes: Got tone amplitudes of ' + str(amp_list)) + self.log.info('get_tone_amplitudes: complete.') # JL write something to the feed here? - return True, 'get_tone_amplitudes complete.' - + return True, 'get_tone_amplitudes complete.' + @ocs_agent.param("power_list", default=None, type=list) def set_tone_powers(self, session, params=None): """ - **Task** - - Task to set resonator tone powers in dBm - + **Task** - + Task to set resonator tone powers in dBm + Args ---- - "power_list", default=None, type=list -- List of resonator powers in dBm + "power_list", default=None, type=list -- List of resonator powers in dBm """ - + with self.lock.acquire_timeout(timeout=3.0, job='set_tone_powers') as acquired: if not acquired: self.log.warn("Lock could not be acquired because it " + f"is held by {self.lock.job}") return False - + self.log.info("set_tone_powers: Preparing to set tone powers for: kid_stream_id: " + self.kid_stream_id) pow_list = params['power_list'] - # JL Might need to add code here that checks the number of tone frequences will match - # the length of the supplied tone amplitude array + # JL Might need to add code here that checks the number of tone frequences will match + # the length of the supplied tone amplitude array - self.log.info('set_tone_powers: Will use tone powers of ' +str(pow_list)) + self.log.info('set_tone_powers: Will use tone powers of ' + str(pow_list)) self.log.info('set_tone_powers: Setting tone powers on RFSoc.') self.client.set_tone_powers(pow_list) - + self.log.info('set_tone_powers: complete.') # JL write something to the feed here? return True, 'set_tone_powers complete.' - def get_tone_powers(self, session, params=None): """ - **Task** - - Task to get resonator tone powers - + **Task** - + Task to get resonator tone powers + Args ---- None @@ -1724,22 +1712,21 @@ def get_tone_powers(self, session, params=None): self.log.warn("Lock could not be acquired because it " + f"is held by {self.lock.job}") return False - + self.log.info("get_tone_powers: Preparing to get tone powers for: kid_stream_id: " + self.kid_stream_id) - # JL Might need to add code here that checks the number of tone frequences will match - # the length of the supplied tone amplitude array + # JL Might need to add code here that checks the number of tone frequences will match + # the length of the supplied tone amplitude array self.log.info('get_tone_powers: Getting tone powers on RFSoc.') pow_list = self.client.get_tone_powers() - self.log.info('get_tone_powers: Got tone powers of ' +str(pow_list)) - + self.log.info('get_tone_powers: Got tone powers of ' + str(pow_list)) + self.log.info('get_tone_powers: complete.') # JL write something to the feed here? return True, 'get_tone_powers complete.' - - - + + ###################################################################### # JL: We may not need command line arguments (other than kid_stream_id) when starting this agent # But I have left the infrasructure in at the moment (a simple --mode switch) @@ -1751,14 +1738,14 @@ def add_agent_args(parser_in=None): parser_in = A() pgroup = parser_in.add_argument_group('Agent Options') pgroup.add_argument('--mode', type=str, default='check_state', - choices=['check_state', 'idle'], # Command line params - either idle (does nothing) or start up the check_state process + choices=['check_state', 'idle'], # Command line params - either idle (does nothing) or start up the check_state process help="Starting action for the Agent.") # Equivalent of stream_id in the SMURF world. Each option corresponds to a specific RFSoc board connected to a specific # sub-array of KID detectors in the focal plane. # Plan would be to have 7 instances per SAT of the ukkid_controller agent running # Each with a different kid_stream_id. - pgroup.add_argument('--kid_stream_id', type=str, default='ufm_kid1', - choices=['ufm_kid1', 'ufm_kid2','ufm_kid3','ufm_kid4', 'ufm_kid5','ufm_kid6','ufm_kid7','ufm_kid8'], # Command line params - either idle (does nothing) check_state or stream process + pgroup.add_argument('--kid_stream_id', type=str, default='ufm_kid1', + choices=['ufm_kid1', 'ufm_kid2', 'ufm_kid3', 'ufm_kid4', 'ufm_kid5', 'ufm_kid6', 'ufm_kid7', 'ufm_kid8'], # Command line params - either idle (does nothing) check_state or stream process help="kid_stream_id - associated with a specific RFSoC board.") return parser_in @@ -1775,8 +1762,8 @@ def main(args=None): args = site_config.parse_args(agent_class='UKKIDController', parser=parser, args=args) - - # If '--mode', 'check_state' in the command line argumetns set up in the config.yaml + + # If '--mode', 'check_state' in the command line argumetns set up in the config.yaml # set startup = True and send it to the register_process below # this is the default behaviour. If startup is false is sent (e.g. if 'idle' is the command line parameter) # the default check_state process does not get started automatically, which isn't what we want. @@ -1786,28 +1773,28 @@ def main(args=None): agent, runner = ocs_agent.init_site_agent(args) - ukkid_controller = UKKIDController(agent,args) - + ukkid_controller = UKKIDController(agent, args) + agent.register_process( 'check_state', ukkid_controller.check_state, ukkid_controller._stop_check_state, startup=startup) - agent.register_task('det_res_freq_from_sweep_data',ukkid_controller.det_res_freq_from_sweep_data) + agent.register_task('det_res_freq_from_sweep_data', ukkid_controller.det_res_freq_from_sweep_data) agent.register_task('initialise_server', ukkid_controller.initialise_server) agent.register_task('get_system_information', ukkid_controller.get_system_information) agent.register_task('stream', ukkid_controller.stream, aborter=ukkid_controller._abort_stream) agent.register_task('full_band_sweep', ukkid_controller.full_band_sweep, aborter=ukkid_controller._abort_full_band_sweep) agent.register_task('narrow_band_sweep', ukkid_controller.narrow_band_sweep, aborter=ukkid_controller._abort_narrow_band_sweep) - agent.register_task('fit_from_narrow_sweep',ukkid_controller.fit_from_narrow_sweep) - agent.register_task('set_tone_frequencies',ukkid_controller.set_tone_frequencies) - agent.register_task('get_tone_frequencies',ukkid_controller.get_tone_frequencies) - agent.register_task('set_tone_amplitudes',ukkid_controller.set_tone_amplitudes) - agent.register_task('get_tone_amplitudes',ukkid_controller.get_tone_amplitudes) - agent.register_task('set_tone_powers',ukkid_controller.set_tone_powers) - agent.register_task('get_tone_powers',ukkid_controller.get_tone_powers) - + agent.register_task('fit_from_narrow_sweep', ukkid_controller.fit_from_narrow_sweep) + agent.register_task('set_tone_frequencies', ukkid_controller.set_tone_frequencies) + agent.register_task('get_tone_frequencies', ukkid_controller.get_tone_frequencies) + agent.register_task('set_tone_amplitudes', ukkid_controller.set_tone_amplitudes) + agent.register_task('get_tone_amplitudes', ukkid_controller.get_tone_amplitudes) + agent.register_task('set_tone_powers', ukkid_controller.set_tone_powers) + agent.register_task('get_tone_powers', ukkid_controller.get_tone_powers) + runner.run(agent, auto_reconnect=True)