Skip to content

Commit

Permalink
Update ACU agent to work with LAT (#526)
Browse files Browse the repository at this point in the history
* ACU: cmdline switch to not start monitor/broadcast

* ACU: corotator data are tagged as "Corotator" instead of "Axis3"

Other fixes to work on the LAT; some simplifications

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* ACU: fix turn-around time computation

The scan generator code was adding an extra "step_time" (usually 1s)
to each turn-around.

* ACU: remove az_min / az_max in preparation for az_drift

* ACU: add az_drift parameter to scan generator

* ACU: add az_drift to agent

* ACU: document startup arg

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
2 people authored and d-hoshino2626 committed Apr 12, 2024
1 parent c69b460 commit c902bed
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 69 deletions.
102 changes: 57 additions & 45 deletions socs/agents/acu/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,14 @@ class ACUAgent:
The full path to a scan config file describing motions to cycle
through on the ACU. If this is None, the associated process and
feed will not be registered.
startup (bool):
If True, immediately start the main monitoring processes
for status and UDP data.
"""

def __init__(self, agent, acu_config='guess', exercise_plan=None):
def __init__(self, agent, acu_config='guess', exercise_plan=None,
startup=False):
# Separate locks for exclusive access to az/el, and boresight motions.
self.azel_lock = TimeoutLock()
self.boresight_lock = TimeoutLock()
Expand Down Expand Up @@ -100,7 +105,7 @@ def __init__(self, agent, acu_config='guess', exercise_plan=None):
'ACU_failures_errors': {},
'platform_status': {},
'ACU_emergency': {},
'third_axis': {},
'corotator': {},
},
'broadcast': {},
}
Expand All @@ -120,12 +125,12 @@ def __init__(self, agent, acu_config='guess', exercise_plan=None):
self.monitor,
self._simple_process_stop,
blocking=False,
startup=True)
startup=startup)
agent.register_process('broadcast',
self.broadcast,
self._simple_process_stop,
blocking=False,
startup=True)
startup=startup)
agent.register_process('generate_scan',
self.generate_scan,
self._simple_process_stop,
Expand Down Expand Up @@ -379,6 +384,7 @@ def monitor(self, session, params):
'Azimuth_mode': None,
'Elevation_mode': None,
'Boresight_mode': None,
'Corotator_mode': None,
}

j = yield self.acu_read.http.Values(self.acu8100)
Expand Down Expand Up @@ -461,9 +467,18 @@ def monitor(self, session, params):
self.log.warn('ACU now in remote mode.')
if self.data['status']['summary']['ctime'] == prev_checkdata['ctime']:
self.log.warn('ACU time has not changed from previous data point!')
for axis_mode in ['Azimuth_mode', 'Elevation_mode', 'Boresight_mode']:
if self.data['status']['summary'][axis_mode] != prev_checkdata[axis_mode]:
self.log.info(axis_mode + ' has changed to ' + self.data['status']['summary'][axis_mode])

# Alert on any axis mode change.
for axis_mode in prev_checkdata.keys():
if 'mode' not in axis_mode:
continue
v = self.data['status']['summary'].get(axis_mode)
if v is None:
v = self.data['status']['corotator'].get(axis_mode)
if v != prev_checkdata[axis_mode]:
self.log.info('{axis_mode} is now "{v}"',
axis_mode=axis_mode, v=v)
prev_checkdata[axis_mode] = v

# influx_blocks are constructed based on refers to all
# other self.data['status'] keys. Do not add more keys to
Expand Down Expand Up @@ -577,11 +592,6 @@ def monitor(self, session, params):

data_blocks.update(new_blocks)

prev_checkdata = {'ctime': self.data['status']['summary']['ctime'],
'Azimuth_mode': self.data['status']['summary']['Azimuth_mode'],
'Elevation_mode': self.data['status']['summary']['Elevation_mode'],
'Boresight_mode': self.data['status']['summary']['Boresight_mode'],
}
return True, 'Acquisition exited cleanly.'

@ocs_agent.param('auto_enable', type=bool, default=True)
Expand Down Expand Up @@ -1158,51 +1168,44 @@ def stop_and_clear(self, session, params):
to Stop; also clear the ProgramTrack stack.
"""

session.set_status('running')
i = 0
while i < 5:
def _read_modes():
modes = [self.data['status']['summary']['Azimuth_mode'],
self.data['status']['summary']['Elevation_mode'],
]
self.data['status']['summary']['Elevation_mode']]
if self.acu_config['platform'] == 'satp':
modes.append(self.data['status']['summary']['Boresight_mode'])
elif self.acu_config['platform'] in ['ccat', 'lat']:
modes.append(self.data['status']['third_axis']['Axis3_mode'])
if modes != ['Stop', 'Stop', 'Stop']:
modes.append(self.data['status']['corotator']['Corotator_mode'])
return modes

