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

DM-21699: Clean up the Rotator XML #8

Merged
merged 10 commits into from Jan 8, 2020
Expand Up @@ -28,7 +28,7 @@


async def main():
parser = argparse.ArgumentParser(f"Run mock rotator cRIO code")
parser = argparse.ArgumentParser(f"Run mock rotator PXI code")
parser.add_argument("host", help="IP address of rotator CSC")
args = parser.parse_args()

Expand Down
8 changes: 4 additions & 4 deletions doc/index.rst
Expand Up @@ -11,12 +11,12 @@ This code is a front end for a low level controller written by Moog.

How to start the system:

* Turn on the rotator cRIO if it is not already running.
* Turn on the rotator PXI if it is not already running.
* Start the rotator CSC.
* Wait for the ``connected`` event to report ``command=True`` and ``telemetry=True``.
This should happen quickly; if it does not then check that the cRIO is fully booted up and configured to use the correct IP address for the CSC.
This should happen quickly; if it does not then check that the PXI is fully booted up and configured to use the correct IP address for the CSC.
* Check the ``controllerState`` event.
If it is ``state=Offline, offline_substate=PublishOnly``, which is the state the cRIO wakes up in, you must use the vendor's engineering user interface (EUI) to change the state to ``state=Offline, offline_substate=Available`` (or any more enabled mode).
If it is ``state=Offline, offline_substate=PublishOnly``, which is the state the PXI wakes up in, you must use the vendor's engineering user interface (EUI) to change the state to ``state=Offline, offline_substate=Available`` (or any more enabled mode).
You can set the state on the main panel.
* Check the ``commandableByDDS`` event.
If ``state=False`` then you must use the EUI to change the control mode from ``GUI`` to ``DDS``.
Expand Down Expand Up @@ -50,7 +50,7 @@ Contributing
``lsst.ts.rotator`` is developed at https://github.com/lsst-ts/ts_rotator.
You can find Jira issues for this module at `labels=ts_rotator <https://jira.lsstcorp.org/issues/?jql=project%20%3D%20DM%20AND%20labels%20%20%3D%20ts_rotator>`_.

.. .. _lsst.ts.rotator-pyapi:
.. _lsst.ts.rotator-pyapi:

Python API reference
====================
Expand Down
6 changes: 3 additions & 3 deletions python/lsst/ts/rotator/enums.py
Expand Up @@ -25,7 +25,7 @@


class CommandCode(enum.IntEnum):
"""Values for ``command.cmd``.
"""Values for ``Command.code``.


In the Moog code these are defined in enum cmdType.
Expand All @@ -43,8 +43,8 @@ class CommandCode(enum.IntEnum):
class SetEnabledSubstateParam(enum.IntEnum):
"""Enabled substate parameters.

Values for ``command.param1`` when
``command.cmd = CommandCode.SET_ENABLED_SUBSTATE``
Values for ``Command.param1`` when
``Command.code = CommandCode.SET_ENABLED_SUBSTATE``
and the current state is ``ENABLED``.

Called ``EnabledSubStateTriggers`` in Moog code.
Expand Down
37 changes: 15 additions & 22 deletions python/lsst/ts/rotator/rotator_commander.py
Expand Up @@ -63,17 +63,15 @@ def __init__(self):
Other commands and arguments:
* configureAcceleration max_acceleration # Set maximum acceleration
* configureVelocity max_velocity # Set maximum velocity
* positionSet position # Set a position for the move command
* move # Move to the position set by positionSet
* move position # Move to the specified position
* stop # Send the stop command and stop any existing ramp or sine.
* trackStart # Put the controller into the FAULT state
# (due to lack of position, velocity, time updates).

Units are degrees and degrees/second

For example:
positionSet 5
move
move 5
stop # In case you want to stop the move early
exit""")

Expand All @@ -89,13 +87,9 @@ async def do_configureVelocity(self, args):
kwargs = self.check_arguments(args, "vlimit")
await self.remote.cmd_configureVelocity.set_start(**kwargs, timeout=STD_TIMEOUT)

async def do_positionSet(self, args):
kwargs = self.check_arguments(args, "angle")
await self.remote.cmd_positionSet.set_start(**kwargs, timeout=STD_TIMEOUT)

