-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Agent for srs cg635m timing clock.
- Loading branch information
Showing
5 changed files
with
329 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters