示例#1
0
def test_standalone_signals():
    # A object's kind only matters when we read its _parent_. It affects
    # whether its parent recursively calls read() and/or read_configuration().
    # When we call read() and/or read_configuration() on the object itself,
    # directly, and its 'kind' setting does not affects its behavior.
    normal_sig = Signal(name='normal_sig', value=3, kind=Kind.normal)
    assert normal_sig.kind == Kind.normal
    assert normal_sig.name not in normal_sig.hints['fields']
    assert 'normal_sig' in normal_sig.read()
    assert 'normal_sig' in normal_sig.read_configuration()

    hinted_sig = Signal(name='hinted_sig', value=3)
    assert hinted_sig.kind == Kind.hinted
    assert hinted_sig.name in hinted_sig.hints['fields']
    assert 'hinted_sig' in hinted_sig.read()
    assert 'hinted_sig' in hinted_sig.read_configuration()

    # Same with a sig set up this way
    config_sig = Signal(name='config_sig', value=5, kind=Kind.config)
    assert config_sig.kind == Kind.config
    assert config_sig.name not in config_sig.hints['fields']
    assert 'config_sig' in config_sig.read()
    assert 'config_sig' in config_sig.read_configuration()

    # Same with a sig set up this way
    omitted_sig = Signal(name='omitted_sig', value=5, kind=Kind.omitted)
    assert omitted_sig.kind == Kind.omitted
    assert omitted_sig.name not in omitted_sig.hints['fields']
    assert 'omitted_sig' in omitted_sig.read()
    assert 'omitted_sig' in omitted_sig.read_configuration()
示例#2
0
def test_signal_connection(qapp):
    # Create a signal and attach our listener
    sig = Signal(name='my_signal', value=1)
    register_signal(sig)
    widget = WritableWidget()
    listener = widget.channels()[0]
    sig_conn = SignalConnection(listener, 'my_signal')
    sig_conn.add_listener(listener)
    # Check that our widget receives the initial value
    qapp.processEvents()
    assert widget._write_access
    assert widget._connected
    assert widget.value == 1
    # Check that we can push values back to the signal which in turn causes the
    # internal value at the widget to update
    widget.send_value_signal[int].emit(2)
    qapp.processEvents()
    qapp.processEvents()  # Must be called twice. Multiple rounds of signals
    assert sig.get() == 2
    assert widget.value == 2
    # Try changing types
    qapp.processEvents()
    qapp.processEvents()  # Must be called twice. Multiple rounds of signals
    sig_conn.remove_listener(listener)
    # Check that our signal is disconnected completely and maintains the same
    # value as the signal updates in the background
    sig.put(3)
    qapp.processEvents()
    assert widget.value == 2
    widget.send_value_signal.emit(1)
    qapp.processEvents()
    assert sig.get() == 3
示例#3
0
def test_array_signal_send_value(qapp, qtbot):
    sig = Signal(name='my_array', value=np.ones(4))
    register_signal(sig)
    widget = PyDMLineEdit()
    qtbot.addWidget(widget)
    widget.channel = 'sig://my_array'
    qapp.processEvents()
    assert all(widget.value == np.ones(4))
示例#4
0
def test_array_signal_put_value(qapp, qtbot):
    sig = Signal(name='my_array_write', value=np.ones(4))
    register_signal(sig)
    widget = PyDMLineEdit()
    qtbot.addWidget(widget)
    widget.channel = 'sig://my_array_write'
    widget.send_value_signal[np.ndarray].emit(np.zeros(4))
    qapp.processEvents()
    assert all(sig.value == np.zeros(4))
示例#5
0
def test_evil_table_names(RE):
    from ophyd import Signal
    sigs = [
        Signal(value=0, name="a:b"),
        Signal(value=0, name="a,b"),
        Signal(value=0, name="a'b"),
        Signal(value=0, name="🐍"),
    ]
    table = LiveTable([s.name for s in sigs],
                      min_width=5,
                      extra_pad=2,
                      separator_lines=False)
    with _print_redirect() as fout:
        print()  # get a blank line in camptured output
        RE(bpp.subs_wrapper(bp.count(sigs, num=2), table))
    reference = """
+------------+--------------+--------+--------+--------+--------+
|   seq_num  |        time  |   a:b  |   a,b  |   a'b  |     🐍  |
+------------+--------------+--------+--------+--------+--------+
|         1  |  12:47:09.7  |     0  |     0  |     0  |     0  |
|         2  |  12:47:09.7  |     0  |     0  |     0  |     0  |
+------------+--------------+--------+--------+--------+--------+"""
    _compare_tables(fout, reference)
