Skip to content

Commit

Permalink
Added Agent for srs cg635m timing clock.
Browse files Browse the repository at this point in the history
  • Loading branch information
mjrand committed Aug 30, 2024
1 parent 99854a7 commit a301c44
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 0 deletions.
1 change: 1 addition & 0 deletions socs/agents/ocs_plugin_so.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
Empty file.
198 changes: 198 additions & 0 deletions socs/agents/srs_cg635m/agent.py
Original file line number Diff line number Diff line change
@@ -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()
129 changes: 129 additions & 0 deletions socs/agents/srs_cg635m/drivers.py
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions socs/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'},
Expand Down

0 comments on commit a301c44

Please sign in to comment.