session.set_status('running')
for i in range(6):
if all([m == 'Stop' for m in _read_modes()]):
self.log.info('All axes in Stop mode')
break
else:
yield self.acu_control.stop()
self.log.info('Stop called (iteration %i)' % (i + 1))
yield dsleep(0.1)
i += 1
else:
self.log.info('All axes in Stop mode')
i = 5
modes = [self.data['status']['summary']['Azimuth_mode'],
self.data['status']['summary']['Elevation_mode'],
]
if self.acu_config['platform'] == 'satp':
modes.append(self.data['status']['summary']['Boresight_mode'])
elif self.acu_config['platform'] in ['ccat', 'lat']:
modes.append(self.data['status']['third_axis']['Axis3_mode'])
if modes != ['Stop', 'Stop', 'Stop']:
self.log.error('Axes could not be set to Stop!')
return False, 'Could not set axes to Stop mode'
j = 0
while j < 5:
else:
msg = 'Failed to set all axes to Stop mode!'
self.log.error(msg)
return False, msg

for i in range(6):
free_stack = self.data['status']['summary']['Free_upload_positions']
if free_stack < FULL_STACK:
yield self.acu_control.http.Command('DataSets.CmdTimePositionTransfer',
'Clear Stack')
self.log.info('Clear Stack called (iteration %i)' % (j + 1))
self.log.info('Clear Stack called (iteration %i)' % (i + 1))
yield dsleep(0.1)
j += 1
else:
self.log.info('Stack cleared')
j = 5
free_stack = self.data['status']['summary']['Free_upload_positions']
if free_stack < FULL_STACK:
self.log.warn('Stack not fully cleared!')
return False, 'Could not clear stack'
break
else:
msg = 'Failed to clear the ProgramTrack stack!'
self.log.warn(msg)
return False, msg

session.set_status('stopping')
return True, 'Job completed'
Expand Down Expand Up @@ -1287,6 +1290,7 @@ def line_batcher(lines, n=10):
@ocs_agent.param('az_start', default='end',
choices=['end', 'mid', 'az_endpoint1', 'az_endpoint2',
'mid_inc', 'mid_dec'])
@ocs_agent.param('az_drift', type=float, default=None)
@ocs_agent.param('az_only', type=bool, default=True)
@ocs_agent.param('scan_upload_length', type=float, default=None)
@inlineCallbacks
Expand All @@ -1297,7 +1301,7 @@ def generate_scan(self, session, params):
el_speed=None, \
num_scans=None, start_time=None, \
wait_to_start=None, step_time=None, \
az_start='end', az_only=True, \
az_start='end', az_drift=None, az_only=True, \
scan_upload_length=None)
**Process** - Scan generator, currently only works for
Expand Down Expand Up @@ -1337,6 +1341,10 @@ def generate_scan(self, session, params):
of the scan use 'mid_inc' (for first half-leg to have
positive az velocity), 'mid_dec' (negative az velocity),
or 'mid' (velocity oriented towards endpoint2).
az_drift (float): if set, this should be a drift velocity
in deg/s. The scan extrema will move accordingly. This
can be used to better follow compact sources as they
rise or set through the focal plane.
az_only (bool): if True (the default), then only the
Azimuth axis is put in ProgramTrack mode, and the El axis
is put in Stop mode.
Expand Down Expand Up @@ -1379,7 +1387,8 @@ def generate_scan(self, session, params):
scan_upload_len = params.get('scan_upload_length')
scan_params = {k: params.get(k) for k in [
'num_scans', 'num_batches', 'start_time',
'wait_to_start', 'step_time', 'batch_size', 'az_start']
'wait_to_start', 'step_time', 'batch_size',
'az_start', 'az_drift']
if params.get(k) is not None}
el_speed = params.get('el_speed', 0.0)

Expand Down Expand Up @@ -1682,6 +1691,8 @@ def add_agent_args(parser_in=None):
pgroup = parser_in.add_argument_group('Agent Options')
pgroup.add_argument("--acu-config")
pgroup.add_argument("--exercise-plan")
pgroup.add_argument("--no-processes", action='store_true',
default=False)
return parser_in


Expand All @@ -1691,7 +1702,8 @@ def main(args=None):
parser=parser,
args=args)
agent, runner = ocs_agent.init_site_agent(args)
_ = ACUAgent(agent, args.acu_config, args.exercise_plan)
_ = ACUAgent(agent, args.acu_config, args.exercise_plan,
startup=not args.no_processes)

runner.run(agent, auto_reconnect=True)

