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

Add PVPositionerSoftDone #531

Merged
merged 16 commits into from
Oct 8, 2021
Merged

Add PVPositionerSoftDone #531

merged 16 commits into from
Oct 8, 2021

Conversation

gfabbris
Copy link
Collaborator

@gfabbris gfabbris commented Sep 3, 2021

The PVPositionerSoftDone is basically the same as ophyd.PVPositioner that uses a soft (instead of EPICS) done signal. A few other (current) differences:

  • There is an option to to initialize it with the readback and setpoint PVs:
example = PVPositionerSoftDone("prefixPV:", readback_pv="myreadbackPV", setpoint_pv="mysetpointPV", name="example")

where, for instance, the readback PV is "prefixPV:myreadbackPV". The advantage of using this is that the readback/setpoint signals are already marked as hinted/normal and forces auto monitor and PV completion. Alternatively, you can still do something like:

class MyPVPositionerSoftDone(PVPositionerSoftDone):
    readback = Component(EpicsSignal, "myreadbackPV")
    setpoint = Component(EpicsSignal, "mysetpointPV")

example = MyPVPositionerSoftDone("prefixPV:", name="example")
  • The tolerance can be set on startup or after:
example = PVPositionerSoftDone(
    "prefixPV:",
    readback_pv="myreadbackPV",
    setpoint_pv="mysetpointPV",
    tolerance = 0.1,
    name="example")

or

example = PVPositionerSoftDone(
    "prefixPV:",
    readback_pv="myreadbackPV",
    setpoint_pv="mysetpointPV",
    name="example")
example.tolerance.put(0.1)

If not set, it will use 10^(-1*precision), where the setpoint precision is used.

  • For certain EPICS devices (like the lakeshore 340) if a ramp rate is used, the setpoint signal is slowly changed, which the PVPositionerSoftDone will recognize as a "done" motion before it's truly done. To fix this, you can define a dummy target signal:
class MyPVPositionerSoftDone(PVPositionerSoftDone):
    target = Component(Signal, value = 0, kind="omitted")

example = MyPVPositionerSoftDone(
    "prefixPV:",
    readback_pv="myreadbackPV",
    setpoint_pv="mysetpointPV",
    target_attr = "target",
    name="example"
)

Closes #530.

@gfabbris gfabbris self-assigned this Sep 3, 2021
@prjemian prjemian added this to the 1.5.2 milestone Sep 16, 2021
@prjemian
Copy link
Contributor

I really want unit testing for this support but this project does not have an EPICS IOC available until #235.

@prjemian
Copy link
Contributor

One way to apply a functional test (to evaluate this PR independently) is to configure one of the synApps userCalcs (swait records) as a simulated temperature controller, such as in the APS Bluesky training instrument

