From 4aa67fefdaa89395d77666b7b1b051cdf56f36dd Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 7 Jun 2024 12:55:13 -0500 Subject: [PATCH 01/22] Add support for direct ethernet connection to Keithley PSUs, in addition to using the Prologix GPIB-to-ethernet adapter. --- socs/agents/scpi_psu/agent.py | 32 ++++++++++++++++++++++--------- socs/common/prologix_interface.py | 28 +++++++++++++++++++-------- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index f89a352fb..75f30105f 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -9,7 +9,7 @@ class ScpiPsuAgent: - def __init__(self, agent, ip_address, gpib_slot): + def __init__(self, agent, ip_address, gpib_slot, **kwargs): self.agent = agent self.log = agent.log self.lock = TimeoutLock() @@ -17,6 +17,9 @@ def __init__(self, agent, ip_address, gpib_slot): self.job = None self.ip_address = ip_address self.gpib_slot = gpib_slot + self.port = None + if('port' in kwargs.keys()): + self.port = kwargs['port'] self.monitor = False self.psu = None @@ -41,12 +44,21 @@ def init(self, session, params=None): if not acquired: return False, "Could not acquire lock" - try: - self.psu = PsuInterface(self.ip_address, self.gpib_slot) - self.idn = self.psu.identify() - except socket.timeout as e: - self.log.error(f"PSU timed out during connect: {e}") - return False, "Timeout" + if self.port == None: # Use the old Prologix-based GPIB code + try: + self.psu = PsuInterface(self.ip_address, self.gpib_slot) + self.idn = self.psu.identify() + except socket.timeout as e: + self.log.error(f"PSU timed out during connect: {e}") + return False, "Timeout" + else: # Use the new direct ethernet connection code + try: + self.psu = PsuInterface(self.ip_address, self.gpib_slot, port=self.port) + self.idn = self.psu.identify() + except socket.timeout as e: + self.log.error(f"PSU timed out during connect: {e}") + return False, "Timeout" + self.log.info("Connected to psu: {}".format(self.idn)) return True, 'Initialized PSU.' @@ -68,6 +80,7 @@ def monitor_output(self, session, params=None): Defaults to False. """ + session.set_status('running') self.monitor = True while self.monitor: @@ -178,6 +191,7 @@ def make_parser(parser=None): pgroup = parser.add_argument_group('Agent Options') pgroup.add_argument('--ip-address') pgroup.add_argument('--gpib-slot') + pgroup.add_argument('--port') return parser @@ -190,9 +204,9 @@ def main(args=None): agent, runner = ocs_agent.init_site_agent(args) - p = ScpiPsuAgent(agent, args.ip_address, int(args.gpib_slot)) + p = ScpiPsuAgent(agent, args.ip_address, int(args.gpib_slot), port=int(args.port)) - agent.register_task('init', p.init) + agent.register_task('init', p.init, startup=True) agent.register_task('set_voltage', p.set_voltage) agent.register_task('set_current', p.set_current) agent.register_task('set_output', p.set_output) diff --git a/socs/common/prologix_interface.py b/socs/common/prologix_interface.py index 351521096..125ddad02 100644 --- a/socs/common/prologix_interface.py +++ b/socs/common/prologix_interface.py @@ -6,20 +6,29 @@ class PrologixInterface: def __init__(self, ip_address, gpibAddr, **kwargs): self.ip_address = ip_address self.gpibAddr = gpibAddr + self.isPrologix = True self.sock = None - self.conn_socket() - self.configure() + self.port = 1234 + if('port' in kwargs.keys()): + self.isPrologix = False + self.port = kwargs['port'] + del kwargs['port'] + self.conn_socket() + else: + self.conn_socket() + self.configure() super().__init__(**kwargs) def conn_socket(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.connect((self.ip_address, 1234)) + self.sock.connect((self.ip_address, self.port)) self.sock.settimeout(5) def configure(self): - self.write('++mode 1') - self.write('++auto 1') - self.write('++addr ' + str(self.gpibAddr)) + if(self.isPrologix): + self.write('++mode 1') + self.write('++auto 1') + self.write('++addr ' + str(self.gpibAddr)) def write(self, msg): message = msg + '\n' @@ -30,8 +39,11 @@ def read(self): return self.sock.recv(128).decode().strip() def version(self): - self.write('++ver') - return self.read() + if(self.isPrologix): + self.write('++ver') + return self.read() + else: + return 'not-prologix' def identify(self): self.write('*idn?') From b23a1b914be1328a6da53426628860686803b2a5 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 10 Jun 2024 08:37:38 -0500 Subject: [PATCH 02/22] Installing pre-commit and fixing all files with 'pre-commit run --all-files' --- requirements.txt | 3 +++ socs/agents/scpi_psu/agent.py | 6 +++--- socs/common/prologix_interface.py | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index d9148fe74..bafd5564a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,3 +60,6 @@ pyModbusTCP # testing -r requirements/testing.txt + +# Contributing +pre-commit diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index 75f30105f..190559afd 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -18,7 +18,7 @@ def __init__(self, agent, ip_address, gpib_slot, **kwargs): self.ip_address = ip_address self.gpib_slot = gpib_slot self.port = None - if('port' in kwargs.keys()): + if ('port' in kwargs.keys()): self.port = kwargs['port'] self.monitor = False @@ -44,14 +44,14 @@ def init(self, session, params=None): if not acquired: return False, "Could not acquire lock" - if self.port == None: # Use the old Prologix-based GPIB code + if self.port is None: # Use the old Prologix-based GPIB code try: self.psu = PsuInterface(self.ip_address, self.gpib_slot) self.idn = self.psu.identify() except socket.timeout as e: self.log.error(f"PSU timed out during connect: {e}") return False, "Timeout" - else: # Use the new direct ethernet connection code + else: # Use the new direct ethernet connection code try: self.psu = PsuInterface(self.ip_address, self.gpib_slot, port=self.port) self.idn = self.psu.identify() diff --git a/socs/common/prologix_interface.py b/socs/common/prologix_interface.py index 125ddad02..5f38b6fc8 100644 --- a/socs/common/prologix_interface.py +++ b/socs/common/prologix_interface.py @@ -9,7 +9,7 @@ def __init__(self, ip_address, gpibAddr, **kwargs): self.isPrologix = True self.sock = None self.port = 1234 - if('port' in kwargs.keys()): + if ('port' in kwargs.keys()): self.isPrologix = False self.port = kwargs['port'] del kwargs['port'] @@ -25,7 +25,7 @@ def conn_socket(self): self.sock.settimeout(5) def configure(self): - if(self.isPrologix): + if (self.isPrologix): self.write('++mode 1') self.write('++auto 1') self.write('++addr ' + str(self.gpibAddr)) @@ -39,7 +39,7 @@ def read(self): return self.sock.recv(128).decode().strip() def version(self): - if(self.isPrologix): + if (self.isPrologix): self.write('++ver') return self.read() else: From bb99d548804136c8fc02c6f7aa1603c2bda7c526 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 12 Jun 2024 12:57:43 -0500 Subject: [PATCH 03/22] Moving all direct-ethernet-connection changes from prologix_interface.py to drivers.py; adding new ScpiPsuInterface class to implement drivers for SCPI PSUs that can be connected to directly by Ethernet. --- socs/agents/scpi_psu/agent.py | 10 ++- socs/agents/scpi_psu/drivers.py | 125 ++++++++++++++++++++++++++++++ socs/common/prologix_interface.py | 28 ++----- 3 files changed, 141 insertions(+), 22 deletions(-) diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index 190559afd..bef12efd9 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -5,7 +5,7 @@ from ocs import ocs_agent, site_config from ocs.ocs_twisted import TimeoutLock -from socs.agents.scpi_psu.drivers import PsuInterface +from socs.agents.scpi_psu.drivers import PsuInterface, ScpiPsuInterface class ScpiPsuAgent: @@ -53,11 +53,17 @@ def init(self, session, params=None): return False, "Timeout" else: # Use the new direct ethernet connection code try: - self.psu = PsuInterface(self.ip_address, self.gpib_slot, port=self.port) + self.psu = ScpiPsuInterface(self.ip_address, self.gpib_slot, port=self.port) self.idn = self.psu.identify() except socket.timeout as e: self.log.error(f"PSU timed out during connect: {e}") return False, "Timeout" + except ValueError as e: + if (e.args[0].startswith('Model number')): + self.log.error(f"PSU initialization error: {e}. Suggest appending {e.args[-1]} to the list of known model numbers in scpi_psu/drivers.py") + else: + self.log.error(f"PSU initialization resulted in unknown ValueError: {e}") + return False, "ValueError" self.log.info("Connected to psu: {}".format(self.idn)) diff --git a/socs/agents/scpi_psu/drivers.py b/socs/agents/scpi_psu/drivers.py index 64a1dbead..3fe1aacb7 100644 --- a/socs/agents/scpi_psu/drivers.py +++ b/socs/agents/scpi_psu/drivers.py @@ -1,6 +1,131 @@ # Tucker Elleflot + +import socket +import time + from socs.common.prologix_interface import PrologixInterface +# append new model strings as needed +ONE_CHANNEL_MODELS = ['2280S-60-3', '2280S-32-6'] +THREE_CHANNEL_MODELS = ['2230G-30-1'] + + +class ScpiPsuInterface: + def __init__(self, ip_address, gpibAddr, port, **kwargs): + self.ip_address = ip_address + self.gpibAddr = gpibAddr + self.port = port + self.sock = None + self.model = None + self.numChannels = 0 + self.conn_socket() + try: + self.configure() + except ValueError as err: + raise ValueError(err) + super().__init__(**kwargs) + + def conn_socket(self): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((self.ip_address, self.port)) + self.sock.settimeout(5) + + def read(self): + return self.sock.recv(128).decode().strip() + + def write(self, msg): + message = msg + '\n' + self.sock.sendall(message.encode()) + time.sleep(0.1) # to prevent flooding the connection + + def identify(self): + self.write('*idn?') + return self.read() + + def read_model(self): + idn_response = self.identify().split(',')[1] + if (idn_response.startswith('MODEL')): + return idn_response[6:] + else: + return idn_response + + def configure(self): + self.model = self.read_model() + if (self.model in ONE_CHANNEL_MODELS): + self.numChannels = 1 + if (self.model in THREE_CHANNEL_MODELS): + self.numChannels = 3 + if (self.numChannels == 0): + raise ValueError('Model number not found in known device models', self.model) + + def enable(self, ch): + ''' + Enables output for channel (1,2,3) but does not turn it on. + Depending on state of power supply, it might need to be called + before the output is set. + ''' + self.set_chan(ch) + self.write('OUTP:ENAB ON') + + def disable(self, ch): + ''' + disabled output from a channel (1,2,3). once called, enable must be + called to turn on the channel again + ''' + self.write('OUTP:ENAB OFF') + + def set_chan(self, ch): + self.write('inst:nsel ' + str(ch)) + + def set_output(self, ch, out): + ''' + set status of power supply channel + ch - channel (1,2,3) to set status + out - ON: True|1|'ON' OFF: False|0|'OFF' + + Calls enable to ensure a channel can be turned on. We might want to + make them separate (and let us use disable as a safety feature) but + for now I am thinking we just want to thing to turn on when we tell + it to turn on. + ''' + self.set_chan(ch) + self.enable(ch) + if isinstance(out, str): + self.write('CHAN:OUTP ' + out) + elif out: + self.write('CHAN:OUTP ON') + else: + self.write('CHAN:OUTP OFF') + + def get_output(self, ch): + ''' + check if the output of a channel (1,2,3) is on (True) or off (False) + ''' + self.set_chan(ch) + self.write('CHAN:OUTP:STAT?') + out = bool(float(self.read())) + return out + + def set_volt(self, ch, volt): + self.set_chan(ch) + self.write('volt ' + str(volt)) + + def set_curr(self, ch, curr): + self.set_chan(ch) + self.write('curr ' + str(curr)) + + def get_volt(self, ch): + self.set_chan(ch) + self.write('MEAS:VOLT? CH' + str(ch)) + voltage = float(self.read()) + return voltage + + def get_curr(self, ch): + self.set_chan(ch) + self.write('MEAS:CURR? CH' + str(ch)) + current = float(self.read()) + return current + class PsuInterface(PrologixInterface): def __init__(self, ip_address, gpibAddr, verbose=False, **kwargs): diff --git a/socs/common/prologix_interface.py b/socs/common/prologix_interface.py index 5f38b6fc8..351521096 100644 --- a/socs/common/prologix_interface.py +++ b/socs/common/prologix_interface.py @@ -6,29 +6,20 @@ class PrologixInterface: def __init__(self, ip_address, gpibAddr, **kwargs): self.ip_address = ip_address self.gpibAddr = gpibAddr - self.isPrologix = True self.sock = None - self.port = 1234 - if ('port' in kwargs.keys()): - self.isPrologix = False - self.port = kwargs['port'] - del kwargs['port'] - self.conn_socket() - else: - self.conn_socket() - self.configure() + self.conn_socket() + self.configure() super().__init__(**kwargs) def conn_socket(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.connect((self.ip_address, self.port)) + self.sock.connect((self.ip_address, 1234)) self.sock.settimeout(5) def configure(self): - if (self.isPrologix): - self.write('++mode 1') - self.write('++auto 1') - self.write('++addr ' + str(self.gpibAddr)) + self.write('++mode 1') + self.write('++auto 1') + self.write('++addr ' + str(self.gpibAddr)) def write(self, msg): message = msg + '\n' @@ -39,11 +30,8 @@ def read(self): return self.sock.recv(128).decode().strip() def version(self): - if (self.isPrologix): - self.write('++ver') - return self.read() - else: - return 'not-prologix' + self.write('++ver') + return self.read() def identify(self): self.write('*idn?') From 8125cc12981375d14bc356d4d512530c316c09c6 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 13 Jun 2024 16:32:22 -0500 Subject: [PATCH 04/22] Fixing issues with parsing output from Keithley 2280S-60-3; fixing problems related to number of channels being different for different models of PSU. --- socs/agents/scpi_psu/drivers.py | 45 +++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/socs/agents/scpi_psu/drivers.py b/socs/agents/scpi_psu/drivers.py index 3fe1aacb7..d2432d41b 100644 --- a/socs/agents/scpi_psu/drivers.py +++ b/socs/agents/scpi_psu/drivers.py @@ -64,8 +64,9 @@ def enable(self, ch): Depending on state of power supply, it might need to be called before the output is set. ''' - self.set_chan(ch) - self.write('OUTP:ENAB ON') + if (self.numChannels != 1): + self.set_chan(ch) + self.write('OUTP:ENAB ON') def disable(self, ch): ''' @@ -75,7 +76,8 @@ def disable(self, ch): self.write('OUTP:ENAB OFF') def set_chan(self, ch): - self.write('inst:nsel ' + str(ch)) + if (self.numChannels != 1): + self.write('inst:nsel ' + str(ch)) def set_output(self, ch, out): ''' @@ -90,19 +92,30 @@ def set_output(self, ch, out): ''' self.set_chan(ch) self.enable(ch) - if isinstance(out, str): - self.write('CHAN:OUTP ' + out) - elif out: - self.write('CHAN:OUTP ON') + if (self.numChannels != 1): + if isinstance(out, str): + self.write('CHAN:OUTP ' + out) + elif out: + self.write('CHAN:OUTP ON') + else: + self.write('CHAN:OUTP OFF') else: - self.write('CHAN:OUTP OFF') + if isinstance(out, str): + self.write('OUTP ' + out) + elif out: + self.write('OUTP ON') + else: + self.write('OUTP OFF') def get_output(self, ch): ''' check if the output of a channel (1,2,3) is on (True) or off (False) ''' self.set_chan(ch) - self.write('CHAN:OUTP:STAT?') + if (self.numChannels != 1): + self.write('CHAN:OUTP:STAT?') + else: + self.write('OUTP:STAT?') out = bool(float(self.read())) return out @@ -116,14 +129,20 @@ def set_curr(self, ch, curr): def get_volt(self, ch): self.set_chan(ch) - self.write('MEAS:VOLT? CH' + str(ch)) - voltage = float(self.read()) + if (self.numChannels != 1): + self.write('MEAS:VOLT? CH' + str(ch)) + else: + self.write('MEAS:VOLT?') + voltage = float(self.read().split(',')[1].strip('V')) return voltage def get_curr(self, ch): self.set_chan(ch) - self.write('MEAS:CURR? CH' + str(ch)) - current = float(self.read()) + if (self.numChannels != 1): + self.write('MEAS:CURR? CH' + str(ch)) + else: + self.write('MEAS:CURR?') + current = float(self.read().split(',')[0].strip('A')) return current From dc08af0e0895f8aa4ebe844a4ad069c42ad463ae Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 13 Jun 2024 18:26:34 -0500 Subject: [PATCH 05/22] Adding '--mode' : 'acq' to the ScpiPsuAgent to enable automatic acquisition of current and voltage measurements as found in other device agents. --- socs/agents/scpi_psu/agent.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index bef12efd9..6a04fdaa3 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -33,7 +33,7 @@ def __init__(self, agent, ip_address, gpib_slot, **kwargs): agg_params=agg_params, buffer_time=0) - @ocs_agent.param('_') + # @ocs_agent.param('_') def init(self, session, params=None): """init() @@ -67,6 +67,14 @@ def init(self, session, params=None): self.log.info("Connected to psu: {}".format(self.idn)) + session.add_message('Initialized PSU.') + auto_acquire = params.get('auto_acquire', False) + + if auto_acquire: + acq_params = None + if self.psu.numChannels == 1: + acq_params = {'channels': [1]} + self.agent.start('monitor_output', acq_params) return True, 'Initialized PSU.' @ocs_agent.param('wait', type=float, default=1) @@ -198,6 +206,7 @@ def make_parser(parser=None): pgroup.add_argument('--ip-address') pgroup.add_argument('--gpib-slot') pgroup.add_argument('--port') + pgroup.add_argument('--mode') return parser @@ -208,11 +217,14 @@ def main(args=None): 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 = ScpiPsuAgent(agent, args.ip_address, int(args.gpib_slot), port=int(args.port)) - agent.register_task('init', p.init, startup=True) + agent.register_task('init', p.init, startup=init_params) agent.register_task('set_voltage', p.set_voltage) agent.register_task('set_current', p.set_current) agent.register_task('set_output', p.set_output) From 18450cd9464be326d7b2e0ea796db0d4a6186835 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 24 Jul 2024 17:46:38 -0500 Subject: [PATCH 06/22] fixing issue with psu agent container crashing when attempting to measure when the output channel is off --- socs/agents/scpi_psu/agent.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index 6a04fdaa3..c94b999de 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -107,9 +107,11 @@ def monitor_output(self, session, params=None): } for chan in params['channels']: - data['data']["Voltage_{}".format(chan)] = self.psu.get_volt(chan) - data['data']["Current_{}".format(chan)] = self.psu.get_curr(chan) - + if self.psu.get_output(1): + data['data']["Voltage_{}".format(chan)] = self.psu.get_volt(chan) + data['data']["Current_{}".format(chan)] = self.psu.get_curr(chan) + else: + self.log.warn("Cannot measure output when output is disabled") # self.log.info(str(data)) # print(data) self.agent.publish_to_feed('psu_output', data) From fbbe9377442f377885a3c6b54c56d5fe6b355c28 Mon Sep 17 00:00:00 2001 From: texmexlab Date: Wed, 24 Jul 2024 19:58:35 -0500 Subject: [PATCH 07/22] Fixing issue related to Setuptools v71 issue #4483; fixing issue where PSU container would crash when attempting to measure when the output was disabled. --- requirements.txt | 11 +++++++++++ socs/agents/scpi_psu/agent.py | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bafd5564a..55bcaa737 100644 --- a/requirements.txt +++ b/requirements.txt @@ -63,3 +63,14 @@ pyModbusTCP # Contributing pre-commit + +# Ensure compatability with Setuptools 71 +packaging>=24 +ordered-set>=3.1.1 +more_itertools>=8.8 +jaraco.text>=3.7 +importlib_resources>=5.10.2 +importlib_metadata>=6 +tomli>=2.0.1 +wheel>=0.43.0 +platformdirs>=2.6.2 diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index c94b999de..a2a16dbc1 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -107,11 +107,14 @@ def monitor_output(self, session, params=None): } for chan in params['channels']: - if self.psu.get_output(1): + if self.psu.get_output(chan): data['data']["Voltage_{}".format(chan)] = self.psu.get_volt(chan) data['data']["Current_{}".format(chan)] = self.psu.get_curr(chan) else: self.log.warn("Cannot measure output when output is disabled") + data['data']["Voltage_{}".format(chan)] = 'off' + data['data']["Current_{}".format(chan)] = 'off' + # self.log.info(str(data)) # print(data) self.agent.publish_to_feed('psu_output', data) From e37ef879739464ce289a71ae1cf73bed6c726b02 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 31 Jul 2024 01:57:57 -0500 Subject: [PATCH 08/22] Fixing issue with data feed output corrupting influxdb when acq mode used with PSU output disabled. --- socs/agents/scpi_psu/agent.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index a2a16dbc1..a0f2cf316 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -112,8 +112,8 @@ def monitor_output(self, session, params=None): data['data']["Current_{}".format(chan)] = self.psu.get_curr(chan) else: self.log.warn("Cannot measure output when output is disabled") - data['data']["Voltage_{}".format(chan)] = 'off' - data['data']["Current_{}".format(chan)] = 'off' + self.monitor = False + return False, "Cannot measure output when output is disabled" # self.log.info(str(data)) # print(data) @@ -211,7 +211,8 @@ def make_parser(parser=None): pgroup.add_argument('--ip-address') pgroup.add_argument('--gpib-slot') pgroup.add_argument('--port') - pgroup.add_argument('--mode') + pgroup.add_argument('--mode', type=str, default='acq', + choices=['init', 'acq']) return parser From b2058d6e53fc5185e37c19501d49284f705a4f9b Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 2 Aug 2024 03:05:19 -0500 Subject: [PATCH 09/22] Adding PSU agent tasks for getting the output state, voltage measurements, and current measurements of each channel of the PSU for use by client scripts. --- socs/agents/scpi_psu/agent.py | 74 +++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index a0f2cf316..e4f4d4c52 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -136,6 +136,31 @@ def stop_monitoring(self, session, params=None): self.monitor = False return True, "Stopping current monitor" + @ocs_agent.param('channel', type=int, choices=[1, 2, 3]) + def get_voltage(self, session, params=None): + """get_voltage(channel) + + **Task** - Measure and return the voltage of the power supply. + + Parameters: + channel (int): Channel number (1, 2, or 3). + + """ + chan = params['channel'] + with self.lock.acquire_timeout(1) as acquired: + if acquired: + data = { + 'timestamp': time.time(), + 'block_name': 'output', + 'data': {} + } + + data['data']['Voltage_{}'.format(chan)] = self.psu.get_volt(chan) + session.data = data + else: + return False, "Could not acquire lock" + return True, 'Channel {} voltage measured'.format(chan) + @ocs_agent.param('channel', type=int, choices=[1, 2, 3]) @ocs_agent.param('volts', type=float, check=lambda x: 0 <= x <= 30) def set_voltage(self, session, params=None): @@ -157,6 +182,30 @@ def set_voltage(self, session, params=None): return True, 'Set channel {} voltage to {}'.format(params['channel'], params['volts']) + @ocs_agent.param('channel', type=int, choices=[1, 2, 3]) + def get_current(self, session, params=None): + """get_current(channel) + + **Task** - Measure and return the current of the power supply. + + Parameters: + channel (int): Channel number (1, 2, or 3). + + """ + chan = params['channel'] + with self.lock.acquire_timeout(1) as acquired: + if acquired: + data = { + 'timestamp': time.time(), + 'block_name': 'output', + 'data': {} + } + data['data']['Current_{}'.format(chan)] = self.psu.get_curr(chan) + session.data = data + else: + return False, "Could not acquire lock" + return True, 'Channel {} current measured'.format(chan) + @ocs_agent.param('channel', type=int, choices=[1, 2, 3]) @ocs_agent.param('current', type=float) def set_current(self, session, params=None): @@ -177,6 +226,26 @@ def set_current(self, session, params=None): return True, 'Set channel {} current to {}'.format(params['channel'], params['current']) + @ocs_agent.param('channel', type=int, choices=[1, 2, 3]) + def get_output(self, session, params=None): + """get_output(channel) + + **Task** - Check if channel ouput is enabled or disabled. + + Parameters: + channel (int): + """ + enabled = False + with self.lock.acquire_timeout(1) as acquired: + if acquired: + enabled = self.psu.get_output(params['channel']) + else: + return False, "Could not acquire lock." + if enabled: + return True, 'Channel {} output is currently enabled.'.format(params['channel']) + else: + return True, 'Channel {} output is currently disabled.'.format(params['channel']) + @ocs_agent.param('channel', type=int, choices=[1, 2, 3]) @ocs_agent.param('state', type=bool) def set_output(self, session, params=None): @@ -235,6 +304,11 @@ def main(args=None): agent.register_task('set_current', p.set_current) agent.register_task('set_output', p.set_output) + # Need tasks for get_voltage, get_current, and get_output + agent.register_task('get_voltage', p.get_voltage) + agent.register_task('get_current', p.get_current) + agent.register_task('get_output', p.get_output) + agent.register_process('monitor_output', p.monitor_output, p.stop_monitoring) runner.run(agent, auto_reconnect=True) From fa297e7864071e156270cc9284dcd3dcd7cb8317 Mon Sep 17 00:00:00 2001 From: texmexlab Date: Sun, 25 Aug 2024 16:19:55 -0500 Subject: [PATCH 10/22] Making initial round of changes based on responses to PR #715 --- requirements.txt | 11 --- socs/agents/scpi_psu/agent.py | 120 +++++++++++++++++++++++--------- socs/agents/scpi_psu/drivers.py | 35 ++++++---- 3 files changed, 110 insertions(+), 56 deletions(-) diff --git a/requirements.txt b/requirements.txt index abb0a57bc..75825d227 100644 --- a/requirements.txt +++ b/requirements.txt @@ -63,14 +63,3 @@ pyModbusTCP # Contributing pre-commit - -# Ensure compatability with Setuptools 71 -packaging>=24 -ordered-set>=3.1.1 -more_itertools>=8.8 -jaraco.text>=3.7 -importlib_resources>=5.10.2 -importlib_metadata>=6 -tomli>=2.0.1 -wheel>=0.43.0 -platformdirs>=2.6.2 diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index e4f4d4c52..b2a6ee1ea 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -9,21 +9,24 @@ class ScpiPsuAgent: - def __init__(self, agent, ip_address, gpib_slot, **kwargs): + def __init__(self, agent, ip_address, gpib_slot=None, port=None): self.agent = agent self.log = agent.log self.lock = TimeoutLock() self.job = None self.ip_address = ip_address - self.gpib_slot = gpib_slot + self.gpib_slot = None self.port = None - if ('port' in kwargs.keys()): - self.port = kwargs['port'] self.monitor = False self.psu = None + if gpib_slot is not None: + self.gpib_slot = gpib_slot + if port is not None: + self.port = port + # Registers Temperature and Voltage feeds agg_params = { 'frame_length': 10 * 60, @@ -33,7 +36,7 @@ def __init__(self, agent, ip_address, gpib_slot, **kwargs): agg_params=agg_params, buffer_time=0) - # @ocs_agent.param('_') + @ocs_agent.param('auto_acquire', default=False, type=bool) def init(self, session, params=None): """init() @@ -44,7 +47,10 @@ def init(self, session, params=None): if not acquired: return False, "Could not acquire lock" - if self.port is None: # Use the old Prologix-based GPIB code + if self.gpib_slot is None and self.port is None: + self.log.error('Either --gpib-slot or --port must be specified') + return False, "Parameter not set" + elif self.port is None: # Use the old Prologix-based GPIB code try: self.psu = PsuInterface(self.ip_address, self.gpib_slot) self.idn = self.psu.identify() @@ -53,17 +59,20 @@ def init(self, session, params=None): return False, "Timeout" else: # Use the new direct ethernet connection code try: - self.psu = ScpiPsuInterface(self.ip_address, self.gpib_slot, port=self.port) + self.psu = ScpiPsuInterface(self.ip_address, port=self.port) self.idn = self.psu.identify() except socket.timeout as e: self.log.error(f"PSU timed out during connect: {e}") return False, "Timeout" except ValueError as e: if (e.args[0].startswith('Model number')): - self.log.error(f"PSU initialization error: {e}. Suggest appending {e.args[-1]} to the list of known model numbers in scpi_psu/drivers.py") + self.log.warn(f"PSU initialization: {e}. \ + Number of channels defaults to 3. \ + Suggest appending {e.args[-1]} to the list \ + of known model numbers in scpi_psu/drivers.py") else: self.log.error(f"PSU initialization resulted in unknown ValueError: {e}") - return False, "ValueError" + return False, "ValueError" self.log.info("Connected to psu: {}".format(self.idn)) @@ -72,7 +81,7 @@ def init(self, session, params=None): if auto_acquire: acq_params = None - if self.psu.numChannels == 1: + if self.psu.num_channels == 1: acq_params = {'channels': [1]} self.agent.start('monitor_output', acq_params) return True, 'Initialized PSU.' @@ -94,7 +103,6 @@ def monitor_output(self, session, params=None): Defaults to False. """ - session.set_status('running') self.monitor = True while self.monitor: @@ -145,18 +153,33 @@ def get_voltage(self, session, params=None): Parameters: channel (int): Channel number (1, 2, or 3). + Examples: + Example for calling in a client:: + + client.get_voltage(channel=1) + + Notes: + An example of the session data:: + + >>> response.session['data'] + {'timestamp': 1723671503.4899583, + 'channel': 1, + 'voltage': 0.0512836} + """ chan = params['channel'] with self.lock.acquire_timeout(1) as acquired: if acquired: - data = { - 'timestamp': time.time(), - 'block_name': 'output', - 'data': {} - } - - data['data']['Voltage_{}'.format(chan)] = self.psu.get_volt(chan) - session.data = data + if self.psu.get_output(chan): + data = { + 'timestamp': time.time(), + 'channel': chan, + 'voltage': self.psu.get_volt(chan) + } + + session.data = data + else: + return False, "Cannot measure output when output is disabled." else: return False, "Could not acquire lock" return True, 'Channel {} voltage measured'.format(chan) @@ -191,17 +214,33 @@ def get_current(self, session, params=None): Parameters: channel (int): Channel number (1, 2, or 3). + Examples: + Example for calling in a client:: + + client.get_current(channel=1) + + Notes: + An example of the session data:: + + >>> response.session['data'] + {'timestamp': 1723671503.4899583, + 'channel' : 1, + 'current': 0.0103236} + """ chan = params['channel'] with self.lock.acquire_timeout(1) as acquired: if acquired: - data = { - 'timestamp': time.time(), - 'block_name': 'output', - 'data': {} - } - data['data']['Current_{}'.format(chan)] = self.psu.get_curr(chan) - session.data = data + # Check if output is enabled before measuring + if self.psu.get_output(chan): + data = { + 'timestamp': time.time(), + 'channel': chan, + 'current': self.psu.get_curr(chan) + } + session.data = data + else: + return False, "Cannot measure output when output is disabled." else: return False, "Could not acquire lock" return True, 'Channel {} current measured'.format(chan) @@ -233,18 +272,38 @@ def get_output(self, session, params=None): **Task** - Check if channel ouput is enabled or disabled. Parameters: - channel (int): + channel (int): Channel number (1, 2, or 3). + + Examples: + Example for calling in a client:: + + client.get_output(channel=1) + + Notes: + An example of the session data:: + + >>> response.session['data'] + {'timestamp': 1723671503.4899583, + 'channel_1': 'enabled'} + """ + chan = params['channel'] enabled = False + data = {} with self.lock.acquire_timeout(1) as acquired: if acquired: - enabled = self.psu.get_output(params['channel']) + data['timestamp'] = time.time() + enabled = self.psu.get_output(chan) else: return False, "Could not acquire lock." if enabled: - return True, 'Channel {} output is currently enabled.'.format(params['channel']) + data['channel_{}'.format(chan)] = 'enabled' + session.data = data + return True, 'Channel {} output is currently enabled.'.format(chan) else: - return True, 'Channel {} output is currently disabled.'.format(params['channel']) + data['channel_{}'.format(chan)] = 'disabled' + session.data = data + return True, 'Channel {} output is currently disabled.'.format(chan) @ocs_agent.param('channel', type=int, choices=[1, 2, 3]) @ocs_agent.param('state', type=bool) @@ -304,7 +363,6 @@ def main(args=None): agent.register_task('set_current', p.set_current) agent.register_task('set_output', p.set_output) - # Need tasks for get_voltage, get_current, and get_output agent.register_task('get_voltage', p.get_voltage) agent.register_task('get_current', p.get_current) agent.register_task('get_output', p.get_output) diff --git a/socs/agents/scpi_psu/drivers.py b/socs/agents/scpi_psu/drivers.py index d2432d41b..72ade060b 100644 --- a/socs/agents/scpi_psu/drivers.py +++ b/socs/agents/scpi_psu/drivers.py @@ -6,18 +6,21 @@ from socs.common.prologix_interface import PrologixInterface # append new model strings as needed -ONE_CHANNEL_MODELS = ['2280S-60-3', '2280S-32-6'] +ONE_CHANNEL_MODELS = ['2280S-60-3', '2280S-32-6', '9171', + '9172', '9181', '9182', '9183', '9184', '9185'] +TWO_CHANNEL_MODELS = ['9173', '9174'] THREE_CHANNEL_MODELS = ['2230G-30-1'] +UNDEFINED_HEADER = -113 +HEADER_SUFFIX_OUT_OF_RANGE = -114 class ScpiPsuInterface: - def __init__(self, ip_address, gpibAddr, port, **kwargs): + def __init__(self, ip_address, port, **kwargs): self.ip_address = ip_address - self.gpibAddr = gpibAddr self.port = port self.sock = None self.model = None - self.numChannels = 0 + self.num_channels = 0 self.conn_socket() try: self.configure() @@ -51,11 +54,15 @@ def read_model(self): def configure(self): self.model = self.read_model() + if (self.model in ONE_CHANNEL_MODELS): - self.numChannels = 1 + self.num_channels = 1 + if (self.model in TWO_CHANNEL_MODELS): + self.num_channels = 2 if (self.model in THREE_CHANNEL_MODELS): - self.numChannels = 3 - if (self.numChannels == 0): + self.num_channels = 3 + if (self.num_channels == 0): + self.num_channels = 3 raise ValueError('Model number not found in known device models', self.model) def enable(self, ch): @@ -64,9 +71,9 @@ def enable(self, ch): Depending on state of power supply, it might need to be called before the output is set. ''' - if (self.numChannels != 1): + if (self.num_channels != 1): self.set_chan(ch) - self.write('OUTP:ENAB ON') + self.write('OUTP:ENAB ON') def disable(self, ch): ''' @@ -76,7 +83,7 @@ def disable(self, ch): self.write('OUTP:ENAB OFF') def set_chan(self, ch): - if (self.numChannels != 1): + if (self.num_channels != 1): self.write('inst:nsel ' + str(ch)) def set_output(self, ch, out): @@ -92,7 +99,7 @@ def set_output(self, ch, out): ''' self.set_chan(ch) self.enable(ch) - if (self.numChannels != 1): + if (self.num_channels != 1): if isinstance(out, str): self.write('CHAN:OUTP ' + out) elif out: @@ -112,7 +119,7 @@ def get_output(self, ch): check if the output of a channel (1,2,3) is on (True) or off (False) ''' self.set_chan(ch) - if (self.numChannels != 1): + if (self.num_channels != 1): self.write('CHAN:OUTP:STAT?') else: self.write('OUTP:STAT?') @@ -129,7 +136,7 @@ def set_curr(self, ch, curr): def get_volt(self, ch): self.set_chan(ch) - if (self.numChannels != 1): + if (self.num_channels != 1): self.write('MEAS:VOLT? CH' + str(ch)) else: self.write('MEAS:VOLT?') @@ -138,7 +145,7 @@ def get_volt(self, ch): def get_curr(self, ch): self.set_chan(ch) - if (self.numChannels != 1): + if (self.num_channels != 1): self.write('MEAS:CURR? CH' + str(ch)) else: self.write('MEAS:CURR?') From b42e7a12ae82f370662da0b4bbea466d76b929c4 Mon Sep 17 00:00:00 2001 From: texmexlab Date: Sun, 25 Aug 2024 16:30:03 -0500 Subject: [PATCH 11/22] Adding comments to explain the existence of the error code definitions for the 2280S power supplies. --- socs/agents/scpi_psu/drivers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/socs/agents/scpi_psu/drivers.py b/socs/agents/scpi_psu/drivers.py index 72ade060b..94b7268f0 100644 --- a/socs/agents/scpi_psu/drivers.py +++ b/socs/agents/scpi_psu/drivers.py @@ -10,6 +10,10 @@ '9172', '9181', '9182', '9183', '9184', '9185'] TWO_CHANNEL_MODELS = ['9173', '9174'] THREE_CHANNEL_MODELS = ['2230G-30-1'] + +# error codes from 2280S devices +# part of an attempt to query devices for the number +# of supported channels UNDEFINED_HEADER = -113 HEADER_SUFFIX_OUT_OF_RANGE = -114 From 46b97c513875ee33b81d0b6b24300a5c792a050d Mon Sep 17 00:00:00 2001 From: texmexlab Date: Tue, 10 Sep 2024 11:09:06 -0500 Subject: [PATCH 12/22] Changing output of get_output() to match the other get* functions. --- socs/agents/scpi_psu/agent.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index b2a6ee1ea..a54d81026 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -284,7 +284,8 @@ def get_output(self, session, params=None): >>> response.session['data'] {'timestamp': 1723671503.4899583, - 'channel_1': 'enabled'} + 'channel': 1, + 'state': 1} """ chan = params['channel'] @@ -292,17 +293,17 @@ def get_output(self, session, params=None): data = {} with self.lock.acquire_timeout(1) as acquired: if acquired: - data['timestamp'] = time.time() - enabled = self.psu.get_output(chan) + data = { + 'timestamp': time.time(), + 'channel': chan, + 'state': self.psu.get_output(chan) + } + session.data = data else: return False, "Could not acquire lock." if enabled: - data['channel_{}'.format(chan)] = 'enabled' - session.data = data return True, 'Channel {} output is currently enabled.'.format(chan) else: - data['channel_{}'.format(chan)] = 'disabled' - session.data = data return True, 'Channel {} output is currently disabled.'.format(chan) @ocs_agent.param('channel', type=int, choices=[1, 2, 3]) From 1e0b56ac699a5d92c64db137db4df7c5022598a4 Mon Sep 17 00:00:00 2001 From: texmexlab Date: Tue, 10 Sep 2024 13:20:57 -0500 Subject: [PATCH 13/22] Fixing issue with call to _initialize_module() in init timing out due to multiple connection attempts. --- socs/agents/scpi_psu/agent.py | 8 ++++---- socs/agents/scpi_psu/drivers.py | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index be0f48296..44a74fae3 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -85,17 +85,17 @@ def init(self, session, params=None): if self.psu.num_channels == 1: acq_params = {'channels': [1]} self.agent.start('monitor_output', acq_params) - while not self._initialize_module(): - time.sleep(5) + # while not self._initialize_module(): + # time.sleep(5) return True, 'Initialized PSU.' def _initialize_module(self): """Initialize the ScpiPsu module.""" try: if self.port is None: - self.psu = PsuInterface(self.ip_address, self.gpib_slot) + self.psu = PsuInterface(self.ip_address, self.gpib_slot) else: - self.psu = ScpiPsuInterface(self.ip_address, port=self.port) + self.psu = ScpiPsuInterface(self.ip_address, port=self.port) except (socket.timeout, OSError) as e: self.log.warn(f"Error establishing connection: {e}") self.psu = None diff --git a/socs/agents/scpi_psu/drivers.py b/socs/agents/scpi_psu/drivers.py index 69ec188c6..1457b96b3 100644 --- a/socs/agents/scpi_psu/drivers.py +++ b/socs/agents/scpi_psu/drivers.py @@ -156,6 +156,9 @@ def get_curr(self, ch): current = float(self.read().split(',')[0].strip('A')) return current + def clear(self): + return True + class PsuInterface(PrologixInterface): def __init__(self, ip_address, gpibAddr, verbose=False, **kwargs): From 4d14a277391d8c062ef4365dc8e0a9a541e71b4a Mon Sep 17 00:00:00 2001 From: texmexlab Date: Fri, 13 Sep 2024 22:30:18 -0500 Subject: [PATCH 14/22] Removing the call to super().__init__(**kwargs) from the ScpiPsuInterface constructor. --- socs/agents/scpi_psu/drivers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/socs/agents/scpi_psu/drivers.py b/socs/agents/scpi_psu/drivers.py index 1457b96b3..54c3e3fc4 100644 --- a/socs/agents/scpi_psu/drivers.py +++ b/socs/agents/scpi_psu/drivers.py @@ -30,7 +30,6 @@ def __init__(self, ip_address, port, **kwargs): self.configure() except ValueError as err: raise ValueError(err) - super().__init__(**kwargs) def conn_socket(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) From de2911c8657395f0e3b6afafb60e549d15d04ca4 Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Mon, 16 Sep 2024 14:54:48 -0400 Subject: [PATCH 15/22] Start PSU tests in 'init' mode This was the expected mode before 'acq' became the default. Fixes an issue with the init test. --- tests/default.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/default.yaml b/tests/default.yaml index 82c39d69d..cc4ab99d9 100644 --- a/tests/default.yaml +++ b/tests/default.yaml @@ -61,7 +61,8 @@ hosts: {'agent-class': 'ScpiPsuAgent', 'instance-id': 'psuK', 'arguments': [['--ip-address', '127.0.0.1'], - ['--gpib-slot', '1'] + ['--gpib-slot', '1'], + ['--mode', 'init'] ] }, {'agent-class': 'PfeifferAgent', From 1f1d2506284a7b1fbd7645255fed72a38d722d36 Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Mon, 16 Sep 2024 15:17:01 -0400 Subject: [PATCH 16/22] Add expected response to channel enabled query in test --- tests/integration/test_scpi_psu_agent_integration.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/test_scpi_psu_agent_integration.py b/tests/integration/test_scpi_psu_agent_integration.py index 318881b3d..ee1c64581 100644 --- a/tests/integration/test_scpi_psu_agent_integration.py +++ b/tests/integration/test_scpi_psu_agent_integration.py @@ -64,6 +64,7 @@ def test_scpi_psu_set_voltage(wait_for_crossbar, gpib_emu, run_agent, client): @pytest.mark.integtest def test_scpi_psu_monitor_output(wait_for_crossbar, gpib_emu, run_agent, client): responses = { + "CHAN:OUTP:STAT?": "1", "MEAS:VOLT? CH1": "3.14", "MEAS:CURR? CH1": "6.28", "MEAS:VOLT? CH2": "2.72", From b6035244bb3f054f42a91d53fc92289f005d26ad Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Mon, 16 Sep 2024 15:17:50 -0400 Subject: [PATCH 17/22] Add ability to set log level via env var --- socs/agents/scpi_psu/agent.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index 44a74fae3..82c8dbce4 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -1,13 +1,18 @@ import argparse +import os import socket import time from typing import Optional +import txaio from ocs import ocs_agent, site_config from ocs.ocs_twisted import TimeoutLock from socs.agents.scpi_psu.drivers import PsuInterface, ScpiPsuInterface +# For logging +txaio.use_twisted() + class ScpiPsuAgent: def __init__(self, agent, ip_address, gpib_slot=None, port=None): @@ -375,6 +380,9 @@ def make_parser(parser=None): def main(args=None): + # Start logging + txaio.start_logging(level=os.environ.get("LOGLEVEL", "info")) + parser = make_parser() args = site_config.parse_args(agent_class='ScpiPsuAgent', parser=parser, From c92b751b56b82d2ba3c16e08203974048111db8d Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Mon, 16 Sep 2024 15:20:16 -0400 Subject: [PATCH 18/22] Abort monitor process after test This ends up hanging on the auto-reconnection otherwise. This was a bit unexpected, as test_mode should break us out before another communication. --- tests/integration/test_scpi_psu_agent_integration.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/integration/test_scpi_psu_agent_integration.py b/tests/integration/test_scpi_psu_agent_integration.py index ee1c64581..b3b970ecc 100644 --- a/tests/integration/test_scpi_psu_agent_integration.py +++ b/tests/integration/test_scpi_psu_agent_integration.py @@ -78,3 +78,7 @@ def test_scpi_psu_monitor_output(wait_for_crossbar, gpib_emu, run_agent, client) resp = client.monitor_output.start(test_mode=True, wait=0) resp = client.monitor_output.wait() check_resp_success(resp) + + # stop process, else test hangs on auto-reconnection + client.monitor_output.stop() + client.monitor_output.wait() From 6ec40a6076379969a35af4ae26b6eb06948b0729 Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Mon, 16 Sep 2024 15:31:54 -0400 Subject: [PATCH 19/22] Remove reundant session message This makes it look like initialize happened twice, since the message included in the return is also logged in the same place. --- socs/agents/scpi_psu/agent.py | 1 - 1 file changed, 1 deletion(-) diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index 82c8dbce4..d8731ae05 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -82,7 +82,6 @@ def init(self, session, params=None): self.log.info("Connected to psu: {}".format(self.idn)) - session.add_message('Initialized PSU.') auto_acquire = params.get('auto_acquire', False) if auto_acquire: From e15cedf2b7f640330bd4f59007b5e0ba9a6e5160 Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Mon, 16 Sep 2024 15:33:03 -0400 Subject: [PATCH 20/22] Add test for initializing in 'acq' mode --- tests/integration/test_scpi_psu_agent_integration.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/integration/test_scpi_psu_agent_integration.py b/tests/integration/test_scpi_psu_agent_integration.py index b3b970ecc..e076d6779 100644 --- a/tests/integration/test_scpi_psu_agent_integration.py +++ b/tests/integration/test_scpi_psu_agent_integration.py @@ -13,6 +13,12 @@ "scpi_psu_agent", args=["--log-dir", "./logs/"], ) +run_agent_acq = create_agent_runner_fixture( + "../socs/agents/scpi_psu/agent.py", + "scpi_psu_agent", + args=["--log-dir", "./logs/", + "--mode", "acq"], +) client = create_client_fixture("psuK") gpib_emu = create_device_emulator( { @@ -37,6 +43,12 @@ def test_scpi_psu_init_psu(wait_for_crossbar, gpib_emu, run_agent, client): check_resp_success(resp) +@pytest.mark.integtest +def test_scpi_psu_init_psu_acq_mode(wait_for_crossbar, gpib_emu, run_agent_acq, client): + resp = client.init.status() + check_resp_success(resp) + + @pytest.mark.integtest def test_scpi_psu_set_output(wait_for_crossbar, gpib_emu, run_agent, client): client.init() From 0d13b3dc6903ca3b31d380ac5ab76ffe078b52bd Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Mon, 16 Sep 2024 15:38:02 -0400 Subject: [PATCH 21/22] Remove commented initialization loop This was commented because it was causing issues, but we should just drop it altogether. It's better that 'init' doesn't loop forever until a connection is made. Rather it should fail and report as failed. --- socs/agents/scpi_psu/agent.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index d8731ae05..5c8084e74 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -89,8 +89,6 @@ def init(self, session, params=None): if self.psu.num_channels == 1: acq_params = {'channels': [1]} self.agent.start('monitor_output', acq_params) - # while not self._initialize_module(): - # time.sleep(5) return True, 'Initialized PSU.' def _initialize_module(self): From 1e4467f247e68a6110d53c4c6d3b9ee59b1d3a6f Mon Sep 17 00:00:00 2001 From: Brian Koopman Date: Mon, 16 Sep 2024 15:39:50 -0400 Subject: [PATCH 22/22] Add num_channels to PsuInterface This was missing, but needed since it's checked in init(). This is maybe a bit of a hack, but by setting to 0 we skip setting 'acq_params' and end running with the default three channel query given by the the default 'channels' on 'monitor_output()'. This is the old behavior before the addition of 'ScpiPsuInterface'. --- socs/agents/scpi_psu/agent.py | 4 ++-- socs/agents/scpi_psu/drivers.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/socs/agents/scpi_psu/agent.py b/socs/agents/scpi_psu/agent.py index 5c8084e74..3f1d007cf 100644 --- a/socs/agents/scpi_psu/agent.py +++ b/socs/agents/scpi_psu/agent.py @@ -86,8 +86,8 @@ def init(self, session, params=None): if auto_acquire: acq_params = None - if self.psu.num_channels == 1: - acq_params = {'channels': [1]} + if self.psu.num_channels != 0: + acq_params = {'channels': [i + 1 for i in range(self.psu.num_channels)]} self.agent.start('monitor_output', acq_params) return True, 'Initialized PSU.' diff --git a/socs/agents/scpi_psu/drivers.py b/socs/agents/scpi_psu/drivers.py index 54c3e3fc4..601234c32 100644 --- a/socs/agents/scpi_psu/drivers.py +++ b/socs/agents/scpi_psu/drivers.py @@ -162,6 +162,7 @@ def clear(self): class PsuInterface(PrologixInterface): def __init__(self, ip_address, gpibAddr, verbose=False, **kwargs): self.verbose = verbose + self.num_channels = 0 super().__init__(ip_address, gpibAddr, **kwargs) def enable(self, ch):