Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hwp_pcu agent for the hwp phase compensation unit operations #588

Merged
merged 12 commits into from
Dec 18, 2023
Empty file added socs/agents/hwp_pcu/__init__.py
Empty file.
245 changes: 245 additions & 0 deletions socs/agents/hwp_pcu/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
import argparse
import time

from ocs import ocs_agent, site_config
from ocs.ocs_twisted import TimeoutLock
from twisted.internet import reactor

import socs.agents.hwp_pcu.drivers.hwp_pcu as pcu


class HWPPCUAgent:
"""Agent to phase compensation improve the CHWP motor efficiency

Args:
agent (ocs.ocs_agent.OCSAgent): Instantiated OCSAgent class for this agent
port (str): Path to USB device in '/dev/'

"""

def __init__(self, agent, port):
self.agent: ocs_agent.OCSAgent = agent
self.log = agent.log
self.lock = TimeoutLock()
self.initialized = False
self.take_data = False
self.port = port
self.status = 'off'

agg_params = {'frame_length': 60}
self.agent.register_feed(
'hwppcu', record=True, agg_params=agg_params)

@ocs_agent.param('auto_acquire', default=False, type=bool)
@ocs_agent.param('force', default=False, type=bool)
def init_connection(self, session, params):
"""init_connection(auto_acquire=False, force=False)

**Task** - Initialize connection to PCU
Controller.

Parameters:
auto_acquire (bool, optional): Default is False. Starts data
acquisition after initialization if True.
force (bool, optional): Force initialization, even if already
initialized. Defaults to False.

"""
if self.initialized and not params['force']:
self.log.info("Connection already initialized. Returning...")
return True, "Connection already initialized"

with self.lock.acquire_timeout(0, job='init_connection') as acquired:
if not acquired:
self.log.warn(
'Could not run init_connection because {} is already running'.format(self.lock.job))
return False, 'Could not acquire lock'

try:
self.PCU = pcu.PCU(port=self.port)
self.log.info('Connected to PCU')
except BrokenPipeError:
self.log.error('Could not establish connection to PCU')
reactor.callFromThread(reactor.stop)
return False, 'Unable to connect to PCU'
self.status = self.PCU.get_status()
self.initialized = True

# Start 'acq' Process if requested
if params['auto_acquire']:
self.agent.start('acq')

return True, 'Connection to PCU established'

@ocs_agent.param('command', default='off', type=str)
ykyohei marked this conversation as resolved.
Show resolved Hide resolved
def send_command(self, session, params):
"""send_command(command)

**Task** - Send commands to the phase compensation unit.
off: The compensation phase is zero.
on_1:The compensation phase is +120 deg.
Copy link
Contributor

@ykyohei ykyohei Dec 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we change from 'on_1' to '+120deg', 'on_2' to '-120deg'?
Is it not allowed in python?

At least I would like to change the status message.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be fine as it is because "on_1" does not necessarily mean +120 deg. We intend to include the hwp_pcu in the hwp_supervisor, so more accurate compensated angle can be described in hwp_supervisor.

on_2: The compensation phase is -120 deg.
hold: Stop the HWP spin.

Parameters:
command (str): set the operation mode from 'off', 'on_1', 'on_2' or 'hold'.

"""
with self.lock.acquire_timeout(3, job='send_command') as acquired:
if not acquired:
self.log.warn('Could not send command because {} is already running'.format(self.lock.job))
return False, 'Could not acquire lock'

command = params['command']
if command == 'off':
off_channel = [0, 1, 2, 5, 6, 7]
for i in off_channel:
self.PCU.relay_off(i)
self.status = 'off'
msg = 'Phase compensation is "off".'
return True, msg

elif command == 'on_1':
on_channel = [0, 1, 2]
off_channel = [5, 6, 7]
for i in on_channel:
self.PCU.relay_on(i)
for i in off_channel:
self.PCU.relay_off(i)
self.status = 'on_1'
msg = 'Phase compensation operates "on_1".'
return True, msg

elif command == 'on_2':
on_channel = [0, 1, 2, 5, 6, 7]
for i in on_channel:
self.PCU.relay_on(i)
self.status = 'on_2'
msg = 'Phase compensation operates "on_2".'
return True, msg

elif command == 'hold':
on_channel = [0, 1, 2, 5]
off_channel = [6, 7]
for i in on_channel:
self.PCU.relay_on(i)
for i in off_channel:
self.PCU.relay_off(i)
self.status = 'hold'
msg = 'Phase compensation operates "hold".'
return True, msg
ykyohei marked this conversation as resolved.
Show resolved Hide resolved

else:
print("Choose the command from 'off', 'on_1', 'on_2' and 'hold'.")

