Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1f32c2c
Add three_leg_turnaround to ACU agent
mjrand Oct 2, 2025
87b9700
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 2, 2025
6f4e41a
Fixed import typo
mjrand Oct 2, 2025
5c3149d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 2, 2025
0c902a7
Changed polyval calcs. Added default behavior for turnaround method i…
mjrand Oct 3, 2025
47db30c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 3, 2025
586011f
Changed default turnaround_method behavior. Added error check. Remove…
mjrand Oct 3, 2025
f132693
Fixed bugs, changed profile step time, removed duplicate point
mjrand Oct 13, 2025
3ee1f9f
Apply 3-leg turnaround to type2/3 scans
mhasself Oct 24, 2025
13db47f
And wire it up to generate_scan
mhasself Oct 24, 2025
5838139
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 24, 2025
75bd9a8
Closer but still not quite it ...
mhasself Oct 24, 2025
b9d9005
ACU: three-leg-turnaround can handle second_leg_time=0
mhasself Oct 29, 2025
b23285d
ACU: type2/3 fixes: correct vel spec and use step_time=.1
mhasself Oct 29, 2025
9e50931
ACU: comments and docstring repair
mhasself Oct 29, 2025
6e87112
Merge pull request #938 from simonsobs/type23-cmdturn
mjrand Oct 29, 2025
72ce70d
fix: fixes to prevent errors when running 3-leg turnarounds with type…
skhrg Oct 31, 2025
63f7f13
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 31, 2025
7b2bd84
Merge pull request #942 from simonsobs/3leg_mods
mjrand Nov 4, 2025
2a55cfb
ACU: reorganize generate_constant_velocity_scan
mhasself Nov 20, 2025
c7f4161
new const vel scan in agent ...
mhasself Nov 20, 2025
d887160
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 20, 2025
cdb6d1e
bugfix
mhasself Nov 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/agents/acu_agent.rst
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,9 @@ ignorance:
of "az", "el", "third", and "none". See further explanation in
:class:`ACUAgent <socs.agents.acu.agent.ACUAgent>`.
- ``scan_params``: Default scan parameters; currently ``az_speed``
(float, deg/s) and ``az_accel`` (float, deg/s/s). If not specfied,
these are given default values depending on the platform type.
(float, deg/s) ``az_accel`` (float, deg/s/s), ``el_freq`` (float, Hz),
and ``turnaround_method`` (str). If not specfied, these are given
default values depending on the platform type.


Other agent functions
Expand Down
74 changes: 58 additions & 16 deletions socs/agents/acu/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@
'az_speed': 2,
'az_accel': 1,
'el_freq': .15,
'turnaround_method': 'standard',
},
'satp': {
'az_speed': 1,
'az_accel': 1,
'el_freq': 0,
'turnaround_method': 'standard',
},
}

Expand Down Expand Up @@ -1794,6 +1796,8 @@ def set_speed_mode(self, session, params):
@ocs_agent.param('az_speed', type=float, default=None)
@ocs_agent.param('az_accel', type=float, default=None)
@ocs_agent.param('el_freq', type=float, default=None)
@ocs_agent.param('turnaround_method', type=str, default=None,
choices=[None, 'standard', 'three_leg'])
@ocs_agent.param('reset', default=False, type=bool)
@inlineCallbacks
def set_scan_params(self, session, params):
Expand All @@ -1814,7 +1818,7 @@ def set_scan_params(self, session, params):
"""
if params['reset']:
self.scan_params.update(self.default_scan_params)
for k in ['az_speed', 'az_accel', 'el_freq']:
for k in ['az_speed', 'az_accel', 'el_freq', 'turnaround_method']:
if params[k] is not None:
self.scan_params[k] = params[k]
self.log.info('Updated default scan params to {sp}', sp=self.scan_params)
Expand Down Expand Up @@ -2034,18 +2038,23 @@ def line_batcher(ff_scan, t_shift=0., n=10):
'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('type', default=1, choices=[1, 2, 3])
@ocs_agent.param('scan_type', default=1, choices=[1, 2, 3])
@ocs_agent.param('az_vel_ref', type=float, default=None)
@ocs_agent.param('turnaround_method', default=None,
choices=[None, 'standard', 'three_leg'])
@ocs_agent.param('scan_upload_length', type=float, default=None)
@ocs_agent.param('type', default=None, choices=[1, 2, 3])
@inlineCallbacks
def generate_scan(self, session, params):
"""generate_scan(az_endpoint1, az_endpoint2, \
az_speed=None, az_accel=None, \
el_endpoint1=None, el_endpoint2=None, \
el_speed=None, \
el_speed=None, el_freq=None, \
num_scans=None, start_time=None, \
wait_to_start=None, step_time=None, \
az_start='end', az_drift=None, az_only=True, \
scan_type=1, az_vel_ref=None, \
turnaround_method=None, \
scan_upload_length=None)