swait configuration details
name PV reference value
calc8.alarm_severity gp:userCalc8.SEVR 0
calc8.alarm_status gp:userCalc8.STAT 0
calc8.calculated_value gp:userCalc8.VAL 24.53297474631876
calc8.calculation gp:userCalc8.CALC A+max(-D,min(D,(B-A)))+C*(RNDM-0.5)
calc8.channels.A.input_pv gp:userCalc8.INAN gp:userCalc8.VAL
calc8.channels.A.input_trigger gp:userCalc8.INAP 1
calc8.channels.A.input_value gp:userCalc8.A 24.854558632791637
calc8.channels.B.input_pv gp:userCalc8.INBN
calc8.channels.B.input_trigger gp:userCalc8.INBP 1
calc8.channels.B.input_value gp:userCalc8.B 25.0
calc8.channels.C.input_pv gp:userCalc8.INCN
calc8.channels.C.input_trigger gp:userCalc8.INCP 1
calc8.channels.C.input_value gp:userCalc8.C 1.0
calc8.channels.D.input_pv gp:userCalc8.INDN
calc8.channels.D.input_trigger gp:userCalc8.INDP 1
calc8.channels.D.input_value gp:userCalc8.D 2.0
calc8.channels.E.input_pv gp:userCalc8.INEN
calc8.channels.E.input_trigger gp:userCalc8.INEP 1
calc8.channels.E.input_value gp:userCalc8.E 1.0
calc8.channels.F.input_pv gp:userCalc8.INFN
calc8.channels.F.input_trigger gp:userCalc8.INFP 1
calc8.channels.F.input_value gp:userCalc8.F 0.0
calc8.channels.G.input_pv gp:userCalc8.INGN
calc8.channels.G.input_trigger gp:userCalc8.INGP 1
calc8.channels.G.input_value gp:userCalc8.G 0.0
calc8.channels.H.input_pv gp:userCalc8.INHN
calc8.channels.H.input_trigger gp:userCalc8.INHP 1
calc8.channels.H.input_value gp:userCalc8.H 0.0
calc8.channels.I.input_pv gp:userCalc8.ININ
calc8.channels.I.input_trigger gp:userCalc8.INIP 1
calc8.channels.I.input_value gp:userCalc8.I 0.0
calc8.channels.J.input_pv gp:userCalc8.INJN
calc8.channels.J.input_trigger gp:userCalc8.INJP 1
calc8.channels.J.input_value gp:userCalc8.J 0.0
calc8.channels.K.input_pv gp:userCalc8.INKN
calc8.channels.K.input_trigger gp:userCalc8.INKP 1
calc8.channels.K.input_value gp:userCalc8.K 0.0
calc8.channels.L.input_pv gp:userCalc8.INLN
calc8.channels.L.input_trigger gp:userCalc8.INLP 1
calc8.channels.L.input_value gp:userCalc8.L 0.0
calc8.description gp:userCalc8.DESC temperature
calc8.device_type gp:userCalc8.DTYP 0
calc8.disable_alarm_severity gp:userCalc8.DISS 0
calc8.disable_value gp:userCalc8.DISV 0
calc8.event_to_issue gp:userCalc8.OEVT 0
calc8.forward_link gp:userCalc8.FLNK 0
calc8.high_operating_range gp:userCalc8.HOPR 0.0
calc8.low_operating_range gp:userCalc8.LOPR 0.0
calc8.new_alarm_severity gp:userCalc8.NSEV 0
calc8.new_alarm_status gp:userCalc8.NSTA 0
calc8.output_data_option gp:userCalc8.DOPT 0
calc8.output_execute_option gp:userCalc8.OOPT 0
calc8.output_execution_delay gp:userCalc8.ODLY 0.0
calc8.output_link_pv gp:userCalc8.OUTN
calc8.output_location_data gp:userCalc8.DOLD 0.0
calc8.output_location_name gp:userCalc8.DOLN
calc8.precision gp:userCalc8.PREC 5
calc8.process_record gp:userCalc8.PROC 0
calc8.processing_active gp:userCalc8.PACT 0
calc8.scan_disable_input_link_value gp:userCalc8.DISA 1
calc8.scan_disable_value_input_link gp:userCalc8.SDIS gp:userCalcEnable.VAL CA MS
calc8.scanning_rate gp:userCalc8.SCAN 5
calc8.trace_processing gp:userCalc8.TPRO 0
and then check here:
In [6]: tsim = PVPositionerSoftDone("gp:userCalc8", name="tsim", readback_pv=".VAL", setpoint_pv=".B", tolerance=1.0)

In [7]: tsim.read()
Out[7]: 
OrderedDict([('tsim',
              {'value': 29.90983443961242, 'timestamp': 1632159680.426064}),
             ('tsim_setpoint',
              {'value': 29.537445639734493, 'timestamp': 1632159674.42613})])

In [8]: tsim.move(25)
Out[8]: MoveStatus(done=True, pos=tsim, elapsed=2.1, success=True, settle_time=0.0)

In [9]: tsim.move(35)
Out[9]: MoveStatus(done=True, pos=tsim, elapsed=9.8, success=True, settle_time=0.0)

In [10]: tsim.move(25)
Out[10]: MoveStatus(done=True, pos=tsim, elapsed=9.4, success=True, settle_time=0.0)

@prjemian
Copy link
Contributor

The functional test seems about right. Device reported comparable times to raise and lower the (simulated) temperature by the same amount to a fixed tolerance of 1 degree. Given the noise on this signal, a lower tolerance could invoke an indefinite wait.

apstools/devices.py Outdated Show resolved Hide resolved
Motion tolerance. The motion is considered done if
`abs(readback-setpoint) <= tolerance`. Defaults to `10^(-1*precision)`,
where `precision = setpoint.precision`
target_attr : str, optional
Copy link
Contributor

@prjemian prjemian Sep 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the target_attr kwarg be refactored into an always-present attribute named target which is initially set to None? Means more logic changes below but less likely to be misconfigured (or means more easily understood) by end-user of this device.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the 530-target branch

@prjemian
Copy link
Contributor

Also, complexity of devices.py is becoming unmanageable. Starting a pattern with other modules (such as utils) to place new support in a sub-package (such as _utils) and import upwards. Will refactor here for new _devices sub-package. I expect this will make code maintenance easier.

@lgtm-com
Copy link

lgtm-com bot commented Sep 20, 2021

This pull request introduces 2 alerts when merging 486e7bd into 40180d1 - view on LGTM.com

new alerts:

  • 2 for Unused import

@prjemian
Copy link
Contributor

prjemian commented Oct 4, 2021

This is a candidate for unit testing with an EPICS IOC (#235).

@prjemian
Copy link
Contributor

prjemian commented Oct 4, 2021

Needs to resolve merge conflicts with main branch.

Copy link
Contributor

@prjemian prjemian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gfabbris Thanks for the contribution!

@prjemian prjemian merged commit b8bdf64 into main Oct 8, 2021
@prjemian prjemian deleted the 530-PVPositionerSoftDone branch October 8, 2021 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PvPositioner with soft done signal
2 participants