async def do_move(self, args):
self.check_arguments(args)
await self.remote.cmd_move.start(timeout=STD_TIMEOUT)
kwargs = self.check_arguments(args, "position")
await self.remote.cmd_move.set_start(**kwargs, timeout=STD_TIMEOUT)

async def do_stop(self, args):
# For safety don't check arguments, just *stop*
Expand All @@ -120,12 +114,12 @@ async def do_sine(self, args):
kwargs = self.check_arguments(args, "start_position", "amplitude", "period")
self.tracking_task = asyncio.ensure_future(self._sine(**kwargs))

async def tel_Motors_callback(self, data):
rounded_value = np.around(data.Calibrated, decimals=3)
if np.array_equal(self.previous_tel_Motors, rounded_value):
async def tel_motors_callback(self, data):
rounded_value = np.around(data.calibrated, decimals=3)
if np.array_equal(self.previous_tel_motors, rounded_value):
return
self.previous_tel_Motors = rounded_value
print(f"Motors: {self.format_data(data)}")
self.previous_tel_motors = rounded_value
print(f"motors: {self.format_data(data)}")

async def _ramp(self, start_position, end_position, velocity):
try:
Expand All @@ -141,11 +135,10 @@ async def _ramp(self, start_position, end_position, velocity):
await self.remote.cmd_trackStart.start(timeout=STD_TIMEOUT)
for i in range(nelts):
pos = start_position + i*dpos
await self.remote.cmd_track.set_start(
angle=pos,
velocity=velocity,
tai=salobj.current_tai(),
timeout=STD_TIMEOUT)
await self.remote.cmd_track.set_start(angle=pos,
velocity=velocity,
tai=salobj.current_tai(),
timeout=STD_TIMEOUT)
await asyncio.sleep(TRACK_INTERVAL)
except asyncio.CancelledError:
print(f"ramp cancelled")
Expand All @@ -162,9 +155,9 @@ async def _sine(self, start_position, amplitude, period):
f"with amplitude {amplitude} and a period of {period}")
nelts = int(period / TRACK_INTERVAL)
vmax = amplitude * 2 * math.pi / period
settings = self.remote.evt_settingsApplied.get()
settings = self.remote.evt_configuration.get()
if settings is None:
raise RuntimeError("Must wait until settingsApplied seen so we can check max velocity")
raise RuntimeError("Must wait until configuration seen so we can check max velocity")
if abs(vmax) > settings.velocityLimit:
raise ValueError(f"maximum velocity {vmax} > allowed {settings.velocityLimit}")
await self.remote.cmd_trackStart.start(timeout=STD_TIMEOUT)
Expand Down
117 changes: 30 additions & 87 deletions python/lsst/ts/rotator/rotator_csc.py
Expand Up @@ -98,65 +98,44 @@ async def do_configureAcceleration(self, data):
self.assert_enabled_substate(Rotator.EnabledSubstate.STATIONARY)
if not 0 < data.alimit <= constants.MAX_ACCEL_LIMIT:
raise salobj.ExpectedError(f"alimit={data.alimit} must be > 0 and <= {constants.MAX_ACCEL_LIMIT}")
await self.run_command(cmd=enums.CommandCode.CONFIG_ACCEL,
await self.run_command(code=enums.CommandCode.CONFIG_ACCEL,
param1=data.alimit)

async def do_configureVelocity(self, data):
"""Specify the velocity limit."""
self.assert_enabled_substate(Rotator.EnabledSubstate.STATIONARY)
if not 0 < data.vlimit <= constants.MAX_VEL_LIMIT:
raise salobj.ExpectedError(f"vlimit={data.vlimit} must be > 0 and <= {constants.MAX_VEL_LIMIT}")
await self.run_command(cmd=enums.CommandCode.CONFIG_VEL,
await self.run_command(code=enums.CommandCode.CONFIG_VEL,
param1=data.vlimit)

async def do_move(self, data):
"""Go to the position specified by the most recent ``positionSet``
command.
"""
self.assert_enabled_substate(Rotator.EnabledSubstate.STATIONARY)
await self.run_command(cmd=enums.CommandCode.SET_ENABLED_SUBSTATE,
param1=enums.SetEnabledSubstateParam.MOVE_POINT_TO_POINT)

async def do_moveConstantVelocity(self, data):
"""Move at the speed and for the duration specified by the most recent
``velocitySet`` command. NOT SUPPORTED.
"""
raise salobj.ExpectedError("Not implemented")
# self.assert_enabled_substate(Rotator.EnabledSubstate.STATIONARY)
# await self.run_command(cmd=enums.CommandCode.SET_ENABLED_SUBSTATE,
# param1=enums.SetEnabledSubstateParam.CONSTANT_VELOCITY)

async def do_positionSet(self, data):
"""Specify a position for the ``move`` command.
"""
self.assert_enabled_substate(Rotator.EnabledSubstate.STATIONARY)
if not self.server.config.lower_pos_limit <= data.angle <= self.server.config.upper_pos_limit:
raise salobj.ExpectedError(f"angle {data.angle} not in range "
if not self.server.config.lower_pos_limit <= data.position <= self.server.config.upper_pos_limit:
raise salobj.ExpectedError(f"position {data.position} not in range "
f"[{self.server.config.lower_pos_limit}, "
f"{self.server.config.upper_pos_limit}]")
await self.run_command(cmd=enums.CommandCode.POSITION_SET,
param1=data.angle)
cmd1 = self.make_command(code=enums.CommandCode.POSITION_SET,
param1=data.position)
cmd2 = self.make_command(code=enums.CommandCode.SET_ENABLED_SUBSTATE,
param1=enums.SetEnabledSubstateParam.MOVE_POINT_TO_POINT)
await self.run_multiple_commands(cmd1, cmd2)
self.evt_target.set_put(position=data.position,
velocity=0,
tai=salobj.current_tai(),
force_output=True)

async def do_stop(self, data):
"""Halt tracking or any other motion.
"""
if self.summary_state != salobj.State.ENABLED:
raise salobj.ExpectedError("Not enabled")
await self.run_command(cmd=enums.CommandCode.SET_ENABLED_SUBSTATE,
await self.run_command(code=enums.CommandCode.SET_ENABLED_SUBSTATE,
param1=enums.SetEnabledSubstateParam.STOP)

async def do_test(self, data):
"""Execute the test command. NOT SUPPORTED.
"""
raise salobj.ExpectedError("Not implemented")
# self.assert_enabled_substate(Rotator.EnabledSubstate.STATIONARY)
# # The test command is unique in that all fields must be left
# # at their initialized value except sync_pattern
# # (at least that is what the Vendor's code does).
# command = structs.Command()
# command.sync_pattern = structs.ROTATOR_SYNC_PATTERN
# await self.server.run_command(command)

async def do_track(self, data):
"""Specify a position, velocity, TAI time tracking update.
"""
Expand All @@ -176,10 +155,14 @@ async def do_track(self, data):
if not abs(data.velocity) <= self.server.config.velocity_limit:
raise salobj.ExpectedError(f"abs(velocity={data.velocity}) > "
f"[{self.server.config.velocity_limit}")
await self.run_command(cmd=enums.CommandCode.TRACK_VEL_CMD,
await self.run_command(code=enums.CommandCode.TRACK_VEL_CMD,
param1=data.tai,
param2=data.angle,
param3=data.velocity)
self.evt_target.set_put(position=data.angle,
velocity=data.velocity,
tai=data.tai,
force_output=True)

async def do_trackStart(self, data):
"""Start tracking.
Expand All @@ -188,23 +171,10 @@ async def do_trackStart(self, data):
until you are done tracking, then issue the ``stop`` command.
"""
self.assert_enabled_substate(Rotator.EnabledSubstate.STATIONARY)
await self.run_command(cmd=enums.CommandCode.SET_ENABLED_SUBSTATE,
await self.run_command(code=enums.CommandCode.SET_ENABLED_SUBSTATE,
param1=enums.SetEnabledSubstateParam.TRACK)
self._tracking_started_telemetry_counter = 2

async def do_velocitySet(self, data):
"""Specify the velocity and duration for the ``moveConstantVelocity``
command. NOT SUPPORTED.
"""
raise salobj.ExpectedError("Not implemented")
# self.assert_enabled_substate(Rotator.EnabledSubstate.STATIONARY)
# if abs(data.velocity) > self.server.config.velocity_limit:
# raise salobj.ExpectedError(f"Velocity {data.velocity} > "
# f"limit {self.server.config.velocity_limit}")
# await self.run_command(cmd=enums.CommandCode.SET_CONSTANT_VEL,
# param1=data.velocity,
# param2=data.moveDuration)

def config_callback(self, server):
"""Called when the TCP/IP controller outputs configuration.

Expand All @@ -213,7 +183,7 @@ def config_callback(self, server):
server : `lsst.ts.hexrotcomm.CommandTelemetryServer`
TCP/IP server.
"""
self.evt_settingsApplied.set_put(
self.evt_configuration.set_put(
positionAngleUpperLimit=server.config.upper_pos_limit,
velocityLimit=server.config.velocity_limit,
accelerationLimit=server.config.accel_limit,
Expand Down Expand Up @@ -247,16 +217,16 @@ def telemetry_callback(self, server):
Position=server.telemetry.current_pos,
Error=server.telemetry.commanded_pos - server.telemetry.current_pos,
)
self.tel_Electrical.set_put(
CopleyStatusWordDrive=[server.telemetry.status_word_drive0,
self.tel_electrical.set_put(
copleyStatusWordDrive=[server.telemetry.status_word_drive0,
server.telemetry.status_word_drive0_axis_b],
CopleyLatchingFaultStatus=[server.telemetry.latching_fault_status_register,
copleyLatchingFaultStatus=[server.telemetry.latching_fault_status_register,
server.telemetry.latching_fault_status_register_axis_b],
)
self.tel_Motors.set_put(
Calibrated=[server.telemetry.state_estimation_ch_a_fb,
self.tel_motors.set_put(
calibrated=[server.telemetry.state_estimation_ch_a_fb,
server.telemetry.state_estimation_ch_b_fb],
Raw=[server.telemetry.state_estimation_ch_a_motor_encoder,
raw=[server.telemetry.state_estimation_ch_a_motor_encoder,
server.telemetry.state_estimation_ch_b_motor_encoder],
)

Expand All @@ -268,38 +238,11 @@ def telemetry_callback(self, server):
state=bool(server.telemetry.application_status & Rotator.ApplicationStatus.DDS_COMMAND_SOURCE),
)

device_errors = []
if server.telemetry.application_status & Rotator.ApplicationStatus.DRIVE_FAULT:
device_errors.append("Drive Error")
if server.telemetry.flags_following_error:
device_errors.append("Following Error")
if server.telemetry.application_status & Rotator.ApplicationStatus.EXTEND_LIMIT_SWITCH:
device_errors.append("Forward Limit Switch")
if server.telemetry.application_status & Rotator.ApplicationStatus.RETRACT_LIMIT_SWITCH:
device_errors.append("Reverse Limit Switch")
if server.telemetry.application_status & Rotator.ApplicationStatus.ETHERCAT_PROBLEM:
device_errors.append("Ethercat Error")
if server.telemetry.application_status & Rotator.ApplicationStatus.SIMULINK_FAULT:
device_errors.append("Simulink Error")
if server.telemetry.application_status & Rotator.ApplicationStatus.ENCODER_FAULT:
device_errors.append("Encoder Error")
device_error_code = ",".join(device_errors)
self.evt_deviceError.set_put(
code=device_error_code,
device="Rotator",
severity=1 if device_error_code else 0,
self.evt_tracking.set_put(
tracking=server.telemetry.flags_tracking_success,
lost=server.telemetry.flags_tracking_lost,
)

if server.telemetry.flags_tracking_success != self._prev_flags_tracking_success:
self._prev_flags_tracking_success = server.telemetry.flags_tracking_success
if server.telemetry.flags_tracking_success:
self.evt_tracking.put()

if server.telemetry.flags_tracking_lost != self._prev_flags_tracking_lost:
self._prev_flags_tracking_lost = server.telemetry.flags_tracking_lost
if server.telemetry.flags_tracking_lost:
self.evt_trackLost.set_put()

safety_interlock = server.telemetry.application_status & Rotator.ApplicationStatus.SAFTEY_INTERLOCK
self.evt_interlock.set_put(
detail="Engaged" if safety_interlock else "Disengaged",
Expand Down