refactor(psi-motor): Review signal kinds, add custom limit signal

This commit is contained in:
2025-08-08 08:58:12 +02:00
committed by Christian Appel
parent b787b428b3
commit 7bb610394e

View File

@@ -22,7 +22,9 @@ class SpmgStates:
class EpicsMotor(OphydEpicsMotor): class EpicsMotor(OphydEpicsMotor):
"""Extended EPICS Motor class """
Extended EPICS Motor class for PSI motors.
Special motor class that exposes additional motor record functionality. Special motor class that exposes additional motor record functionality.
It extends EpicsMotor base class to provide some simple status checks It extends EpicsMotor base class to provide some simple status checks
@@ -32,7 +34,7 @@ class EpicsMotor(OphydEpicsMotor):
tolerated_alarm = AlarmSeverity.INVALID tolerated_alarm = AlarmSeverity.INVALID
motor_deadband = Cpt( motor_deadband = Cpt(
EpicsSignal, ".RDBD", auto_monitor=True, kind=Kind.config, doc="Retry Deadband (EGU)" EpicsSignal, ".RDBD", auto_monitor=True, kind=Kind.omitted, doc="Retry Deadband (EGU)"
) )
motor_mode = Cpt( motor_mode = Cpt(
EpicsSignal, EpicsSignal,
@@ -77,6 +79,34 @@ class EpicsMotor(OphydEpicsMotor):
return super().move(position, wait, **kwargs) return super().move(position, wait, **kwargs)
class TravelLimitsECMC(EpicsSignal):
"""
Custom EpicsSignal for ECMC travel limits.
"""
def put(self, value, use_complete: bool = True, **kwargs):
"""
Override put method to handle travel limits.
This method allows setting the travel limits for the ECMC motor.
It ensures that the value is within the acceptable range before
sending it to the EPICS server.
"""
# Get first the current value
old_value = self.get()
if not use_complete:
use_complete = True
self.log.warning(f"Overruling use_complete for {self.pvname} to True")
# Set the value with put_complete
super().put(value, use_complete=use_complete, **kwargs)
# Now check if the PLC layer accepted the value change
new_value = self.get(auto_monitor=False)
if new_value != value:
raise ValueError(
f"Failed to set travel limit: {value}. Update was rejected by PLC. Limit remains {new_value}."
)
class EpicsMotorEC(EpicsMotor): class EpicsMotorEC(EpicsMotor):
"""Detailed ECMC EPICS motor class """Detailed ECMC EPICS motor class
@@ -90,7 +120,7 @@ class EpicsMotorEC(EpicsMotor):
EpicsSignalRO, EpicsSignalRO,
"-EnaAct", "-EnaAct",
auto_monitor=True, auto_monitor=True,
kind=Kind.normal, kind=Kind.config,
doc="[ECMC] Reflects whether the axis is enabled in the hardware level.", doc="[ECMC] Reflects whether the axis is enabled in the hardware level.",
) )
motor_enable = Cpt( motor_enable = Cpt(
@@ -99,15 +129,17 @@ class EpicsMotorEC(EpicsMotor):
write_pv="-EnaCmd", write_pv="-EnaCmd",
put_complete=True, put_complete=True,
auto_monitor=True, auto_monitor=True,
kind=Kind.normal, kind=Kind.omitted,
doc="[ECMC] Send enable/disable command to hardware.", doc="[ECMC] Send enable/disable command to hardware.",
) )
homed = Cpt(EpicsSignalRO, "-Homed", auto_monitor=True, kind=Kind.normal) homed = Cpt(
EpicsSignalRO, "-Homed", auto_monitor=True, kind=Kind.omitted, doc="[ECMC] Homed status"
)
velocity_readback = Cpt( velocity_readback = Cpt(
EpicsSignalRO, EpicsSignalRO,
"-VelAct", "-VelAct",
auto_monitor=True, auto_monitor=True,
kind=Kind.normal, kind=Kind.config, # I would think this depends on the beamline? normal or config
doc="[ECMC] Velocity readback", doc="[ECMC] Velocity readback",
) )
position_readback = Cpt( position_readback = Cpt(
@@ -121,14 +153,18 @@ class EpicsMotorEC(EpicsMotor):
EpicsSignalRO, "-PosErr", auto_monitor=True, kind=Kind.normal, doc="[ECMC] Position error" EpicsSignalRO, "-PosErr", auto_monitor=True, kind=Kind.normal, doc="[ECMC] Position error"
) )
# Virtual motor and temperature limits are interlocks # Virtual motor and temperature limits are interlocks
high_interlock = Cpt(EpicsSignalRO, "-SumIlockFwd", auto_monitor=True, kind=Kind.normal) high_interlock = Cpt(EpicsSignalRO, "-SumIlockFwd", auto_monitor=True, kind=Kind.omitted)
low_interlock = Cpt(EpicsSignalRO, "-SumIlockBwd", auto_monitor=True, kind=Kind.normal) low_interlock = Cpt(EpicsSignalRO, "-SumIlockBwd", auto_monitor=True, kind=Kind.omitted)
error = Cpt(EpicsSignalRO, "-ErrId", auto_monitor=True, kind=Kind.normal) error = Cpt(
EpicsSignalRO, "-ErrId", auto_monitor=True, kind=Kind.omitted, doc="[ECMC] Error ID"
)
error_msg = Cpt( error_msg = Cpt(
EpicsSignalRO, "-MsgTxt", auto_monitor=True, kind=Kind.normal, doc="[ECMC] Error message" EpicsSignalRO, "-MsgTxt", auto_monitor=True, kind=Kind.omitted, doc="[ECMC] Error message"
) )
error_reset = Cpt(EpicsSignal, "-ErrRst", put_complete=True, kind=Kind.omitted) error_reset = Cpt(EpicsSignal, "-ErrRst", put_complete=True, kind=Kind.omitted)
high_limit_travel = Cpt(TravelLimitsECMC, ".HLM", kind="omitted", auto_monitor=True)
low_limit_travel = Cpt(TravelLimitsECMC, ".LLM", kind="omitted", auto_monitor=True)
def move(self, position, wait=True, **kwargs) -> MoveStatus: def move(self, position, wait=True, **kwargs) -> MoveStatus:
"""Extended move function with a few sanity checks """Extended move function with a few sanity checks
@@ -167,5 +203,9 @@ class EpicsMotorEC(EpicsMotor):
# Reset the error # Reset the error
self.error_reset.set(1, settle_time=0.2).wait() self.error_reset.set(1, settle_time=0.2).wait()
# Check if it disappeared # Check if it disappeared
if self.error.get(): error = self.error.get(auto_monitor=False)
raise RuntimeError(f"Failed to reset axis, still in error: '{self.error_msg.get()}'") if error:
error_msg = self.error_msg.get(auto_monitor=False)
raise RuntimeError(
f"Failed to reset axis, still in error with id: '{error}, msg: {error_msg}'"
)