示例#6
0
def test_plot_ints(RE):
    from ophyd import Signal
    from bluesky.callbacks.best_effort import BestEffortCallback
    from bluesky.plans import count
    import bluesky.plan_stubs as bps

    bec = BestEffortCallback()
    RE.subscribe(bec)

    s = Signal(name='s')
    RE(bps.mov(s, int(0)))
    assert s.describe()['s']['dtype'] == 'integer'
    s.kind = 'hinted'
    with pytest.warns(None) as record:
        RE(count([s], num=35))

    assert len(record) == 0
示例#7
0
def mono_vernier_scan(
    *, ev_bounds=None, urad_bounds=None, duration,
    fake_mono=False, fake_vernier=False, fake_daq=False,
    ):
    """
    WARNING: this scan CANNOT be safely inspected! Only pass into RE!

    Parameters
    ----------
    ev_bounds: list of numbers, keyword-only
        Upper and lower bounds of the scan in ev.
        Provide either ev_bounds or urad_bounds, but not both.

    urad_bounds: list of numbers, keyword-only
        Upper and lower bounds of the scan in mono grating urad.
        Provide either ev_bounds or urad_bounds, but not both.

    duration: number, required keyword-only
        Duration of the scan in seconds.
    """
    setup_scan_devices()
    if fake_mono:
        mono_grating = FixSlowMotor(name='fake_mono')
    else:
        mono_grating = scan_devices.mono_grating
    if fake_vernier:
        vernier = Signal(name='fake_vernier')
    else:
        vernier = scan_devices.energy_req
    if fake_daq:
        inner_scan = interpolation_mono_vernier_duration_scan
    else:
        inner_scan = daq_interpolation_mono_vernier_duration_scan
    return (yield from inner_scan(
        mono_grating,
        vernier,
        scan_devices.config,
        ev_bounds=ev_bounds,
        urad_bounds=urad_bounds,
        duration=duration
        )
    )
示例#8
0
def test_signal_connection(qapp, qtbot):
    # Create a signal and attach our listener
    sig = Signal(name='my_signal', value=1)
    register_signal(sig)
    widget = PyDMLineEdit()
    qtbot.addWidget(widget)
    widget.channel = 'sig://my_signal'
    listener = widget.channels()[0]
    # If PyDMChannel can not connect, we need to connect it ourselves
    # In PyDM > 1.5.0 this will not be neccesary as the widget will be
    # connected after we set the channel name
    if not hasattr(listener, 'connect'):
        pydm.utilities.establish_widget_connections(widget)
    # Check that our widget receives the initial value
    qapp.processEvents()
    assert widget._write_access
    assert widget._connected
    assert widget.value == 1
    # Check that we can push values back to the signal which in turn causes the
    # internal value at the widget to update
    widget.send_value_signal[int].emit(2)
    qapp.processEvents()
    qapp.processEvents()  # Must be called twice. Multiple rounds of signals
    assert sig.get() == 2
    assert widget.value == 2
    # Try changing types
    qapp.processEvents()
    qapp.processEvents()  # Must be called twice. Multiple rounds of signals
    # In PyDM > 1.5.0 we will not need the application to disconnect the
    # widget, but until then we have to check for the attribute
    if hasattr(listener, 'disconnect'):
        listener.disconnect()
    else:
        qapp.close_widget_connections(widget)
    # Check that our signal is disconnected completely and maintains the same
    # value as the signal updates in the background
    sig.put(3)
    qapp.processEvents()
    assert widget.value == 2
    widget.send_value_signal.emit(1)
    qapp.processEvents()
    assert sig.get() == 3
示例#9
0
def documentation_run(words, md=None, stream=None):
    """
    Save text as a bluesky run.
    """
    text = Signal(value=words, name="text")
    stream = stream or "primary"
    _md = dict(
        purpose=f"save text as bluesky run",
        plan_name="documentation_run",
    )
    _md.update(md or {})
    bec.disable_plots()
    bec.disable_table()
    uid = yield from bps.open_run(md=_md)
    yield from bps.create(stream)
    yield from bps.read(text)
    yield from bps.save()
    yield from bps.close_run()
    bec.enable_table()
    bec.enable_plots()
    return uid