Expand Down
71 changes: 47 additions & 24 deletions socs/agents/acu/drivers.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ def generate_constant_velocity_scan(az_endpoint1, az_endpoint2, az_speed,
batch_size=500,
az_start='mid_inc',
az_first_pos=None,
az_drift=None,
ptstack_fmt=True):
"""Python generator to produce times, azimuth and elevation positions,
azimuth and elevation velocities, azimuth and elevation flags for
Expand Down Expand Up @@ -266,33 +267,55 @@ def generate_constant_velocity_scan(az_endpoint1, az_endpoint2, az_speed,
az_first_pos (float): If not None, the first az scan will
start at this position (but otherwise proceed in the same
starting direction).
az_drift (float): The rate (deg / s) at which to shift the
scan endpoints in time. This can be used to better track
celestial sources in targeted scans.
ptstack_fmt (bool): determine whether values are produced with the
necessary format to upload to the ACU. If False, this function will
produce lists of time, azimuth, elevation, azimuth velocity,
elevation velocity, azimuth flags, and elevation flags. Default is
True.
"""
az_min = min(az_endpoint1, az_endpoint2)
az_max = max(az_endpoint1, az_endpoint2)
if az_max == az_min:
def get_target_az(current_az, current_t, increasing):
# Return the next endpoint azimuth, based on current (az, t)
# and whether to move in +ve or -ve az direction.
#
# Includes the effects of az_drift, to keep the scan endpoints
# (at least at the end of a scan) on the drifted trajectories.
if increasing:
target = max(az_endpoint1, az_endpoint2)
else:
target = min(az_endpoint1, az_endpoint2)
if az_drift is not None:
v = az_speed if increasing else -az_speed
target = target + az_drift / (v - az_drift) * (
(target - current_az + v * current_t))
return target

if az_endpoint1 == az_endpoint2:
raise ValueError('Generator requires two different az endpoints!')

# Note that starting scan direction gets modified, below,
# depending on az_start.
increasing = az_endpoint2 > az_endpoint1

if az_start in ['az_endpoint1', 'az_endpoint2', 'end']:
if az_start in ['az_endpoint1', 'end']:
az = az_endpoint1
else:
az = az_endpoint2
increasing = (az == az_min)
increasing = not increasing
elif az_start in ['mid_inc', 'mid_dec', 'mid']:
az = (az_endpoint1 + az_endpoint2) / 2
if az_start == 'mid':
increasing = az_endpoint2 > az_endpoint1
pass
elif az_start == 'mid_inc':
increasing = True
else:
increasing = False
else:
raise ValueError('az_start value not supported. Choose from '
raise ValueError(f'az_start value "{az_start}" not supported. Choose from '
'az_endpoint1, az_endpoint2, mid_inc, mid_dec')
az_vel = az_speed if increasing else -az_speed

Expand All @@ -318,7 +341,7 @@ def generate_constant_velocity_scan(az_endpoint1, az_endpoint2, az_speed,
stop_iter = float('inf')
else:
stop_iter = num_batches
batch_size = int(np.ceil((az_max - az_min) / daz))
batch_size = int(np.ceil(abs(az_endpoint2 - az_endpoint1) / daz))

def dec_num_scans():
nonlocal num_scans
Expand All @@ -328,6 +351,7 @@ def dec_num_scans():
def check_num_scans():
return num_scans is None or num_scans > 0

target_az = get_target_az(az, t, increasing)
point_group_batch = 0

i = 0
Expand All @@ -344,64 +368,63 @@ def check_num_scans():
point_block[6].append(el_flag)
point_block[7].append(int(point_group_batch > 0))

t += step_time
if point_group_batch > 0:
point_group_batch -= 1

if increasing:
if az <= (az_max - 2 * daz):
if az <= (target_az - 2 * daz):
t += step_time
az += daz
az_vel = az_speed
el_vel = el_speed
az_flag = 1
el_flag = 0
increasing = True
elif az == az_max:
elif az == target_az:
# Turn around.
t += turntime
az_vel = -1 * az_speed
el_vel = el_speed
az_flag = 1
el_flag = 0
increasing = False
target_az = get_target_az(az, t, increasing)
dec_num_scans()
point_group_batch = MIN_GROUP_NEW_LEG - 1
else:
az_remaining = az_max - az
time_remaining = az_remaining / az_speed
az = az_max
t += (time_remaining - step_time)
time_remaining = (target_az - az) / az_speed
az = target_az
t += time_remaining
az_vel = az_speed
el_vel = el_speed
az_flag = 2
el_flag = 0
increasing = True
else:
if az >= (az_min + 2 * daz):
if az >= (target_az + 2 * daz):
t += step_time
az -= daz
az_vel = -1 * az_speed
el_vel = el_speed
az_flag = 1
el_flag = 0
increasing = False
elif az == az_min:
elif az == target_az:
# Turn around.
t += turntime
az_vel = az_speed
el_vel = el_speed
az_flag = 1
el_flag = 0
increasing = True
target_az = get_target_az(az, t, increasing)
dec_num_scans()
point_group_batch = MIN_GROUP_NEW_LEG - 1
else:
az_remaining = az - az_min
time_remaining = az_remaining / az_speed
az = az_min
t += (time_remaining - step_time)
time_remaining = (az - target_az) / az_speed
az = target_az
t += time_remaining
az_vel = -1 * az_speed
el_vel = el_speed
az_flag = 2
el_flag = 0
increasing = False

if not check_num_scans():
# Kill the velocity on the last point and exit -- this
Expand Down

0 comments on commit c902bed

Please sign in to comment.