-
Notifications
You must be signed in to change notification settings - Fork 0
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
custom device tutorial #42
Comments
move to next milestone -- might be able to satisfy this by pointing to existing examples |
Suppose you have an EPICS database and IOC that creates these PVs: To create ophyd code for Bluesky, this is a starting template: from ophyd import Component, Device, EpicsSignal
class MyUserThing(Device):
pv1 = Component(EpicsSignal, "pv1")
pv2 = Component(EpicsSignal, "pv2")
pv3 = Component(EpicsSignal, "pv3")
# create the Python object:
thing = MyUserThing("thing:", name="thing") This connects PV That's the general picture. There are details about when to use kind="config" and when to subclass from PvPositioner and the like. But if you keep the interface simple, this is the outline to follow. Make change as your skills allow. |
One variation might be recognizing that all of the PVs are the same EPICS record type, such as EPICS from apstools.synApps import EpicsRecordDeviceCommonAll
from apstools.synApps import EpicsRecordFloatFields
from ophyd import Component, Device, EpicsSignal
class EpicsAoRecord(EpicsRecordFloatFields, EpicsRecordDeviceCommonAll):
value = Component(EpicsSignal, ".VAL")
class MyUserThing(Device):
pv1 = Component(EpicsAoRecord, "pv1")
pv2 = Component(EpicsAoRecord, "pv2")
pv3 = Component(EpicsAoRecord, "pv3")
# create the Python object:
thing = MyUserThing("thing:", name="thing") This gives you many, many additional fields with standard names, such as: description = Component(EpicsSignal, ".DESC", kind="config")
processing_active = Component(EpicsSignalRO, ".PACT", kind="omitted")
scanning_rate = Component(EpicsSignal, ".SCAN", kind="config")
disable_value = Component(EpicsSignal, ".DISV", kind="config")
scan_disable_input_link_value = Component(EpicsSignal, ".DISA", kind="config")
scan_disable_value_input_link = Component(EpicsSignal, ".SDIS", kind="config")
process_record = Component(EpicsSignal, ".PROC", kind="omitted", put_complete=True)
forward_link = Component(EpicsSignal, ".FLNK", kind="config")
trace_processing = Component(EpicsSignal, ".TPRO", kind="omitted")
device_type = Component(EpicsSignalRO, ".DTYP", kind="config")
alarm_status = Component(EpicsSignalRO, ".STAT", kind="config")
alarm_severity = Component(EpicsSignalRO, ".SEVR", kind="config")
new_alarm_status = Component(EpicsSignalRO, ".NSTA", kind="config")
new_alarm_severity = Component(EpicsSignalRO, ".NSEV", kind="config")
disable_alarm_severity = Component(EpicsSignal, ".DISS", kind="config")
units = Component(EpicsSignal, ".EGU", kind="config")
precision = Component(EpicsSignal, ".PREC", kind="config")
monitor_deadband = Component(EpicsSignal, ".MDEL", kind="config") |
A realistic demonstration might be a heater controller simulation, such as epics-modules/optics#10 (comment) (which needs some updates). |
Could start with more basic examples. Make note of common prefix. Reasons for a custom Device
Here are examples from 2019 slides: Groupings - Neat Stage 3IDDclass NeatStage_3IDD(Device):
x = Component(EpicsMotor, "m1", labels=("NEAT stage",))
y = Component(EpicsMotor, "m2", labels=("NEAT stage",))
theta = Component(EpicsMotor, "m3", labels=("NEAT stage",))
neat_stage = NeatStage_3IDD("3idd:", name="neat_stage") APS undulator support uses this pattern. class ApsUndulator(Device):
"""
APS Undulator
EXAMPLE::
undulator = ApsUndulator("ID09ds:", name="undulator")
"""
energy = Component(
EpicsSignal, "Energy", write_pv="EnergySet", put_complete=True, kind="hinted",
)
energy_taper = Component(
EpicsSignal, "TaperEnergy", write_pv="TaperEnergySet", kind="config",
)
gap = Component(EpicsSignal, "Gap", write_pv="GapSet")
gap_taper = Component(
EpicsSignal, "TaperGap", write_pv="TaperGapSet", kind="config"
)
start_button = Component(EpicsSignal, "Start", put_complete=True, kind="omitted")
stop_button = Component(EpicsSignal, "Stop", kind="omitted")
harmonic_value = Component(EpicsSignal, "HarmonicValue", kind="config")
gap_deadband = Component(EpicsSignal, "DeadbandGap", kind="config")
device_limit = Component(EpicsSignal, "DeviceLimit", kind="config")
# ... more Devices can be nested. For example, the dual undulator is a Device that contains an upstream ( class ApsUndulatorDual(Device):
upstream = Component(ApsUndulator, "us:")
downstream = Component(ApsUndulator, "ds:") Aggregate custom data - User Infoclass ExperimentInfo(Device): # from the APS General User Proposal system
GUP_number = Component(EpicsSignalRO, "ProposalNumber", string=True)
title = Component(EpicsSignalRO, "ProposalTitle", string=True)
user_name = Component(EpicsSignalRO, "UserName", string=True)
user_institution = Component(EpicsSignalRO, "UserInstitution", string=True)
user_badge_number = Component(EpicsSignalRO, "UserBadge", string=True)
user_info = ExperimentInfo("2bmS1:", name="user_info") Modify a standard deviceSometimes, a standard device is missing a feature, such as connection with an additional field in an EPICS record. For example, the class MyEpicsMotor(EpicsMotor):
steps_per_revolution = Component(EpicsSignal, ".SREV", kind="omitted") Also see
MixinSee the |
Start with the Hello, World! example, which is pure ophyd (does not involve EPICS). |
Connect EPICS contains another grouping example, which also describes the |
Form follows function: Integral to the implementation of a custom |
This is really a howto. Moving to that section. |
The document has more depth than a HowTo. Moving back to tutorials. |
Might be good to start with a FAQ. |
per BCDA-APS/use_bluesky#29
The text was updated successfully, but these errors were encountered: