From a301c44c8ed0c92dfc669a6d3969006e11f41ce0 Mon Sep 17 00:00:00 2001 From: mjrand Date: Fri, 30 Aug 2024 16:20:03 +0000 Subject: [PATCH] Added Agent for srs cg635m timing clock. --- socs/agents/ocs_plugin_so.py | 1 + socs/agents/srs_cg635m/__init__.py | 0 socs/agents/srs_cg635m/agent.py | 198 +++++++++++++++++++++++++++++ socs/agents/srs_cg635m/drivers.py | 129 +++++++++++++++++++ socs/plugin.py | 1 + 5 files changed, 329 insertions(+) create mode 100644 socs/agents/srs_cg635m/__init__.py create mode 100644 socs/agents/srs_cg635m/agent.py create mode 100644 socs/agents/srs_cg635m/drivers.py diff --git a/socs/agents/ocs_plugin_so.py b/socs/agents/ocs_plugin_so.py index 6a12954b8..f40843a2f 100644 --- a/socs/agents/ocs_plugin_so.py +++ b/socs/agents/ocs_plugin_so.py @@ -43,6 +43,7 @@ ('SmurfFileEmulator', 'smurf_file_emulator/agent.py'), ('SmurfStreamSimulator', 'smurf_stream_simulator/agent.py'), ('SmurfTimingCardAgent', 'smurf_timing_card/agent.py'), + ('SRSCG635mAgent', 'srs_cg635m/agent.py'), ('SupRsync', 'suprsync/agent.py'), ('SynaccessAgent', 'synacc/agent.py'), ('SynthAgent', 'holo_synth/agent.py'), diff --git a/socs/agents/srs_cg635m/__init__.py b/socs/agents/srs_cg635m/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/socs/agents/srs_cg635m/agent.py b/socs/agents/srs_cg635m/agent.py new file mode 100644 index 000000000..fdf4b7c51 --- /dev/null +++ b/socs/agents/srs_cg635m/agent.py @@ -0,0 +1,198 @@ +import argparse +import socket +import time +from os import environ + +import txaio +from ocs import ocs_agent, site_config +from ocs.ocs_twisted import TimeoutLock + +from socs.agents.srs_cg635m.drivers import SRS_CG635m_Interface + + +class SRSCG635mAgent: + def __init__(self, agent, ip_address, gpib_slot): + self.agent = agent + self.log = agent.log + self.lock = TimeoutLock() + + self.job = None + self.ip_address = ip_address + self.gpib_slot = gpib_slot + self.monitor = False + + self.clock = None + + # Registers Temperature and Voltage feeds + agg_params = { + 'frame_length': 10 * 60, + } + self.agent.register_feed('srs_clock', + record=True, + agg_params=agg_params, + buffer_time=0) + + @ocs_agent.param('auto_acquire', default=False, type=bool) + def init(self, session, params=None): + """init(auto_acquire=False) + + **Task** - Initialize the connection to the srs clock. + + Parameters + ---------- + auto_acquire: bool, optional + Default is False. Starts data acquisition after initialization + if True. + """ + with self.lock.acquire_timeout(0) as acquired: + if not acquired: + return False, "Could not acquire lock" + + try: + self.clock = SRS_CG635m_Interface(self.ip_address, self.gpib_slot) + self.idn = self.clock.identify() + + except socket.timeout as e: + self.log.error(f"Clock timed out during connect: {e}") + return False, "Timeout" + self.log.info("Connected to Clock: {}".format(self.idn)) + + # Start data acquisition if requested in site-config + auto_acquire = params.get('auto_acquire', False) + if auto_acquire: + self.agent.start('acq') + + return True, 'Initialized Clock.' + + @ocs_agent.param('test_mode', default=False, type=bool) + @ocs_agent.param('wait', default=1, type=float) + def acq(self, session, params): + """acq(wait=1, test_mode=False) + + **Process** - Continuously monitor srs clock lock registers + and send info to aggregator. + + The ``session.data`` object stores the most recent published values + in a dictionary. For example:: + + session.data = { + 'timestamp': 1598626144.5365012, + 'block_name': 'clock_output', + 'data': { + 'Frequency': 122880000.0000000 + 'Standard_CMOS_Output': 3, + 'Running_State': 1, + 'PLL_RF_UNLOCKED': 1, + 'PLL_19MHZ_UNLOCKED': 1, + 'PLL_10MHz_UNLOCKED': 0, + 'PLL_RB_UNLOCKED': 1, + 'PLL_OUT_UNLOCKED': 0, + 'PLL_Phase_Shift': 0, + } + } + + Parameters + ---------- + wait: float, optional + time to wait between measurements [seconds]. Default=1s. + + """ + self.monitor = True + + while self.monitor: + with self.lock.acquire_timeout(1) as acquired: + if acquired: + data = { + 'timestamp': time.time(), + 'block_name': 'clock_output', + 'data': {} + } + + try: + data['data']['Frequency'] = self.clock.get_freq() + data['data']['Standard_CMOS_Output'] = self.clock.get_stdc() + data['data']['Running_State'] = self.clock.get_runs() + + # get_lock_statuses returns a dict of the register bits + # Loop through the items to add each to the data + lock_statuses = self.clock.get_lock_statuses() + for register, status in lock_statuses.items(): + # Not adding PLL causes a naming error + # Two of the registers start with an number + data['data']["PLL_" + register] = status + + except ValueError as e: + self.log.error(f"Error in collecting data: {e}") + continue + + self.agent.publish_to_feed('srs_clock', data) + + # Allow this process to be queried to return current data + session.data = data + + else: + self.log.warn("Could not acquire in monitor clock") + + time.sleep(params['wait']) + + if params['test_mode']: + break + + return True, "Finished monitoring clock" + + def _stop_acq(self, session, params): + """Stop monitoring the clock output.""" + if self.monitor: + self.monitor = False + return True, 'requested to stop taking data.' + else: + return False, 'acq is not currently running' + + +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('--ip-address', type=str, help="Internal GPIB IP Address") + pgroup.add_argument('--gpib-slot', type=int, help="Internal SRS GPIB Address") + pgroup.add_argument('--mode', type=str, help="Set to acq to run acq on " + + "startup") + + return parser + + +def main(args=None): + # Start logging + txaio.start_logging(level=environ.get("LOGLEVEL", "info")) + + parser = site_config.add_arguments() + + # Get the default ocs agrument parser + parser = make_parser() + + args = site_config.parse_args(agent_class='SRSCG635mAgent', + parser=parser, + args=args) + + init_params = False + if args.mode == 'acq': + init_params = {'auto_acquire': True} + + agent, runner = ocs_agent.init_site_agent(args) + + p = SRSCG635mAgent(agent, args.ip_address, int(args.gpib_slot)) + + agent.register_task('init', p.init, startup=init_params) + agent.register_process('acq', p.acq, p._stop_acq) + + runner.run(agent, auto_reconnect=True) + + +if __name__ == '__main__': + main() diff --git a/socs/agents/srs_cg635m/drivers.py b/socs/agents/srs_cg635m/drivers.py new file mode 100644 index 000000000..4f3314bcd --- /dev/null +++ b/socs/agents/srs_cg635m/drivers.py @@ -0,0 +1,129 @@ +# This device uses the Prologix GPIB interface +from socs.common.prologix_interface import PrologixInterface + + +class SRS_CG635m_Interface(PrologixInterface): + """ + This device driver is written for the SRS CG635m clock used for the timing system in the SO Office. + """ + + def __init__(self, ip_address, gpibAddr, verbose=False, **kwargs): + self.verbose = verbose + super().__init__(ip_address, gpibAddr, **kwargs) + + def get_freq(self): + """ + Queries the clock for its current output frequency in Hz. + + Returns the frequency as a float. + """ + + self.write("FREQ?") + freq = self.read() + + return float(freq) + + def get_stdc(self): + """ + Queries the clock for the current Standard CMOS (STDC) output setting. + + The query returns an int with the int representing the CMOS output setting. + The outputs are represented in volts between the CMOS low and CMOS high with CMOS low = 0V. + + The standard CMOS output settings this query can return are are: + -1 = Not a standard CMOS Output + 0 = 1.2V + 1 = 1.8V + 2 = 2.5V + 3 = 3.3V (The default for our current setup) + 4 = 5.0V + """ + + self.write("STDC?") + stdc = self.read() + + return int(stdc) + + def get_runs(self): + """ + Queries the clock for the current Running State (RUNS). + + Returns an int which represents the following running states: + 0 = Not Running (Output is off) + 1 = Running (Output is on) + """ + + self.write("RUNS?") + runs = self.read() + + return int(runs) + + def get_lock_statuses(self): + """ + Queries the clock for the current Lock Registers (LCKR). + + The lock registers represent the current Lock status for following registers: + RF_UNLOCK + 19MHZ_UNLOCK + 10MHZ_UNLOCK + RB_UNLOCK + OUT_DISABLED + PHASE_SHIFT + + Returns a dict of the registers and registers statuses with the keys being the registers + and the values being an int representing the register statuses. + 1 = True, 0 = False + """ + self.write("LCKR?") + lckr = self.read() + + # The LCKR is a 8 bit register with each register status represented by a single bit. + # The LCKR? query returns a single int representation of the register bits + # The decode_lckr function finds the register bit for all registers + lckr_status = self._decode_lckr(lckr) + + return lckr_status + + def _decode_lckr(self, lckr): + """ + Takes the int representation of the lock register (lckr) and translates it into dict form. + The dict keys are the register names and the values are the register status: + 1 = True, 0 = False + + The incoming lckr int should always be <256 because its a int representation of an 8 bit reigster. + + The lock register bits are as follows: + 0 = RF_UNLOCK + 1 = 19MHZ_UNLOCK + 2 = 10MHZ_UNLOCK + 3 = RB_UNLOCK + 4 = OUT_DISABLED + 5 = PHASE_SHIFT + 6 = Reserved + 7 = Reserved + """ + + registers = {"RF_UNLOCK": None, + "19MHZ_UNLOCK": None, + "10MHZ_UNLOCK": None, + "RB_UNLOCK": None, + "OUT_DISABLED": None, + "PHASE_SHIFT": None} + + try: + lckr = int(lckr) + + if not 0 <= lckr <= 255: + # If the lckr register is outside of an 8 bit range + raise ValueError + + # Decode the lckr int by performing successive int division and subtractionof 2**(5-i) + for i, register in enumerate(list(registers)[::-1]): + register_bit = int(lckr / (2**(5 - i))) + registers[register] = int(register_bit) + lckr -= register_bit * (2**(5 - i)) + + except ValueError: + print("Invalid LCKR returned, cannot decode") + + return registers diff --git a/socs/plugin.py b/socs/plugin.py index 6ceec5d1b..307b73f59 100644 --- a/socs/plugin.py +++ b/socs/plugin.py @@ -36,6 +36,7 @@ 'SmurfFileEmulator': {'module': 'socs.agents.smurf_file_emulator.agent', 'entry_point': 'main'}, 'SmurfStreamSimulator': {'module': 'socs.agents.smurf_stream_simulator.agent', 'entry_point': 'main'}, 'SmurfTimingCardAgent': {'module': 'socs.agents.smurf_timing_card.agent', 'entry_point': 'main'}, + 'SRSCG635mAgent': {'module': 'socs.agents.srs_cg635m.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'},