def get_status(self, session, params):
self.status = self.PCU.get_status()
ykyohei marked this conversation as resolved.
Show resolved Hide resolved
msg = 'Current status is ' + self.status
return True, msg

def acq(self, session, params):
"""acq()

**Process** - Start PCU data acquisition.

Notes:
The most recent data collected is stored in the session data in the
structure::

>>> response.session['data']
{'status': 'on_1',
'last_updated': 1649085992.719602}

"""
with self.lock.acquire_timeout(timeout=0, job='acq') as acquired:
ykyohei marked this conversation as resolved.
Show resolved Hide resolved
if not acquired:
self.log.warn('Could not start pcu acq because {} is already running'
.format(self.lock.job))
return False, 'Could not acquire lock'

session.set_status('running')
last_release = time.time()
self.take_data = True

while self.take_data:
# Relinquish sampling lock occasionally.
if time.time() - last_release > 1.:
last_release = time.time()
if not self.lock.release_and_acquire(timeout=10):
self.log.warn(f"Failed to re-acquire sampling lock, "
f"currently held by {self.lock.job}.")
continue

data = {'timestamp': time.time(),
'block_name': 'hwppcu', 'data': {}}

# status = self.PCU.get_status()
status = self.status
data['data']['status'] = status

self.agent.publish_to_feed('hwppcu', data)

session.data = {'status': status,
'last_updated': time.time()}

time.sleep(1)
ykyohei marked this conversation as resolved.
Show resolved Hide resolved

self.agent.feeds['hwppcu'].flush_buffer()
return True, 'Acqusition exited cleanly'

def _stop_acq(self, session, params):
"""
Stop acq process.
"""
if self.take_data:
self.PCU.close()
self.take_data = False
return True, 'requested to stop taking data'

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
baised 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('--port')
pgroup.add_argument('--mode', type=str, default='acq',
choices=['init', 'acq'],
help="Starting operation for the Agent.")
return parser


def main(args=None):
parser = make_parser()
args = site_config.parse_args(agent_class='HWPPCUAgent',
parser=parser,
args=args)

init_params = False
if args.mode == 'init':
init_params = {'auto_acquire': False}
elif args.mode == 'acq':
init_params = {'auto_acquire': True}

agent, runner = ocs_agent.init_site_agent(args)
hwppcu_agent = HWPPCUAgent(agent,
port=args.port)
agent.register_task('init_connection', hwppcu_agent.init_connection,
startup=init_params)
agent.register_process('acq', hwppcu_agent.acq,
hwppcu_agent._stop_acq)
agent.register_task('send_command', hwppcu_agent.send_command)
agent.register_task('get_status', hwppcu_agent.get_status)

runner.run(agent, auto_reconnect=True)


if __name__ == '__main__':
main()
Empty file.
73 changes: 73 additions & 0 deletions socs/agents/hwp_pcu/drivers/hwp_pcu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import time

import serial


class PCU:
"""Class to communicate with the phase compensation unit.

Args:
port (str): Path to USB device in '/dev/'

Attributes:
status (str): The status of the unit (off/on_1/on_2/hold)
"""

def __init__(self, port):
self.port = serial.Serial(
port,
baudrate=19200,
timeout=1
ykyohei marked this conversation as resolved.
Show resolved Hide resolved
)

def close(self):
self.port.close()

def relay_on(self, channel):
cmd = "relay on " + str(channel) + "\n\r"
self.port.write(cmd.encode('utf-8'))
time.sleep(.1)

def relay_off(self, channel):
cmd = "relay off " + str(channel) + "\n\r"
self.port.write(cmd.encode('utf-8'))
time.sleep(.1)

def relay_read(self, channel):
cmd = "relay read " + str(channel) + "\n\r"
self.port.write(cmd.encode('utf-8'))
time.sleep(.1)
response = self.port.read(25)
time.sleep(.1)
response = response.decode('utf-8')
if response.find("on") > 0:
return True
elif response.find("off") > 0:
return False
else:
return -1

def get_status(self):
"""get_status()

**Task** - Get the operation mode of the phase compensation unit.
off: The compensation phase is zero.
on_1:The compensation phase is +120 deg.
ykyohei marked this conversation as resolved.
Show resolved Hide resolved
on_2: The compensation phase is -120 deg.
hold: Stop the HWP spin.
"""
channel = [0, 1, 2, 5, 6, 7]
channel_switch = []

for i in channel:
channel_switch.append(self.relay_read(i))
if channel_switch == [False, False, False, False, False, False]:
return 'off'
elif channel_switch == [True, True, True, False, False, False]:
return 'on_1'
elif channel_switch == [True, True, True, True, True, True]:
return 'on_2'
elif channel_switch == [True, True, True, True, False, False]:
return 'hold'
else:
return 'undefined'