示例#10
0
def gisaxs_scan(dets=[pil300KW, pil1M],
                trajectory='None',
                measurement_time=1,
                number_images=1,
                user_name='GF',
                md=None):

    #Pull out the motor names from a cycler
    #ToDo: This can be improved
    motor_names = []
    for trajs in trajectory:
        for traj in trajs.items():
            if traj[0].name not in motor_names:
                motor_names.append(traj[0].name)

    # Check if what is planned is doable
    try:
        motor_names
        [det for det in dets]
    except:
        raise Exception('Motors or detectors not known')

    # Fixed values. If this values change over a scan, possibility to transform in ophyd signal and record over a scan
    base_md = {
        'plan_name': 'gi_scan',
        'geometry': 'reflection',
        'detectors': [det.name for det in dets],
        'user_name': user_name,
        'motor_scanned': motor_names,
        'exposure_time': measurement_time,
        'number_image': number_images,
    }

    base_md.update(md or {})

    all_detectors = dets  # Start a list off all the detector to trigger
    all_detectors.append(xbpm2)  # add all the values to track  for analysis
    all_detectors.append(xbpm3)  # add all the values to track  for analysis
    all_detectors.append(
        ring.current)  # add all the values to track  for analysis

    sd.baseline = []  # Initializatin of the baseline
    SMI.update_md()  # update metadata from the beamline

    # Update metadata for the detectors
    if 'pil300KW' in [det.name for det in dets]:
        all_detectors.append(
            waxs)  # Record the position of the WAXS arc and beamstop
        sd.baseline.append(smi_waxs_detector
                           )  # Load metadata of WAXS detector in the baseline

    if 'pil1M' in [det.name for det in dets]:
        SMI_SAXS_Det()  # Update metadata from the beamline
        # Nothing added as detector so far since the saxs detector is not moved but anything could be implemented
        sd.baseline.append(smi_saxs_detector
                           )  # Load metadata of SAXS detector in the baseline

    if 'rayonix' in [det.name for det in dets]:
        print('no metadata for the rayonix yet')

    # Update metadata for motors not used in baseline and add the motor as detector if so
    all_detectors.append(
        piezo) if 'piezo' in motor_names else sd.baseline.append(piezo)
    all_detectors.append(
        stage) if 'stage' in motor_names else sd.baseline.append(stage)
    all_detectors.append(prs) if 'prs' in motor_names else sd.baseline.append(
        prs)
    all_detectors.append(
        energy) if 'energy' in motor_names else sd.baseline.append(energy)
    all_detectors.append(
        waxs) if 'waxs' in motor_names else sd.baseline.append(waxs)
    all_detectors.append(ls) if 'ls' in motor_names else sd.baseline.append(ls)
    '''
    if 'piezo' in [motor_names]:
        all_detectors.append(piezo)
    else:
        sd.baseline.append(piezo)

    if 'stage' in [motor_names]:
        all_detectors.append(stage)
    else:
        sd.baseline.append(stage)

    if 'prs' in [motor_names]:
        all_detectors.append(prs)
    else:
        sd.baseline.append(prs)

    if 'energy' in [motor_names]:
        all_detectors.append(energy)
    else:
        sd.baseline.append(energy)

    if 'waxs' in [motor_names]:
        all_detectors.append(waxs)
        else:
        sd.baseline.append(waxs)
    '''

    sample_na = Signal(name='sample_name', value='test')
    all_detectors.append(sample_na)

    #Set exposure time
    det_exposure_time(measurement_time, number_images * measurement_time)

    bec.disable_plots()
    yield from bp.scan_nd(all_detectors, trajectory, md=base_md)
    bec.enable_plots()

    print('GISAXS scan with metadata done')
