by

Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
    Till Stensitzki
    @Tillsten
    I think you are reinventing epics/tango
    Blaise Thompson
    @untzag
    yeah I could believe you...
    Till Stensitzki
    @Tillsten
    But I really dig the traits idea.
    Kyle Sunden
    @ksunden_gitlab
    That is our real core, I think, though the strong interface is also key
    Blaise Thompson
    @untzag
    can you say more about why EPICS was too much for you Till?
    Till Stensitzki
    @Tillsten
    Before I came in the lab was running Visual Basic 6, hence windows computers
    I think epic is almost never used outside the bigger instituitions
    Because the hurdle to install it is really highm, if you don't have decidated stuff for that, which many people dont.
    The alternative is "just" writing an dll wrapper or use some serial commands via visa/pyserial.
    Thomas A Caswell
    @tacaswell
    @untzag @ksunden_gitlab do you have a way to get push notifications from the daemons?
    Blaise Thompson
    @untzag
    nope
    strictly client intiated
    idea is that clients would have poll loops, if needed
    Kyle Sunden
    @ksunden_gitlab
    We do not, we designed with the idea that all requests would be initiated by the client, and return quickly
    Thomas A Caswell
    @tacaswell
    poll loops don't scale up well
    how does a motor tell you it got where it was going?
    Blaise Thompson
    @untzag
    so you call set_position
    and then you sit in a loop calling busy until it returns false
    and then (most of the time) you don't communicate with the motor at all until you need to move it again
    if you have a graphical interface you can call get_position while it's moving
    and have that streaming feedback experience
    Kyle Sunden
    @ksunden_gitlab
    Part of this is from the fact that JSON-RPC is an inheriently mono-directional protocol
    Blaise Thompson
    @untzag
    it's not optimized for tight timing---I guess that's just not something I've found necessary in my work
    Thomas A Caswell
    @tacaswell
    can I make the test motor have an appreciable move time?
    Blaise Thompson
    @untzag
    um...
    edit source? that's all I got. @ksunden_gitlab ?
    Kyle Sunden
    @ksunden_gitlab
    I can make one pretty quick (and just put the python in here)
    Thomas A Caswell
    @tacaswell
    if you don't have one handy don't worry about it
    Kyle Sunden
    @ksunden_gitlab
    
    import asyncio
    from yaqd_core import Hardware
    
    class SlowHardware(Hardware):
        _kind = "slow-hardware"
    
        def _set_position(self, position):
            self._loop.create_task(self.wait_and_set(position))
    
        async def wait_and_set(self, position):
            self._busy = True
            await asyncio.sleep(2)
            self._position = position
            self._busy = False
    
    if __name__ == "__main__":
        SlowHardware.main()
    Then just python slow_motor_test.py --config config.toml
    Or whatever you call the python file, rather
    (I will note that I did not put any protections in there for calling set_position multiple times while it is 'moving', more properly I would. Most real hardware you set the busy state via other means, so it can handle it fine, some you do have to prempt motion as its occurring.)
    Till Stensitzki
    @Tillsten
    isn't json rpc little bit slow for sensor readout?
    Thomas A Caswell
    @tacaswell
    from ophyd import Device, Component as Cpt, Signal, DeviceStatus
    
    
    class YaqMotor(Device):
        setpoint = Cpt(Signal, kind="hinted")
        readback = Cpt(Signal)
        busy = Cpt(Signal, kind="omitted")
    
        def __init__(self, port, *, name):
            self._client = yaqc.Client(port)
            super().__init__(name=name)
            # force initial reading
            self.read()
    
        def set(self, value):
            self._client.send("set_position", value)
            st = DeviceStatus(self)
            # TODO set up thread to poll busy, for now insta-done
            st._finished()
            # update the signals
            self.read()
            return st
    
        def read(self):
            v = self._client.send("get_state")
            self.setpoint.put(v["destination"])
            self.readback.put(v["position"])
            return super().read()
    Blaise Thompson
    @untzag
    @Tillsten yes it can be slow if you need to transfer a relatively large amount of data
    Thomas A Caswell
    @tacaswell
    there are lots of things wrong with that class, but that is enough to make bluesky happy to use the device...
    Blaise Thompson
    @untzag
    our approach is to do data processing on the daemon side, and transfer the smaller processed arrays
    Kyle Sunden
    @ksunden_gitlab

    For a lot of what we have been doing, our sensors are scalar values... That said, we are looking towards array detecteion/cameras.

    I have some ideas, including something I think I saw in Ophyd, which is writing directly to a file on the daemon side for such large detectors (thereby only needing to transmit what is actually displayed in a client application over the RPC)

    Blaise Thompson
    @untzag
    I'll also note that you can use yaq daemons for a subset of your hardware, and use a more direct approach for any really high density information like array detectors
    @tacaswell wow that's super helpful
    Thomas A Caswell
    @tacaswell
    
    In [49]: RE(bp.scan([], ym, -5, 5, 25))                                                                                                                              
    
    
    Transient Scan ID: 8     Time: 2020-04-08 19:22:56
    Persistent Unique Scan ID: '667c5f23-c416-4ee7-9004-86c722454a00'
    New stream: 'primary'
    +-----------+------------+-------------+
    |   seq_num |       time | ym_setpoint |
    +-----------+------------+-------------+
    |         1 | 19:22:56.6 |      -5.000 |
    |         2 | 19:22:56.6 |      -4.583 |
    |         3 | 19:22:56.6 |      -4.167 |
    |         4 | 19:22:56.6 |      -3.750 |
    |         5 | 19:22:56.6 |      -3.333 |
    |         6 | 19:22:56.6 |      -2.917 |
    |         7 | 19:22:56.6 |      -2.500 |
    |         8 | 19:22:56.6 |      -2.083 |
    |         9 | 19:22:56.6 |      -1.667 |
    |        10 | 19:22:56.6 |      -1.250 |
    |        11 | 19:22:56.6 |      -0.833 |
    |        12 | 19:22:56.6 |      -0.417 |
    |        13 | 19:22:56.6 |       0.000 |
    |        14 | 19:22:56.6 |       0.417 |
    |        15 | 19:22:56.6 |       0.833 |
    |        16 | 19:22:56.6 |       1.250 |
    |        17 | 19:22:56.6 |       1.667 |
    |        18 | 19:22:56.6 |       2.083 |
    |        19 | 19:22:56.6 |       2.500 |
    |        20 | 19:22:56.6 |       2.917 |
    |        21 | 19:22:56.6 |       3.333 |
    |        22 | 19:22:56.6 |       3.750 |
    |        23 | 19:22:56.6 |       4.167 |
    |        24 | 19:22:56.6 |       4.583 |
    |        25 | 19:22:56.6 |       5.000 |
    +-----------+------------+-------------+
    generator scan ['667c5f23'] (scan num: 8)
    
    
    
    Out[49]: ('667c5f23-c416-4ee7-9004-86c722454a00',)
    that is a very fast motor :-p
    Till Stensitzki
    @Tillsten
    Some real motors are not much slower :)
    Assuming 500 mm/s, it would have been 20 mu s
    Thomas A Caswell
    @tacaswell
    from ophyd import Device, Component as Cpt, Signal, DeviceStatus
    import yaqc
    import threading
    import time
    
    class YaqMotor(Device):
        setpoint = Cpt(Signal, kind="hinted")
        readback = Cpt(Signal)
        busy = Cpt(Signal, kind="omitted")
    
        def __init__(self, port, *, name):
            self._client = yaqc.Client(port)
            assert 'has-position' in self._client.send('get_traits')
            self._busy_lock = threading.Lock()
            super().__init__(name=name)
            # force initial reading
            self._read_yaq()
    
        def set(self, value):
            with self._busy_lock:
                self.busy.put(1)
            self._client.send("set_position", value)
            st = DeviceStatus(self)
    
            def poll_busy():
                busy = self._client.send('busy')
                while busy:
                    time.sleep(.1)
                    busy = self._client.send('busy')
                with self._busy_lock:
                    self.busy.put(int(busy))
                st._finished()
    
            threading.Thread(target=poll_busy).start()
            # update the signals
            self._read_yaq()        
            return st
    
        def _read_yaq(self):
            v = self._client.send("get_state")
            self.setpoint.put(v["destination"])
            self.readback.put(v["position"])
            b = self._client.send('busy')
            with self._busy_lock:
                self.busy.put(int(b))
    
        def read(self):
            self._read_yaq()
            return super().read()
    
    
    ym = YaqMotor(38001, name='ym')
    the version that waits for the slow motor...
    Blaise Thompson
    @untzag
    @tacaswell thanks so much you've really gone above and beyond in return to my humble publication request
    Thomas A Caswell
    @tacaswell
    this is much more fun than writing papers ;)
    Blaise Thompson
    @untzag
    believe me I don't want to reinvent wheels... seems like there is a lot that I could just have if I can get a foot in the door with bluesky
    at the very least it's neat to imagine that we could just use your implementation for directives and client interfaces
    @Tillsten if you ever want to chat more directly about ultrafast spectroscopy hit me up :smile: