Signal Classes
AggregateSignal
AggregateSignal
is a signal class that allows you to calculate a
readback value from multiple other signals.
Note
While this class requires subclassing in order to use, see also the
MultiDerivedSignal
described below which can be more easily
embedded in Device class hierarchies.
AggregateSignal handles configuring callbacks for you such that you will only need to define a single calculation function in order to use it.
In your subclass, you should define a method such as:
def _calc_readback(self):
"""The calculation method you should implement."""
# Access `._signals` here to calculate your value.
return 10.0
The _signals
attribute includes access to all signals you have defined
in the attribute list. You should use this dictionary in order to calculate
the aggregated value.
Why not just use self.parent.cpt.get()?
The AggregateSignal handles caching of values from callbacks. It will only call your calculation method once everything is ready.
Your signal should perform better and faster. Network connections and EPICS Channel Access calls will not be necessary when using the provided cache.
Example
The following example defines a new Signal class called MySummingSignal
which will sum its sibling component values “a” “b” and “c”. This assumes
it’s used in a Device hierarchy with those components defined appropriately.
class MySummingSignal(AggregateSignal):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for attr_name in ["a", "b", "c"]:
self.add_signal_by_attr_name(attr_name)
def _calc_readback(self):
return sum(sig.value for sig in self._signals.values())
class MyDevice(ophyd.Device):
a = Cpt(EpicsSignalRO, "A")
b = Cpt(EpicsSignalRO, "B")
c = Cpt(EpicsSignalRO, "C")
summer = Cpt(MySummingSignal)
# summer.get() is a.get() + b.get() + c.get()
AvgSignal
AvgSignal
is a signal that calculates the rolling average of another
signal.
It uses ophyd subscriptions to populate a list of values of length average
.
Example
Acquire up to 10 data points from raw_signal
, perform the arithmetic mean,
and show its value in averaged
:
class MyDevice(ophyd.Device):
raw_signal = Cpt(EpicsSignalRO, "OTHER:SIGNAL")
averaged = Cpt(AvgSignal, signal="raw_signal", averages=10)
For this setting, after 10 data points, the first data point will be overwritten.
PVStateSignal
PVStateSignal
is a signal class that implements the
PVStatePositioner
logic. It’s part of that class hierarchy, and
you should not have to use it yourself.
It uses AggregateSignal
in order to match up attributes from the
Device and map their values onto state positions. A sample dictionary
that is usually in sig._state_logic
might be:
{
"signal_name": {
0: "OUT",
1: "IN",
2: "Unknown",
3: "defer"
}
}
For more information, see :clas:`PVStatePositioner`.
PytmcSignal
Note
Use this signal class if you are using [pytmc](https://github.com/pcdshub/pytmc/) in your device’s corresponding EPICS IOC.
A PytmcSignal
uses information about the pytmc convention to determine
which signal class should be used automatically (either PytmcSignalRW
and PytmcSignalRO
).
A symbol marked as “input” (or “ro”) will have
"_RBV"
as a PV suffix. This will be mapped onto thePytmcSignalRW
signal class.A symbol marked as “output” (or “rw”) will both a setpoint PV and a readback PV, with
"_RBV"
as its suffix. This will be mapped onto thePytmcSignalRO
signal class.
Usage examples are in the following sections.
PytmcSignal for Outputs
Let’s say you have a pragma as follows:
{attribute 'pytmc' := '
pv: PREFIX:fValue
io: output
'}
fValue: LREAL;
If found in a PLC program, this would create two top-level PVs:
PREFIX:fValue
- a setpoint value the user could caput to change the value of
fValue on the PLC
PREFIX:fValue_RBV
- a “Read-Back Value” (RBV) that reflects the current value held in fValue on the PLC.
You would use the following component to match this:
class MyDevice(ophyd.Device):
value = ophyd.Component(
PytmcSignal,
":fValue", # <-- copy in ":fValue" from above
io="output", # <-- copy in "output" from above
kind="normal",
doc="Something useful about the 'fValue' signal"
)
dev = MyDevice("PREFIX", name="dev")
Once instantiated, your device will have a readable and writable
PytmcSignalRW
instance available from the .value
attribute that talks
to the PLC fValue
symbol.
The corresponding readback PV will be PREFIX:fValue_RBV
(which you can
check with dev.value.pvname
) and the setpoint PV will be PREFIX:fValue
(which you can check with dev.value.setpoint_pvname
).
PytmcSignal for Inputs
Let’s say you have a pragma as follows:
{attribute 'pytmc' := '
pv: PREFIX:fValue
io: input
'}
fValue: LREAL;
If found in a program, this would create a single top-level PVs:
PREFIX:fValue
- a “read-back value” that reflects the current value held in fValue on the PLC.
You would use the following component to match this:
class MyDevice(ophyd.Device):
value = ophyd.Component(
PytmcSignal,
":fValue", # <-- copy in ":fValue" from above
io="input", # <-- copy in "input" from above
kind="normal",
doc="Something useful about the 'fValue' signal"
)
dev = MyDevice("PREFIX", name="dev")
Once instantiated, your device will have a read-only PytmcSignalRO
instance
available from the .value
attribute that talks to the PLC fValue
symbol. The corresponding PV will be PREFIX:fValue_RBV
(which you can check
with dev.value.pvname
).
MultiDerivedSignal and MultiDerivedSignalRO
MultiDerivedSignal
and MultiDerivedSignalRO
are signal
classes that allow you to calculate a readback value from multiple other
signals. Optionally, you can also take a single value from the user and
write to multiple other signals.
In short, these classes represent:
Multiple source signals may be used to calculate a single
MultiDerivedSignal
value. The calculation method used here iscalculate_on_get
in either a keyword argument to the signal or a method in a subclass.A single
MultiDerivedSignal
value, whenset()
, may be used to write to those same signals. The calculation method used here iscalculate_on_put
in either a keyword argument to the signal or a method in a subclass.
A read-only MultiDerivedSignalRO
This read-only example takes three signals from its parent device - “a”, “b”, and “c” - and calculates a single value for them.
from pcdsdevices.type_hints import SignalToValue, OphydDataType
class MdsReadOnlyExample(Device):
def _on_get(self, mds: MultiDerivedSignal, items: SignalToValue) -> int:
return sum(value for value in items.values())
mds = Cpt(
MultiDerivedSignalRO,
attrs=["a", "b", "c"],
calculate_on_get=_on_get,
)
a = Cpt(FakeEpicsSignal, "a")
b = Cpt(FakeEpicsSignal, "b")
c = Cpt(FakeEpicsSignal, "c")
>>> device.a.get()
1
>>> device.b.get()
2
>>> device.c.get()
3
>>> device.mds.get()
6
A read-write MultiDerivedSignal
class MdsReadWriteExample(Device):
def _on_get(self, mds: MultiDerivedSignal, items: SignalToValue) -> int:
return sum(value for value in items.values())
def _on_put(self, mds: MultiDerivedSignal, value: OphydDataType) -> SignalToValue:
to_write = float(value / 3.)
return {
self.parent.a: to_write,
self.parent.b: to_write,
self.parent.c: to_write,
}
mds = Cpt(
MultiDerivedSignal,
attrs=["a", "b", "c"],
calculate_on_get=_on_get,
calculate_on_put=_on_put,
)
a = Cpt(FakeEpicsSignal, "a")
b = Cpt(FakeEpicsSignal, "b")
c = Cpt(FakeEpicsSignal, "c")
>>> device.a.get()
1
>>> device.b.get()
2
>>> device.c.get()
3
>>> device.mds.get()
6
>> device.mds.set(24).wait()
>>> device.a.get()
8
>>> device.b.get()
8
>>> device.c.get()
8
UnitConversionDerivedSignal
UnitConversionDerivedSignal
is a derived signal class (i.e., one that
sources its information from another existing signal) which allows you to
change the units of the source signal into the units of your choice.
UnitConversionDerivedSignal Example
class UcdsExample(Device):
original = Cpt(
EpicsSignal,
"PV",
doc="PV with units of mm"
)
converted = Cpt(
UnitConversionDerivedSignal,
derived_from=orig,
original_units='mm',
derived_units='m',
doc="Converted signal with units of 'm'"
)
>>> device.original.get()
10
>>> device.converted.get()
0.01
Put 0.1m and note that it’s 100mm:
>> device.converted.put(0.1)
>>> device.original.get()
100
Advanced Signal Types
There are additional signal types which will not be discussed in depth here because their usage is uncommon or advanced. For more details, see the associated source code.
Fake Signal Types
Fake signal classes are used in the test suite. For each of the signal classes here, we provide a “fake” version of them.
Users should mostly be concerned with using make_fake_device
on the
whole device and should not need to concern themselves with the details of
the fake signals directly.
InternalSignal
InternalSignal
is a signal that is intended to be used internally
by a class and not presented to the end-user. Similar to NotImplementedSignal,
it is generally an advanced signal type.
NotepadLinkedSignal
Creates a notepad metadata dict for usage by pcdsdevices-notepad.
NotImplementedSignal
NotImplementedSignal
is primarily a placeholder for when you are
creating abstract ophyd device classes with the intent of subclassing and
updating them by way of UpdateComponent
.
If the above doesn’t make sense to you, you are probably not the target audience of this. Consider this an “advanced” signal type.
SignalEditMD, and EpicsSignalEditMD and EpicsSignalROEditMD
SignalEditMD
, and EpicsSignalEditMD
and
EpicsSignalROEditMD
are variants of a signal type that allow you
to edit metadata coming from an existing signal. This metadata
can include timestamps, units, enum information, and so on.