**Process** - Scan generator, currently only works for
Expand All @@ -2062,6 +2071,7 @@ def generate_scan(self, session, params):
track.
el_endpoint2 (float): this is ignored.
el_speed (float): this is ignored.
el_freq(float): frequency of the elevation nods for scan_type=3.
num_scans (int or None): if not None, limits the scan to
the specified number of constant velocity legs. The
process will exit without error once that has
Expand Down Expand Up @@ -2092,16 +2102,22 @@ def generate_scan(self, session, params):
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.
type (int): What type of scan to use. Only 1, 2, 3 are valid.
scan_type (int): What type of scan to use. Only 1, 2, 3 are valid.
Type 1 is a constant elevation scan.
Type 2 includes a variation in az speed that scales as sin(az).
Type 3 is a Type 2 with an sinusoidal el nod.
az_vel_ref (float or None): azimuth to center the velocity profile at.
If None then the average of the endpoints is used.
turnaround_method (str): The method used for generating turnaround.
Default (None) generates the baseline minimal jerk trajectory.
'three_leg' generates a three-leg turnaround which attempts to
minimize the acceleration at the midpoint of the turnaround.
scan_upload_length (float): number of seconds for each set
of uploaded points. If this is not specified, the
track manager will try to use as short a time as is
reasonable.
type (int): Temporary alias for scan_type. Do not
use. Will be removed.

Notes:
Note that all parameters are optional except for
Expand All @@ -2116,6 +2132,11 @@ def generate_scan(self, session, params):
if self._get_sun_policy('motion_blocked'):
return False, "Motion blocked; Sun avoidance in progress."

if params['type'] is not None:
self.log.warn('Caller passed "type" instead of "scan_type" arg; moving.')
params['scan_type'] = params['type']
del params['type']

self.log.info('User scan params: {params}', params=params)

az_endpoint1 = params['az_endpoint1']
Expand All @@ -2128,12 +2149,23 @@ def generate_scan(self, session, params):
az_speed = params['az_speed']
az_accel = params['az_accel']
el_freq = params['el_freq']
turnaround_method = params['turnaround_method']
if az_speed is None:
az_speed = self.scan_params['az_speed']
if az_accel is None:
az_accel = self.scan_params['az_accel']
if el_freq is None:
el_freq = self.scan_params['el_freq']
if turnaround_method is None:
if params['scan_type'] in [2, 3]:
turnaround_method = 'three_leg'
self.log.info('Setting turnaround_method="three_leg" for type2/3 scan.')
else:
turnaround_method = self.scan_params['turnaround_method']

# Check if the turnaround method is usable for the called scan type.
if turnaround_method == "standard" and params['scan_type'] != 1:
raise ValueError("Cannot use standard turnaround method with type 2 or 3 scans!")

# Do we need to limit the az_accel? This limit comes from a
# maximum jerk parameter; the equation below (without the
Expand Down Expand Up @@ -2175,7 +2207,8 @@ def generate_scan(self, session, params):
el_speed = params.get('el_speed', 0.0)
plan = sh.plan_scan(az_endpoint1, az_endpoint2,
el=el_endpoint1, v_az=az_speed, a_az=az_accel,
az_start=scan_params.get('az_start'))
az_start=scan_params.get('az_start'),
scan_type=params['scan_type'])

# Use the plan to set scan upload parameters.
if scan_params.get('step_time') is None:
Expand Down Expand Up @@ -2237,30 +2270,36 @@ def generate_scan(self, session, params):

# Prepare the point generator.
free_form = False
if params["type"] == 1:
if params['scan_type'] == 1:
if turnaround_method == 'three_leg':
free_form = True