示例#11
0
def scan(dets=[pil300KW, pil1M],
         trajectory='None',
         measurement_time=1,
         number_images=1,
         user_name='GF',
         plan_name='gisaxs_scan',
         md=None):
    '''
    Read a trajectory of motors and acquire data at the each position of the trajectory. Data will be save as a databroker document.
    :param dets: list of SMI detector which will acquire data: pil300KW, pil1M, rayonix
    :param trajectory: a cycle trjactory of motor and ophyd signal
    :param measurement_time: (integer) exposure time for 1 image
    :param number_images: (integer) number of images
    :param user_name: (string) user name
    :param plan_name: (string) plan name that will be used for the analysis
    :param md: extra metadata if needed
    '''

    #Pull out the motor names from a cycler
    motor_names = []
    for trajs in trajectory:
        for traj in trajs.items():
            if traj[0].name not in motor_names:
                motor_names.append(traj[0].name)

    # Check if what is planned is doable
    try:
        motor_names
        [det for det in dets]
    except:
        raise Exception('Motors or detectors not known')

    # Fixed values. If this values change over a scan, possibility to transform in ophyd signal and record over a scan
    geometry = 'reflection' if 'gisaxs' in plan_name or 'giwaxs' in plan_name else 'transmission'
    base_md = {
        'plan_name': plan_name,
        'geometry': geometry,
        'detectors': [det.name for det in dets],
        'user_name': user_name,
        'motor_scanned': motor_names,
        'exposure_time': measurement_time,
        'number_image': number_images,
    }

    base_md.update(md or {})

    all_detectors = dets  # Start a list off all the detector to trigger
    all_detectors.append(xbpm2)  # add all the values to track  for analysis
    all_detectors.append(xbpm3)  # add all the values to track  for analysis
    all_detectors.append(
        ring.current)  # add all the values to track  for analysis

    sd.baseline = []  # Initializatin of the baseline
    SMI.update_md()  # update metadata from the beamline

    # Update metadata for the detectors
    if 'pil300KW' in [det.name for det in dets]:
        all_detectors.append(
            waxs)  # Record the position of the WAXS arc and beamstop
        sd.baseline.append(smi_waxs_detector
                           )  # Load metadata of WAXS detector in the baseline

    if 'pil1M' in [det.name for det in dets]:
        SMI_SAXS_Det()  # Update metadata from the beamline
        sd.baseline.append(smi_saxs_detector
                           )  # Load metadata of SAXS detector in the baseline

    if 'rayonix' in [det.name for det in dets]:
        print('no metadata for the rayonix yet')

    # Update metadata for motors not used in baseline and add the motor as detector if so
    all_detectors.append(
        piezo) if 'piezo' in motor_names else sd.baseline.append(piezo)
    all_detectors.append(
        stage) if 'stage' in motor_names else sd.baseline.append(stage)
    all_detectors.append(prs) if 'prs' in motor_names else sd.baseline.append(
        prs)
    all_detectors.append(
        energy) if 'energy' in motor_names else sd.baseline.append(energy)
    all_detectors.append(
        waxs) if 'waxs' in motor_names else sd.baseline.append(waxs)
    all_detectors.append(ls) if 'ls' in motor_names else sd.baseline.append(ls)

    #That is not needed because sample name should be in the motor list
    sample_na = Signal(name='sample_name', value='test')
    all_detectors.append(sample_na)

    #Set exposure time
    det_exposure_time(measurement_time, number_images * measurement_time)

    bec.disable_plots()
    yield from bp.scan_nd(all_detectors, trajectory, md=base_md)
    bec.enable_plots()

    print('Scan with metadata done')
示例#12
0
def test_set_signal_to_None():
    s = Signal(value='0', name='bob')
    set_and_wait(s, None, timeout=1)
示例#13
0
    # resume 100s after current > 10 mA
    logger.info("Installing suspender for low APS current.")
    suspend_APS_current = bluesky.suspenders.SuspendFloor(aps.current,
                                                          2,
                                                          resume_thresh=10,
                                                          sleep=100)
    RE.install_suspender(suspend_APS_current)

    # remove comment if likely to use this suspender (issue #170)
    # suspend_FE_shutter = bluesky.suspenders.SuspendFloor(FE_shutter.pss_state, 1)
    # RE.install_suspender(suspend_FE_shutter)

    logger.info(f"mono shutter connected = {mono_shutter.pss_state.connected}")
    # remove comment if likely to use this suspender (issue #170)
    # suspend_mono_shutter = bluesky.suspenders.SuspendFloor(mono_shutter.pss_state, 1)

    logger.info(
        "Defining suspend_BeamInHutch.  Install/remove in scan plans as desired."
    )
    suspend_BeamInHutch = bluesky.suspenders.SuspendBoolLow(BeamInHutch)
    # be more judicious when to use this suspender (only within scan plans) -- see #180
    # RE.install_suspender(suspend_BeamInHutch)
    # logger.info("BeamInHutch suspender installed")

