import argparse
import time
import numpy as np
from ocs import ocs_agent, site_config
from ocs.ocs_client import OCSClient
from ocs.ocs_twisted import TimeoutLock
from socs.agents.wiregrid_kikusui.drivers.common import openlog, writelog
from socs.common import pmx
[docs]
class WiregridKikusuiAgent:
"""Agent to control the wire-grid rotation
The KIKUSUI is a power supply and
it is controlled via serial-to-ethernet converter.
The converter is linked to the KIKUSUI
via RS-232 (D-sub 9pin cable).
The agent communicates with the converter via eternet.
Args:
kikusui_ip (str): IP address of the serial-to-ethernet converter
kikusui_port (int or str): Asigned port for the KIKUSUI power supply
The converter has four D-sub ports to control
multiple devices connected via serial communication.
Communicating device is determined
by the ethernet port number of the converter.
encoder_agent (str): Instance ID of the wiregrid encoder agent
debug (bool): ON/OFF of writing a log file
"""
def __init__(self, agent, kikusui_ip, kikusui_port,
encoder_agent='wgencoder', debug=False):
self.agent = agent
self.log = agent.log
self.lock = TimeoutLock()
self.take_data = False
self.kikusui_ip = kikusui_ip
self.kikusui_port = int(kikusui_port)
self.encoder_agent = encoder_agent
self.debug = debug
self.debug_log_path = '/data/wg-data/action/'
self.open_trial = 10
self.Deg = 360 / 52000
# self.feedback_time = [0.151, 0.241, 0.271, 0.301, 0.331]
self.feedback_time = [0.151, 0.241, 0.271, 0.361, 0.451]
self.feedback_cut = [1., 2., 3., 5.0, 8.0]
self.feedback_steps = 8
self.num_laps = 2
self.stopped_time = 10
self.agent_interval = 0.1
agg_params = {'frame_length': 60}
self.agent.register_feed(
'kikusui_psu', record=True, agg_params=agg_params)
try:
self.PMX = pmx.PMX(
tcp_ip=self.kikusui_ip,
tcp_port=self.kikusui_port,
timeout=0.5)
except Exception as e:
self.log.warn(
'Could not connect to serial converter! | Error = "%s"' % e)
self.PMX = None
if self.PMX is not None:
self.cmd = pmx.Command(self.PMX)
else:
self.cmd = None
# Connect to the encoder agent
self.encoder_clident = None
self._connect_encoder()
######################
# Internal functions #
######################
def _check_connect(self):
if self.PMX is None:
msg = 'No connection to the KIKUSUI power supply. | '\
'Error = "PMX is None"'
self.log.warn(msg)
return False, msg
else:
msg, ret = self.PMX.check_connect()
if not ret:
msg = 'No connection to the KIKUSUI power supply. | '\
'Error = "%s"' % msg
self.log.warn(msg)
return False, msg
return True, 'Connection is OK.'
def _reconnect(self):
self.log.warn('Trying to reconnect...')
# reconnect
try:
if self.PMX:
del self.PMX
if self.cmd:
del self.cmd
self.PMX = pmx.PMX(
tcp_ip=self.kikusui_ip,
tcp_port=self.kikusui_port,
timeout=0.5)
except Exception as e:
msg = 'Could not reconnect to the KIKUSUI power supply! | '\
'Error: %s' % e
self.log.warn(msg)
self.PMX = None
self.cmd = None
return False, msg
# reinitialize cmd
self.cmd = pmx.Command(self.PMX)
ret, msg = self._check_connect()
if ret:
msg = 'Successfully reconnected to the KIKUSUI power supply!'
self.log.info(msg)
return True, msg
else:
msg = 'Failed to reconnect to the KIKUSUI power supply!'
self.log.warn(msg)
if self.PMX:
del self.PMX
if self.cmd:
del self.cmd
self.PMX = None
self.cmd = None
return False, msg
def _connect_encoder(self):
self.encoder_client = OCSClient(self.encoder_agent)
def _rotate_alittle(self, operation_time):
if operation_time != 0.:
self.cmd.user_input('ON')
time.sleep(operation_time)
self.cmd.user_input('OFF')
time.sleep(self.agent_interval)
return True, 'Successfully rotate a little!'
return True, 'No rotation!'
def _get_position(self):
position = -1.
try:
response = self.encoder_client.acq.status()
except Exception as e:
self.log.warn(
'Failed to get ENCODER POSITION | '
'{}'.format(e)
)
self.log.warn(
' --> Retry to connect to the encoder agent'
)
self._connect_encoder()
try:
response = self.encoder_client.acq.status()
except Exception as e:
self.log.error(
'Failed to get encoder position | '
'{}'.format(e)
)
return -1.
try:
position = (float)(
response.session['data']['fields']['encoder_data']['reference_degree'][-1])
except Exception as e:
self.log.warn(
'Failed to get encoder position | '
'{}'.format(e)
)
return position
def _get_exectime(self, position_difference, feedback_cut, feedback_time):
if position_difference >= feedback_cut[4]:
operation_time = feedback_time[4]
if (feedback_cut[4] > position_difference) &\
(position_difference >= feedback_cut[3]):
operation_time = feedback_time[3]
if (feedback_cut[3] > position_difference) &\
(position_difference >= feedback_cut[2]):
operation_time = feedback_time[2]
if (feedback_cut[2] > position_difference) &\
(position_difference >= feedback_cut[1]):
operation_time = feedback_time[1]
if (feedback_cut[1] > position_difference) &\
(position_difference >= feedback_cut[0]):
operation_time = feedback_time[0]
if feedback_cut[0] > position_difference:
operation_time = 0.
return operation_time
def _move_next(self, logfile, feedback_steps, feedback_time):
wanted_angle = 22.5
uncertaity_cancel = 3
absolute_position = np.arange(0, 360, wanted_angle)
start_position = self._get_position()
if (360 < start_position + uncertaity_cancel):
goal_position = wanted_angle
elif absolute_position[-1] < start_position + uncertaity_cancel:
goal_position = 0
else:
goal_position = min(
absolute_position[np.where(
start_position + uncertaity_cancel < absolute_position)[0]
]
)
if self.debug:
writelog(logfile, 'ON', 0, start_position, 'stepwise')
self._rotate_alittle(feedback_time[-1] + 0.1)
time.sleep(self.agent_interval)
for step in range(feedback_steps):
mid_position = self._get_position()
if goal_position + wanted_angle < mid_position:
operation_time =\
self._get_exectime(
goal_position - (mid_position - 360),
self.feedback_cut,
feedback_time)
else:
operation_time =\
self._get_exectime(
goal_position - mid_position,
self.feedback_cut,
feedback_time)
if operation_time == 0.:
break
self._rotate_alittle(operation_time)
if self.debug:
writelog(logfile, 'OFF', 0,
self._get_position(), 'stepwise')
##################
# Main functions #
##################
[docs]
def set_on(self, session, params=None):
"""set_on()
**Task** - Set output ON.
"""
with self.lock.acquire_timeout(timeout=5, job='set_on') as acquired:
if not acquired:
self.log.warn('Could not set ON because {} is already running'
.format(self.lock.job))
return False, 'Could not acquire lock'
if self.debug:
logfile = openlog(self.debug_log_path)
writelog(logfile, 'ON', 0,
self._get_position(), 'continuous')
logfile.close()
self.cmd.user_input('ON')
return True, 'Set Kikusui on'
[docs]
def set_off(self, session, params=None):
"""set_off()
**Task** - Set output OFF.
"""
with self.lock.acquire_timeout(timeout=5, job='set_off') as acquired:
if not acquired:
self.log.warn('Could not set OFF because {} is already running'
.format(self.lock.job))
return False, 'Could not acquire lock'
if self.debug:
logfile = openlog(self.debug_log_path)
writelog(logfile, 'OFF', 0,
self._get_position(), 'continuous')
logfile.close()
self.cmd.user_input('OFF')
return True, 'Set Kikusui off'
[docs]
@ocs_agent.param('current', default=0., type=float,
check=lambda x: 0.0 <= x <= 4.9)
def set_c(self, session, params):
"""set_c(current=0)
**Task** - Set current [A]
Parameters:
current (float): set current [A] (should be [0.0, 4.9])
"""
current = params.get('current')
with self.lock.acquire_timeout(timeout=5, job='set_c') as acquired:
if not acquired:
self.log.warn(
'Could not run set_c() because {} is already running'
.format(self.lock.job))
return False, 'Could not acquire lock'
self.cmd.user_input('C {}'.format(current))
return True, 'Set Kikusui current to {} A'.format(current)
[docs]
@ocs_agent.param('volt', default=12., type=float,
check=lambda x: 0.0 <= x <= 12.0)
def set_v(self, session, params):
"""set_v*volt=12.)
**Task** - Set voltage [V].
Parameters:
volt: set voltage [V] (Usually 12V / Should be [0.0, 12.0])
"""
volt = params.get('volt')
with self.lock.acquire_timeout(timeout=5, job='set_v') as acquired:
if not acquired:
self.log.warn(
'Could not run set_v() because {} is already running'
.format(self.lock.job))
return False, 'Could not acquire lock'
self.cmd.user_input('V {}'.format(volt))
return True, 'Set Kikusui voltage to {} V'.format(volt)
[docs]
def get_vc(self, session, params=None):
"""get_vc()
**Task** - Show voltage [V], current [A], output on/off.
"""
with self.lock.acquire_timeout(timeout=5, job='get_vc') as acquired:
if not acquired:
self.log.warn(
'Could not run get_vc() because {} is already running'
.format(self.lock.job))
return False, 'Could not acquire lock'
v_val = None
c_val = None
s_val = None
msg = 'Error'
s_msg = 'Error'
# check connection
ret, msg = self._check_connect()
if not ret:
msg = 'Could not get c,v because of failure of connection.'
self.log.warn(msg)
else:
v_val, c_val = self.cmd.user_input('VC?')
s_msg, s_val = self.cmd.user_input('O?')
return True, \
'Get Kikusui voltage / current: {} V / {} A [status={}]'\
.format(v_val, c_val, s_val)
[docs]
def get_angle(self, session, params=None):
"""get_angle()
**Task** - Show wire-grid rotaiton angle [deg].
"""
with self.lock.acquire_timeout(timeout=5, job='get_angle') as acquired:
if not acquired:
self.log.warn(
'Could not run get_angle() because {} is already running'
.format(self.lock.job))
return False, 'Could not acquire lock'
angle = self._get_position()
if angle < 0.:
return False, \
'Could not get the angle of the wire-grid rotation.'
return True, \
'Get wire-grid rotation angle = {} deg'.format(angle)
[docs]
def calibrate_wg(self, session, params=None):
"""calibrate_wg()
**Task** - Run rotation-motor calibration for wire-grid.
"""
with self.lock.acquire_timeout(timeout=5, job='calibrate_wg')\
as acquired:
if not acquired:
self.log.warn('Could not run calibrate_wg() '
'because {} is already running'
.format(self.lock.job))
return False, 'Could not acquire lock'
logfile = openlog(self.debug_log_path)
cycle = 1
for i in range(11):
tperiod = 0.10 + 0.02 * i
for j in range(100):
if j % 20 == 0:
self.log.warn(f'this is {cycle}th time action')
writelog(logfile, 'ON', tperiod,
self._get_position(), 'calibration')
self._rotate_alittle(tperiod)
time.sleep(self.agent_interval + 1.)
writelog(logfile, 'OFF', 0.,
self._get_position(), 'calibration')
writelog(logfile, 'ON', 0.70,
self._get_position(), 'calibration')
self._rotate_alittle(0.70)
time.sleep(self.agent_interval + 1.)
writelog(logfile, 'OFF', 0.,
self._get_position(), 'calibration')
cycle += 1
logfile.close()
return True, \
'Micro step rotation of wire grid finished. '\
'Please calibrate and take feedback params.'
[docs]
def stepwise_rotation(self, session, params=None):
"""stepwise_rotation(feedback_steps=8, num_laps=1, stopped_time=10, \
feedback_time=[0.181, 0.221, 0.251, 0.281, 0.301])
**Task** - Run step-wise rotation for wire-grid calibration. In each
step, seveal small-rotations are performed to rotate 22.5-deg.
Parameters:
feedback_steps (int): Number of small rotations
for each 22.5-deg step.
num_laps (int): Number of laps (revolutions).
stopped_time (float): Stopped time [sec]
for each 22.5-deg step.
feedback_time (list): Calibration constants
for the 22.5-deg rotation.
"""
if params is None:
params = {}
with self.lock.acquire_timeout(timeout=5, job='stepwise_rotation')\
as acquired:
if not acquired:
self.log.warn('Could not run stepwise_rotation() '
'because {} is already running'
.format(self.lock.job))
return False, 'Could not acquire lock'
self.feedback_steps = params.get('feedback_steps', 8)
self.num_laps = params.get('num_laps', 1)
self.stopped_time = params.get('stopped_time', 10)
self.feedback_time = params.get(
'feedback_time', [0.181, 0.221, 0.251, 0.281, 0.301])
if self.debug:
logfile = openlog(self.debug_log_path)
else:
logfile = None
for i in range(int(self.num_laps * 16.)):
self._move_next(
logfile, self.feedback_steps, self.feedback_time)
time.sleep(self.stopped_time)
if self.debug:
logfile.close()
return True, 'Step-wise rotation finished'
[docs]
def IV_acq(self, session, params=None):
"""IV_acq()
**Process** - Run data acquisition.
Notes:
The most recent data collected is stored in session.data in the
structure::
>>> response.session['data']
{'fields':
{
'kikusui':
{'volt': voltage [V],
'curr': current [A],
'voltset': voltage setting [V],
'currset': current setting [A],
'status': output power status 1(on) or 0(off)
}
},
'timestamp':1601925677.6914878
}
"""
# timeout is long because calibrate_wg will take a long time (> hours)
with self.lock.acquire_timeout(timeout=1000, job='IV_acq') as acquired:
if not acquired:
self.log.warn('Could not run IV_acq '
'because {} is already running'
.format(self.lock.job))
return False, 'Could not acquire lock'
self.take_data = True
last_release = time.time()
session.data = {'fields': {}}
while self.take_data:
if time.time() - last_release > 1.:
last_release = time.time()
if not self.lock.release_and_acquire(timeout=1000):
self.log.warn(
'IV_acq(): '
'Could not re-acquire lock now held by {}.'
.format(self.lock.job))
if self.lock.job == 'calibrate_wg':
self.log.warn(
'IV_acq(): '
'Continue to wait for {}()'
.format(self.lock.job))
# Wait for lock acquisition forever
acquired = self.lock.acquire(
timeout=-1, job='IV_acq')
if acquired:
self.log.info(
'Succcessfully got the lock '
'for IV_acq()!')
else:
self.log.warn(
'Failed to aquire the lock '
'for IV_acq()!')
return False, \
'Could not re-acquire lock '\
'for IV_acq() (timeout=-1)'
else:
return False, \
'Could not re-acquire lock '\
'for IV_acq() (timeout=1000 sec)'
current_time = time.time()
data = {'timestamp': time.time(),
'block_name': 'Kikusui_IV',
'data': {}}
# check connection
ret, msg = self._check_connect()
if not ret:
msg = 'Could not connect to the KIKUSUI power supply!'
v_val, i_val, vs_val, is_val = 0., 0., 0., 0.
s_val = -1 # -1 means Not connected.
self.log.warn(msg)
# try to reconnect
self._reconnect()
else:
v_msg, v_val = self.cmd.user_input('V?')
i_msg, i_val = self.cmd.user_input('C?')
vs_msg, vs_val = self.cmd.user_input('VS?')
is_msg, is_val = self.cmd.user_input('CS?')
s_msg, s_val = self.cmd.user_input('O?')
data['data']['kikusui_volt'] = v_val
data['data']['kikusui_curr'] = i_val
data['data']['kikusui_voltset'] = vs_val
data['data']['kikusui_currset'] = is_val
data['data']['kikusui_status'] = s_val
self.agent.publish_to_feed('kikusui_psu', data)
# store session.data
field_dict = {'kikusui':
{'volt': v_val,
'curr': i_val,
'voltset': vs_val,
'currset': is_val,
'status': s_val
}
}
session.data['fields'] = field_dict
session.data['timestamp'] = current_time
time.sleep(1) # DAQ interval
# End of while loop for take_data
# End of acquired lock
self.agent.feeds['kikusui_feed'].flush_buffer()
return True, 'Acqusition exited cleanly'
def stop_IV_acq(self, session, params=None):
if self.take_data:
self.take_data = False
return True, 'requested to stop taking data'
return False, 'acq is not currently running'
def make_parser(parser=None):
if parser is None:
parser = argparse.ArgumentParser()
pgroup = parser.add_argument_group('Agent Options')
pgroup.add_argument('--kikusui-ip')
pgroup.add_argument('--kikusui-port')
pgroup.add_argument('--encoder-agent', dest='encoder_agent',
default='wgencoder',
help='Instance id of the wiregrid encoder agent')
pgroup.add_argument('--debug', dest='debug',
action='store_true', default=False,
help='Write a log file for debug')
return parser
def main(args=None):
parser = make_parser()
args = site_config.parse_args(agent_class='WiregridKikusuiAgent',
parser=parser,
args=args)
agent, runner = ocs_agent.init_site_agent(args)
kikusui_agent = WiregridKikusuiAgent(agent, kikusui_ip=args.kikusui_ip,
kikusui_port=args.kikusui_port,
encoder_agent=args.encoder_agent,
debug=args.debug)
agent.register_process('IV_acq', kikusui_agent.IV_acq,
kikusui_agent.stop_IV_acq, startup=True)
agent.register_task('set_on', kikusui_agent.set_on)
agent.register_task('set_off', kikusui_agent.set_off)
agent.register_task('set_c', kikusui_agent.set_c)
agent.register_task('set_v', kikusui_agent.set_v)
agent.register_task('get_vc', kikusui_agent.get_vc)
agent.register_task('get_angle', kikusui_agent.get_angle)
agent.register_task('calibrate_wg', kikusui_agent.calibrate_wg)
agent.register_task('stepwise_rotation',
kikusui_agent.stepwise_rotation)
runner.run(agent, auto_reconnect=True)
if __name__ == '__main__':
main()