diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 4756814f3..60d32bf02 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -24,9 +24,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - # Don't test in 3.12 until #1035 is fixed - # https://github.com/simonsobs/socs/issues/1035 - python: ["3.8", "3.9", "3.10", "3.11"] + python: ["3.10", "3.11", "3.12"] steps: - name: Set up Python ${{ matrix.python }} diff --git a/docs/agents/holo_fpga.rst b/docs/agents/holo_fpga.rst deleted file mode 100644 index c1f6b9da9..000000000 --- a/docs/agents/holo_fpga.rst +++ /dev/null @@ -1,91 +0,0 @@ -.. highlight:: rst - -.. _holo_fpga_agent: - -======================== -Holography FPGA Agent -======================== - -The Holography FPGA Agent is provided with OCS to help demonstrate and debug -issues with the holography ROACH2 FPGA. It will connect the computer to the -ROACH via an ethernet port, take data, and pass it to the OCS feed. - -.. argparse:: - :module: socs.agents.holo_fpga.agent - :func: make_parser - :prog: python3 agent.py - -.. _holo_fpga_deps: - -Dependencies ------------- - -.. note:: - These dependencies only support Python 3.8! As such, they are not - automatically installed when you install ``socs``. You can manually install - them, or follow the instructions below. - -- `casperfpga `_ -- `holo_daq `_ - -You can install these by first checking you are running Python 3.8:: - - $ python --version - Python 3.8.13 - -Then by either installing via pip:: - - $ python -m pip install 'casperfpga @ git+https://github.com/casper-astro/casperfpga.git@py38' - $ python -m pip install 'holog_daq @ git+https://github.com/McMahonCosmologyGroup/holog_daq.git@main' - -Or by cloning the socs repository and using the provided requirements file:: - - $ git clone https://github.com/simonsobs/socs.git - $ python -m pip install -r socs/requirements/holography.txt - -Configuration File Examples ---------------------------- - -Below are configuration examples for the ocs config file. - -OCS Site Config -```````````````` - -To configure the Holography FPGA Agent we need to add a FPGAAgent block to our -ocs configuration file. Here is an example configuration block using all of the -available arguments:: - - {'agent-class': 'FPGAAgent', - 'instance-id': 'fpga', - 'arguments': ['--config_file', 'holog_config.yaml']}, - -Holography Config File -`````````````````````` - -.. code-block:: yaml - - roach: '192.168.4.20' - ghz_to_mhz: 1000 - N_MULT: 8 - F_OFFSET: 10 - baseline: 'bd' - path_to_roach_init: "~/Desktop/holog_daq/scripts/upload_fpga_py2.py" - python2_env: "/opt/anaconda2/bin/python2 " - -Description ------------ - -The FPGAAgent contains functions which control the FPGA for holography -measurements. Before the FPGA can take measuremnts, the user needs to -initialize the FPGA using the init_FPGA() function. This will connect to the -FPGA via an ethernet port (user specified in the holog_config.yaml file) and -programs the FPGA using a .fpg file. - -Once the FPGA is initialized, the user can take data using the take_data() -function. This will record the cross-correlations A, BB, AB, and phase. - -Agent API ---------- - -.. autoclass:: socs.agents.holo_fpga.agent.FPGAAgent - :members: diff --git a/docs/agents/holo_synth.rst b/docs/agents/holo_synth.rst deleted file mode 100644 index d9e51ef19..000000000 --- a/docs/agents/holo_synth.rst +++ /dev/null @@ -1,96 +0,0 @@ -.. highlight:: rst - -.. _holo_synth_agent: - -============================= -Holography Synthesizer Agent -============================= - -The Holography Synthesizer Agent is provided with OCS to help demonstrate and -debug issues with the holography synthesizers. The synthesizers provide a -signal at a desired frequency for holography measurements. This agent will -connect the computer to the two Local Oscillators (LO's) via USB port, -initialize the LO's, set the frequency of each and pass the frequency to the -OCS feed. - -.. argparse:: - :module: socs.agents.holo_synth.agent - :func: make_parser - :prog: python3 agent.py - -.. _holo_synth_deps: - -Dependencies ------------- - -.. note:: - These dependencies are not automatically installed when you install - ``socs``. You can manually install them, or follow the instructions below. - - Also note that since this agent is tightly coupled with the - :ref:`holo_fpga_agent`, the instructions below will pull dependencies - related to that agent as well. - -- `holo_daq `_ - -You can install these by first checking you are running Python 3.8:: - - $ python --version - Python 3.8.13 - -Then by either installing via pip:: - - $ python -m pip install 'holog_daq @ git+https://github.com/McMahonCosmologyGroup/holog_daq.git@main' - -Or by cloning the socs repository and using the provided requirements file:: - - $ git clone https://github.com/simonsobs/socs.git - $ python -m pip install -r socs/requirements/holography.txt - -Configuration File Examples ---------------------------- - -Below are configuration examples for the ocs config file. - -OCS Site Config -```````````````` - -To configure the Holography FPGA Agent we need to add a FPGAAgent block to our -ocs configuration file. Here is an example configuration block using all of the -available arguments:: - - {'agent-class': 'SynthAgent', - 'instance-id': 'synth_lo', - 'arguments': ['--config_file','holog_config.yaml']} - -Holography Config File -`````````````````````` - -.. code-block:: yaml - - roach: '192.168.4.20' - ghz_to_mhz: 1000 - N_MULT: 8 - F_OFFSET: 10 - baseline: 'bd' - path_to_roach_init: "~/Desktop/holog_daq/scripts/upload_fpga_py2.py" - python2_env: "/opt/anaconda2/bin/python2 " - -Description ------------ - -The SynthAgent contains functions which control the two synthesizers for -holography measurements. Before the synthesizers can output a frequency, the -user needs to initialize both using the init_synth() function. This will -connect to the 2 synthesizers via 2 USB ports and prepares them to read in the -user-desired frequency as the signal output. - -Once the synthesizers are initialized, the user can take set the frequency -output using the set_frequencies() function. This will set the frequency -output of BOTH synthesizers. The user-specified frequency should be in GHz. - -Agent API ---------- - -.. autoclass:: socs.agents.holo_synth.agent.SynthAgent - :members: diff --git a/docs/index.rst b/docs/index.rst index 7a9240cba..c870b5a71 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -53,8 +53,6 @@ API Reference Full API documentation for core parts of the SOCS library. agents/hwp_pid agents/hwp_pmx agents/hwp_supervisor_agent - agents/holo_fpga - agents/holo_synth agents/ibootbar agents/ifm_sbn246_flowmeter agents/kikusui_pcr500ma diff --git a/docs/user/installation.rst b/docs/user/installation.rst index b73171fae..ecfe05dcd 100644 --- a/docs/user/installation.rst +++ b/docs/user/installation.rst @@ -49,8 +49,6 @@ If you would like to install all optional dependencies use the special varient trying to run for more details. - :ref:`ACU Agent` - - :ref:`Holography FPGA Agent` - - :ref:`Holography Synthesizer Agent` - :ref:`Pysmurf Controller Agent` - :ref:`LATRt XY Stage Agent` diff --git a/pyproject.toml b/pyproject.toml index 13091cf3f..daa63dcae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,14 +10,12 @@ name = "socs" dynamic = ["version"] description = "Simons Observatory Control System" readme = "README.rst" -requires-python = ">=3.8" +requires-python = ">=3.10" classifiers = [ "Framework :: Twisted", "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -49,8 +47,7 @@ acu = [ "so3g", # "soaculib @ git+https://github.com/simonsobs/soaculib.git@master", ] -# Note: Not including the holography deps, which are Python 3.8 only. Also not -# including any dependencies with only direct references. +# Note: Not including any dependencies with only direct references. all = [ "imutils", "labjack-ljm", @@ -62,12 +59,9 @@ all = [ "pyepics", "scipy", "so3g", + "sodetlib", + "sotodlib", ] -# Holography FPGA and Synthesizer Agents -# holography = [ # Note: supports python 3.8 only! -# "casperfpga @ git+https://github.com/casper-astro/casperfpga.git@py38", -# "holog_daq @ git+https://github.com/McMahonCosmologyGroup/holog_daq.git@main", -# ] # Labjack Agent labjack = [ "labjack-ljm", @@ -93,8 +87,8 @@ pfeiffer = [ pysmurf = [ "pyepics", # "pysmurf-slac @ git+https://github.com/slaclab/pysmurf.git@main", - # "sodetlib @ git+https://github.com/simonsobs/sodetlib.git@master", - # "sotodlib @ git+https://github.com/simonsobs/sotodlib.git@master", + "sodetlib", + "sotodlib", ] # SMuRF File Emulator, SMuRF Stream Simulator smurf_sim = [ diff --git a/requirements.txt b/requirements.txt index a0df3840c..4ac879ca5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,9 +21,6 @@ soaculib @ git+https://github.com/simonsobs/soaculib.git@master so3g pixell -# holography agent - python 3.8 only! -# -r requirements/holography.txt - # LabJack Agent numexpr scipy @@ -43,9 +40,8 @@ pfeiffer-vacuum-protocol==0.4 # pysmurf controller pyepics pysmurf-slac @ git+https://github.com/slaclab/pysmurf.git@main -sodetlib @ git+https://github.com/simonsobs/sodetlib.git@master -# pin to just before 3.8 support dropped -sotodlib @ git+https://github.com/simonsobs/sotodlib.git@5d613d5915b1716c401abecb5446088bce5fc1a4 +sodetlib +sotodlib # timing master monitor pyepics diff --git a/requirements/holography.txt b/requirements/holography.txt deleted file mode 100644 index de2fbbfd4..000000000 --- a/requirements/holography.txt +++ /dev/null @@ -1,2 +0,0 @@ -casperfpga @ git+https://github.com/casper-astro/casperfpga.git@py38 -holog_daq @ git+https://github.com/McMahonCosmologyGroup/holog_daq.git@main diff --git a/socs/agents/holo_fpga/__init__.py b/socs/agents/holo_fpga/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/socs/agents/holo_fpga/agent.py b/socs/agents/holo_fpga/agent.py deleted file mode 100644 index 26fd3b766..000000000 --- a/socs/agents/holo_fpga/agent.py +++ /dev/null @@ -1,183 +0,0 @@ -import argparse -import os -import time - -import numpy as np -import txaio -import yaml -from ocs import ocs_agent, site_config -from ocs.ocs_twisted import TimeoutLock - -ON_RTD = os.environ.get("READTHEDOCS") == "True" -if not ON_RTD: - import casperfpga - from holog_daq import fpga_daq3, synth3 - - -class FPGAAgent: - """ - Agent for programming FPGA and data acquisition for holography. - - Args: - config_file (str): ocs-site-configs/uchicago/field/holog_config.yaml - """ - - def __init__(self, agent, config_file): - - self.fpga = None - self.initialized = False - # self.take_data = False - - self.agent = agent - self.log = agent.log - self.lock = TimeoutLock() - - agg_params = {"frame_length": 10 * 60} # [sec] - self.agent.register_feed( - "fpga", record=True, agg_params=agg_params, buffer_time=0 - ) - - # Load dictionary of specific mirror paramters, since some parameters - # like limits and translate vary over different FTSes This is loaded - # from a yaml file, which is assumed to be in the $OCS_CONFIG_DIR - # directory. - if config_file is None: - raise Exception("No config file specified for the FTS mirror config") - else: - config_file_path = os.path.join(os.environ["OCS_CONFIG_DIR"], config_file) - with open(config_file_path) as stream: - self.holog_configs = yaml.safe_load(stream) - if self.holog_configs is None: - raise Exception("No mirror configs in config file.") - self.log.info(f"Loaded mirror configs from file {config_file_path}") - self.baseline = self.holog_configs.pop("baseline", None) - self.roach = self.holog_configs.pop("roach", None) - self.path_to_roach_init = self.holog_configs.pop("path_to_roach_init", None) - self.python2_env = self.holog_configs.pop("python2_env", None) - # The other mirror configs (speed, timeout) are optional and - # have defaults so we leave them as the dictionary. - if self.roach is None or self.baseline is None: - raise Exception("IP address and channels must be included " - "in the holography configuration keys.") - - self.roach, self.opts, self.baseline = fpga_daq3.roach2_init() - - err = os.system(self.python2_env + self.path_to_roach_init) - assert err == 0 - - print("Connecting to server %s ... " % (self.roach)) - - self.fpga = casperfpga.CasperFpga(self.roach) - time.sleep(1) - - if self.fpga.is_connected(): - print("ok\n") - - def take_data(self, session, params=None): - """take_data() - - **Task** - A task to take data from the FPGA. - - Examples: - Example for calling in a client:: - - from ocs.ocs_client import OCSClient - agent_fpga = OCSClient("fpga") # create agent - agent_fpga.take_data() # take data - - Notes: - An example of the session data:: - - >>> response.session['data'] - {"timestamp": 1601924482.722671, - "block_name": "fpga", - "data": {"amp_AA": 293.644, - "amp_BB": 33.752, - "amp_AB": 33.752, - "arr_P": 33.752} - } - """ - with self.lock.acquire_timeout(timeout=3, job="take_data") as acquired: - if not acquired: - self.log.warn( - f"Could not set position because lock held by {self.lock.job}" - ) - return False, "Could not acquire lock" - - # Grab synthesizer settings here (which FPGA bins to integrate over) - self.synth_settings = synth3.SynthOpt() - - self.synth_settings.IGNORE_PEAKS_BELOW = int(655) - self.synth_settings.IGNORE_PEAKS_ABOVE = int(660) - # Take data here - arr_aa, arr_bb, arr_ab, arr_phase, arr_index = fpga_daq3.TakeAvgData( - self.baseline, self.fpga, self.synth_settings - ) - - # Data dictionary is what we will send to the data feed: - data = {"timestamp": time.time(), "block_name": "fpga", "data": {}} - - arr_AA = np.array(fpga_daq3.running_mean(arr_aa.tolist(), 1)) - arr_BB = np.array(fpga_daq3.running_mean(arr_bb.tolist(), 1)) - arr_AB = np.array(fpga_daq3.running_mean(arr_ab.tolist(), 1)) - arr_P = np.array(fpga_daq3.running_mean(arr_phase.tolist(), 1)) - - n_channels = np.size(arr_AA) - - amp_AA = arr_AA[int(n_channels / 2)] - amp_BB = arr_BB[int(n_channels / 2)] - amp_AB = np.power(arr_AB[int(n_channels / 2)], 1) - amp_P = np.remainder(arr_P[int(n_channels / 2)], 360.0) - - data["data"]["amp_AA"] = amp_AA - data["data"]["amp_BB"] = amp_BB - data["data"]["amp_AB"] = amp_AB - data["data"]["amp_P"] = amp_P - - self.agent.publish_to_feed("fpga", data) - session.data.update(data["data"]) - - return True, "Data acquired." - - -def make_parser(parser=None): - """Build the argument parser for the Agent. Allows sphinx to automatically - build documentation based on this function. - """ - if parser is None: - parser = argparse.ArgumentParser() - - # Add options specific to this agent. - pgroup = parser.add_argument_group("Agent Options") - pgroup.add_argument("--config_file") - - return parser - - -def main(args=None): - # For logging - txaio.use_twisted() - txaio.make_logger() - - # Start logging - txaio.start_logging(level=os.environ.get("LOGLEVEL", "info")) - - parser = make_parser() - - # Interpret options in the context of site_config. - args = site_config.parse_args(agent_class="FPGAAgent", - parser=parser, - args=args) - - agent, runner = ocs_agent.init_site_agent(args) - - fpga_agent = FPGAAgent(agent, args.config_file) - - # agent.register_task("init_FPGA", fpga_agent.init_FPGA) - agent.register_task("take_data", fpga_agent.take_data) - - runner.run(agent, auto_reconnect=True) - - -if __name__ == "__main__": - main() diff --git a/socs/agents/holo_synth/__init__.py b/socs/agents/holo_synth/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/socs/agents/holo_synth/agent.py b/socs/agents/holo_synth/agent.py deleted file mode 100644 index 876ff4b82..000000000 --- a/socs/agents/holo_synth/agent.py +++ /dev/null @@ -1,252 +0,0 @@ -import argparse -import os -import time - -import txaio -import yaml -from ocs import ocs_agent, site_config -from ocs.ocs_twisted import TimeoutLock - -ON_RTD = os.environ.get("READTHEDOCS") == "True" -if not ON_RTD: - from holog_daq import synth3 - - -class SynthAgent: - """ - Agent for connecting to the Synths for holography. - - Args: - config_file (str): ocs-site-configs/uchicago/field/holog_config.yaml - """ - - def __init__(self, agent, config_file): - - self.lo_id = None - self.initialized = False - self.take_data = False - - self.agent = agent - self.log = agent.log - self.lock = TimeoutLock() - - agg_params = {"frame_length": 10 * 60} # [sec] - self.agent.register_feed( - "synth_lo", record=True, agg_params=agg_params, buffer_time=0 - ) - - if config_file is None: - raise Exception("No config file specified for the FTS mirror config") - else: - config_file_path = os.path.join(os.environ["OCS_CONFIG_DIR"], config_file) - - with open(config_file_path) as stream: - self.holog_configs = yaml.safe_load(stream) - if self.holog_configs is None: - raise Exception("No mirror configs in config file.") - self.log.info(f"Loaded mirror configs from file {config_file_path}") - self.N_MULT = self.holog_configs.pop("N_MULT", None) - self.ghz_to_mhz = self.holog_configs.pop("ghz_to_mhz", None) - - def init_synth(self, session, params=None): - """init_synth() - - **Task** - A task to initialize the synthesizers. - - Examples: - Example for calling in a client:: - - from ocs.ocs_client import OCSClient - client = OCSClient("synth_lo") - client.init_synth() - - Notes: - This task is called to turn on the synthesizers. - """ - - self.log.debug("Trying to acquire lock") - with self.lock.acquire_timeout(timeout=0, job="init") as acquired: - # Locking mechanism stops code from proceeding if no lock acquired - if not acquired: - self.log.warn( - "Could not start init because {} is already running".format( - self.lock.job - ) - ) - return False, "Could not acquire lock." - # Run the function you want to run - self.log.debug("Lock Acquired Connecting to Stages") - self.lo_id = synth3.get_LOs() - - synth3.set_RF_output(0, 1, self.lo_id) # LO ID, On=1, USB connection ID - synth3.set_RF_output(1, 1, self.lo_id) # LO ID, On=1, USB connection ID - - # data = {"timestamp": time.time(), "block_name": "synth_lo", "data": {}} - - # data["data"]["F1_status"] = 1 - # data["data"]["F2_status"] = 1 - - # self.agent.publish_to_feed("synth_lo", data) - # session.data.update(data["data"]) - - # This part is for the record and to allow future calls to proceed, - # so does not require the lock - self.initialized = True - - return True, "Synth Initialized." - - @ocs_agent.param("offset", type=float, default=0, check=lambda x: 0 <= x <= 1000) - @ocs_agent.param("freq1", type=float, default=0, check=lambda x: 0 <= x <= 1000) - def set_frequencies(self, session, params): - """set_frequencies(freq1=0, offset=0) - - **Task** - A task to set the frequencies of the synthesizers. - - Parameters: - freq1 (float): Frequency of holography measuremnt [GHz]. - offset (float): Frequency offset of holography measurement [MHz]. - - Examples: - Example for calling in a client:: - - from ocs.ocs_client import OCSClient - client = OCSClient("synth_lo") - client.set_frequencies(freq1=210, offset=10) - - Notes: - An example of the session data:: - - >>> response.session['data'] - {"timestamp": 1601924482.722671, - "block_name": "synth_lo", - "data": {"F1": 11 , - "F2": 11.1}}} - """ - f1 = params.get("freq1", 0) - f_offset = params.get("offset", 0) - - F_1 = int(f1 * self.ghz_to_mhz / self.N_MULT) # Convert GHz -> MHz for synthesizers - - with self.lock.acquire_timeout(timeout=3, job="set_frqeuencies") as acquired: - if not acquired: - self.log.warn( - f"Could not set position because lock held by {self.lock.job}" - ) - return False, "Could not acquire lock" - - synth3.set_f(0, F_1, self.lo_id) - synth3.set_f(1, F_1 + f_offset, self.lo_id) - - data = {"timestamp": time.time(), "block_name": "synth_lo", "data": {}} - - data["data"]["F1"] = F_1 - data["data"]["F2"] = F_1 + f_offset - - self.agent.publish_to_feed("synth_lo", data) - session.data.update(data["data"]) - - return True, "Frequencies Updated" - - # This function is not finished, need to figure out how to read out frequency from USB connection. - # def read_frequencies(self, session, params=None): - - # with self.lock.acquire_timeout(timeout=3, job="read_frqeuencies") as acquired: - # if not acquired: - # self.log.warn( - # f"Could not set position because lock held by {self.lock.job}" - # ) - # return False, "Could not acquire lock" - - # return True, "Frequencies Updated" - - @ocs_agent.param("lo_id", type=int, default=0, choices=[0, 1]) - @ocs_agent.param("status", type=int, default=0, choices=[0, 1]) - def set_synth_status(self, session, params): - """set_synth_status(lo_id=0, status=1) - - **Task** - A task to set the status of the synthesizers. - - Parameters: - lo_id (int): Local Oscillator ID (either 0 or 1). - status (int): Status of local oscillator (0 is off, 1 is on). - - Examples: - Example for calling in a client:: - - from ocs.ocs_client import OCSClient - agent = OCSClient("synth_lo") - agent.set_synth_status(lo_id=0, status=1) - - Notes: - An example of the session data:: - - >>> response.session['data'] - {"timestamp": 1601924482.722671, - "block_name": "synth_lo", - "data": {"F1_status": 1}} - """ - lo_id = params.get("lo_id", 0) - switch = params.get("switch", 0) - - with self.lock.acquire_timeout(timeout=3, job="turn_on_or_off_synth") as acquired: - if not acquired: - self.log.warn( - f"Could not set position because lock held by {self.lock.job}" - ) - return False, "Could not acquire lock" - - synth3.set_RF_output(lo_id, switch, self.lo_id) - - data = {"timestamp": time.time(), "block_name": "synth_lo", "data": {}} - - data["data"]["F1_status"] = switch - - self.agent.publish_to_feed("synth_lo", data) - session.data.update(data["data"]) - - return True, "Frequencies Updated" - - -def make_parser(parser=None): - """Build the argument parser for the Agent. Allows sphinx to automatically - build documentation based on this function. - """ - if parser is None: - parser = argparse.ArgumentParser() - - # Add options specific to this agent. - pgroup = parser.add_argument_group("Agent Options") - pgroup.add_argument("--config_file") - - return parser - - -def main(args=None): - # For logging - txaio.use_twisted() - txaio.make_logger() - - # Start logging - txaio.start_logging(level=os.environ.get("LOGLEVEL", "info")) - - parser = make_parser() - - # Interpret options in the context of site_config. - args = site_config.parse_args(agent_class="SynthAgent", - parser=parser, - args=args) - - agent, runner = ocs_agent.init_site_agent(args) - - synth_agent = SynthAgent(agent, args.config_file) - - agent.register_task("init_synth", synth_agent.init_synth) - agent.register_task("set_frequencies", synth_agent.set_frequencies) - # agent.register_task("read_frequencies", synth_agent.read_frequencies) - agent.register_task("set_synth_status", synth_agent.set_synth_status) - - runner.run(agent, auto_reconnect=True) - - -if __name__ == "__main__": - main() diff --git a/socs/plugin.py b/socs/plugin.py index 8ce353764..478afb608 100644 --- a/socs/plugin.py +++ b/socs/plugin.py @@ -6,7 +6,6 @@ 'CrateAgent': {'module': 'socs.agents.smurf_crate_monitor.agent', 'entry_point': 'main'}, 'CryomechCPAAgent': {'module': 'socs.agents.cryomech_cpa.agent', 'entry_point': 'main'}, 'DS378Agent': {'module': 'socs.agents.devantech_dS378.agent', 'entry_point': 'main'}, - 'FPGAAgent': {'module': 'socs.agents.holo_fpga.agent', 'entry_point': 'main'}, 'FlowmeterAgent': {'module': 'socs.agents.ifm_sbn246_flowmeter.agent', 'entry_point': 'main'}, 'FTSAerotechAgent': {'module': 'socs.agents.fts_aerotech.agent', 'entry_point': 'main'}, 'GalilAxisAgent': {'module': 'socs.agents.galil_axis.agent', 'entry_point': 'main'}, @@ -47,7 +46,6 @@ 'StimThermometerAgent': {'module': 'socs.agents.stimulator_thermometer.agent', 'entry_point': 'main'}, 'SupRsync': {'module': 'socs.agents.suprsync.agent', 'entry_point': 'main'}, 'SynaccessAgent': {'module': 'socs.agents.synacc.agent', 'entry_point': 'main'}, - 'SynthAgent': {'module': 'socs.agents.holo_synth.agent', 'entry_point': 'main'}, 'TektronixAWGAgent': {'module': 'socs.agents.tektronix3021c.agent', 'entry_point': 'main'}, 'ThorlabsMC2000BAgent': {'module': 'socs.agents.thorlabs_mc2000b.agent', 'entry_point': 'main'}, 'UCSCRadiometerAgent': {'module': 'socs.agents.ucsc_radiometer.agent', 'entry_point': 'main'},