g = sh.generate_constant_velocity_scan(az_endpoint1=az_endpoint1,
az_endpoint2=az_endpoint2,
az_speed=az_speed, acc=az_accel,
turnaround_method=turnaround_method,
el_endpoint1=el_endpoint1,
el_endpoint2=el_endpoint2,
el_speed=el_speed,
az_first_pos=plan['init_az'],
**scan_params)
elif params["type"] == 2:
elif params['scan_type'] == 2:
free_form = True
g = sh.generate_type2_scan(az_endpoint1=az_endpoint1,
az_endpoint2=az_endpoint2,
az_speed=az_speed, acc=az_accel,
turnaround_method=turnaround_method,
el_endpoint1=el_endpoint1,
az_vel_ref=az_vel_ref,
az_first_pos=plan['init_az'],
**scan_params)
elif params["type"] == 3:
elif params['scan_type'] == 3:
free_form = True
azonly = False
g = sh.generate_type3_scan(az_endpoint1=az_endpoint1,
az_endpoint2=az_endpoint2,
az_speed=az_speed, acc=az_accel,
turnaround_method=turnaround_method,
el_endpoint1=el_endpoint1,
el_endpoint2=el_endpoint2,
el_freq=el_freq,
Expand All @@ -2283,8 +2322,8 @@ def generate_scan(self, session, params):
'el1': el_endpoint1,
'el2': el_endpoint2,
'el_freq': el_freq,
'type': params['type'],
'turnaround_type': sh.TURNAROUNDS_ENUM['standard'],
'type': params['scan_type'],
'turnaround_type': sh.TURNAROUNDS_ENUM[turnaround_method],
})

self.agent.publish_to_feed('scan_params',
Expand Down Expand Up @@ -2416,6 +2455,9 @@ def _run_track(self, session, point_gen, step_time, azonly=False,
first_upload_time = None
wait_stop_timeout = None

# eesh
unabort_failure = False

while True:
now = time.time()
current_modes = {'Az': self.data['status']['summary']['Azimuth_mode'],
Expand Down Expand Up @@ -2447,6 +2489,8 @@ def _run_track(self, session, point_gen, step_time, azonly=False,
else:
if got_progtrack:
self.log.warn('Unexpected exit from ProgramTrack mode!')
if mode == 'stop':
unabort_failure = True # close enough!
mode = 'abort'
elif now - start_time > MAX_PROGTRACK_SET_TIME:
self.log.warn('Failed to set ProgramTrack mode in a timely fashion.')
Expand All @@ -2458,7 +2502,7 @@ def _run_track(self, session, point_gen, step_time, azonly=False,
if current_modes['Remote'] == 0:
self.log.warn('ACU no longer in remote mode!')
mode = 'abort'
if session.status == 'stopping':
if session.status == 'stopping' and mode not in ['stop', 'abort']:
mode = 'stop'
stop_message = 'User-requested stop.'
lines = []
Expand All @@ -2477,7 +2521,7 @@ def _run_track(self, session, point_gen, step_time, azonly=False,
# check is used to decide we're done.
while mode == 'go' and (len(lines) <= new_line_target or lines[-1].group_flag != 0):
try:
lines.extend(next(point_gen))
lines.append(next(point_gen))
except StopIteration:
mode = 'stop'
stop_message = 'Stop due to end of the planned track.'
Expand All @@ -2491,10 +2535,6 @@ def _run_track(self, session, point_gen, step_time, azonly=False,
# Grab the minimum batch
upload_lines, lines = lines[:new_line_target], lines[new_line_target:]

# If the last line has a "group" flag, keep transferring lines.
while len(lines) and len(upload_lines) and upload_lines[-1].group_flag != 0:
upload_lines.append(lines.pop(0))

if len(upload_lines):
# Discard the group flag and upload all.
text = sh.get_track_points_text(
Expand Down Expand Up @@ -2545,6 +2585,8 @@ def _run_track(self, session, point_gen, step_time, azonly=False,
'Clear Stack')

if mode == 'abort':
if unabort_failure:
return True, 'Problems on shutdown but close enough.'
return False, 'Problems during scan'
return True, f'Scan ended. {stop_message}'

Expand Down
Loading
Loading