else:
    # simulators
    _simulated_beam_in_hutch = Signal(name="_simulated_beam_in_hutch")
    suspend_BeamInHutch = bluesky.suspenders.SuspendBoolHigh(
        _simulated_beam_in_hutch)
    # RE.install_suspender(suspend_BeamInHutch)
示例#14
0
    # blow up on inverted values
    assert start < stop, (f"start ({start}) must be smaller than "
                          f"stop ({stop}) for {k}")
    limits = motor.limits
    if any(not (limits[0] < v < limits[1]) for v in (start, stop)):
        raise LimitError(f"your requested {k} values are out of limits for "
                         "the motor "
                         f"{limits[0]} < ({start}, {stop}) < {limits[1]}")


def _get_v_with_dflt(sig, dflt):
    ret = yield from bps.read(sig)
    return ret[sig.name]["value"] if ret is not None else dflt


x_centers = Signal(value=[], name="x_centers", kind="normal")
x_centers.tolerance = 1e-15
y_centers = Signal(value=[], name="y_centers", kind="normal")
y_centers.tolerance = 1e-15
z_centers = Signal(value=[], name="z_centers", kind="normal")
z_centers.tolerance = 1e-15


def xy_fly(
    scan_title,
    *,
    beamline_operator,
    dwell_time,
    xstart,
    xstop,
    xstep_size,
示例#15
0
def test_strings():
    sig = Signal(name='sig', kind='normal')
    assert sig.kind == Kind.normal
import threading

import numpy
import numpy as np
from ophyd import Component, Device, Signal, DeviceStatus
from bluesky.plan_stubs import mv

from .generate_data import generate_measured_image, SHAPE

sample_selector = Signal(value=0, name="sample_selector")


class TimerStatus(DeviceStatus):
    """Simulate the time it takes for a detector to acquire an image."""
    def __init__(self, device, delay):
        super().__init__(device)
        self.delay = delay  # for introspection purposes
        threading.Timer(delay, self.set_finished).start()


class DiffractionDetector(Device):
    # exposure_time = Component(Signal, value=1)
    image = Component(Signal, value=numpy.zeros(SHAPE))
    signal_to_noise = Component(Signal, value=0)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.delay = 2  # simulated exposure time delay

    def trigger(self):
        "Generate a simulated reading with noise for the current sample."
            yield from bps.read(sclr)
            # and maybe the xspress3
            if xspress3 is not None:
                yield from bps.read(xspress3)
            yield from bps.save()

        for y in range(num_ypixels):
            if xspress3 is not None:
                yield from bps.mov(xspress3.fly_next, True)

            yield from fly_row()

    yield from fly_body()


E_centers = Signal(value=[], name='E_centers', kind='normal')
E_centers.tolerance = 1e-15


def E_fly(scan_title, *, start, stop, step_size, num_scans):
    _validate_motor_limits(mono.energy, start, stop, 'E')
    assert step_size > 0, f'step_size ({step_size}) must be more than 0'
    assert num_scans > 0, f'num_scans ({num_scans}) must be more than 0'

    e_back = yield from _get_v_with_dflt(mono.e_back, 1977.04)
    energy_cal = yield from _get_v_with_dflt(mono.cal, 0.40118)

    def _linear_to_energy(linear):
        linear = np.asarray(linear)
        return (e_back / np.sin(
            np.deg2rad(45) + 0.5 * np.arctan((28.2474 - linear) / 35.02333) +
示例#18
0
    move_motors
    usaxs_slit
""".split()

from ..session_logs import logger

logger.info(__file__)

from ..framework import sd
from apstools.utils import pairwise
from bluesky import plan_stubs as bps
from ophyd import Component, EpicsMotor, EpicsSignal, MotorBundle, Signal
import ophyd


guard_h_size = Signal(name="guard_h_size", value=0.5, labels=["terms",])
guard_v_size = Signal(name="guard_v_size", value=0.5, labels=["terms",])


def move_motors(*args):
    """
    move one or more motors at the same time, returns when all moves are done

    move_motors(m1, 0)
    move_motors(m2, 0, m3, 0, m4, 0)
    """
    status = []
    for m, v in pairwise(args):
        status.append(m.move(v, wait=False))

    for st in status:
示例#19
0
class PSO_Device(Device):
    """
    Operate the motion trajectory controls of an Aerotech Ensemble controller
    
    note: PSO means Position Synchronized Output (Aerotech's term)
    
    USAGE:
    
        pso1 = PSO_Device("2bmb:PSOFly1:", name="pso1")
        #
        # ... configure the pso1 object
        #
        pso1.set("taxi")    # or pso1.taxi() interactively
        pso1.set("fly")     # or pso1.fly() interactively
        
        # in a plan, use this instead
        yield from abs_set(pso1, "taxi", wait=True)
        yield from abs_set(pso1, "fly", wait=True)

    """
    # TODO: this might fit the ophyd "Flyer" API
    slew_speed = Component(EpicsSignal, "slewSpeed.VAL")
    scan_control = Component(EpicsSignal, "scanControl.VAL", string=True)
    start_pos = Component(EpicsSignal, "startPos.VAL")
    end_pos = Component(EpicsSignal, "endPos.VAL")
    scan_delta = Component(EpicsSignal, "scanDelta.VAL")
    pso_taxi = Component(EpicsSignal, "taxi.VAL", put_complete=True)
    pso_fly = Component(EpicsSignal, "fly.VAL", put_complete=True)
    busy = Signal(value=False, name="busy")

    #     def __init__(self, motor, detector):
    #         self.motor = motor
    #         self.detector = detector
    #
    #         self.return_position = self.motor.position
    #         self.poll_delay_s = 0.05
    #         self.num_spins = 1
    #
    #         self._completion_status = None
    #         self._data = deque()

    def taxi(self):
        """
        request move to taxi position, interactive use
        
        Since ``pso_taxi`` has the ``put_complete=True`` attribute,
        this will block until the move is complete.
        
        (note: ``2bmb:PSOFly1:taxi.RTYP`` is a ``busy`` record.)
        """
        # TODO: verify that this blocks until complete
        self.pso_taxi.put("Taxi")

    def fly(self):
        """
        request fly scan to start, interactive use
        
        Since ``pso_fly`` has the ``put_complete=True`` attribute,
        this will block until the move is complete.
        """
        # TODO: verify that this blocks until complete
        self.pso_fly.put("Fly")

    def set(self, value):  # interface for BlueSky plans
        """value is either Taxi, Fly, or Return"""
        #         allowed = "Taxi Fly Return".split()
        allowed = "Taxi Fly".split()
        if str(value).lower() not in list(map(str.lower, allowed)):
            msg = "value should be one of: " + " | ".join(allowed)
            msg + " received " + str(value)
            raise ValueError(msg)

        if self.busy.value:
            raise RuntimeError("shutter is operating")

        status = DeviceStatus(self)

        def action():
            """the real action of ``set()`` is here"""
            if str(value).lower() == "taxi":
                self.taxi()
            elif str(value).lower() == "fly":
                self.fly()
#             elif str(value).lower() == "return":
#                 self.motor.move(self.return_position)

        def run_and_wait():
            """handle the ``action()`` in a thread"""
            self.busy.put(True)
            action()
            self.busy.put(False)
            status._finished(success=True)

        threading.Thread(target=run_and_wait, daemon=True).start()
        return status
示例#20
0
def test_array_into_softsignal():
    data = np.array([1, 2, 3])
    s = Signal(name='np.array')
    set_and_wait(s, data)
    assert np.all(s.get() == data)
示例#21
0
def sim_signal():
    sim_sig = Signal(name='tst_this_2')
    sim_sig.put(3.14)
    register_signal(sim_sig)
    return sim_sig
示例#22
0
from ophyd import (EpicsSignal, Signal)
from ophyd.utils.epics_pvs import record_field


def callback(sub_type=None, timestamp=None, value=None, **kwargs):
    logger.info('[callback] [%s] (type=%s) value=%s', timestamp, sub_type,
                value)

    # Test that the monitor dispatcher works (you cannot use channel access in
    # callbacks without it)
    logger.info('[callback] caget=%s', rw_signal.get())

logger = config.logger

motor_record = config.motor_recs[0]
val = record_field(motor_record, 'VAL')
rbv = record_field(motor_record, 'RBV')

rw_signal = EpicsSignal(rbv, write_pv=val)
rw_signal.subscribe(callback, event_type=rw_signal.SUB_VALUE)
rw_signal.subscribe(callback, event_type=rw_signal.SUB_SETPOINT)

rw_signal.value = 2
time.sleep(1.)
rw_signal.value = 1
time.sleep(1.)

# You can also create a Python Signal:
sig = Signal(name='testing', value=10)
logger.info('Python signal: %s', sig)