具有“外部”更新属性的PyTango设备

时间:2016-02-17 15:27:26

标签: python events

假设您有一个硬件设备,它会以或多或少的随机间隔向您发送更新,并且客户端希望每次发生这种情况时都会收到事件。你会如何写一个PyTango(Tango controls library PyTango.server.Device类的Python包装器,它使用一个推送新属性值的线程来模拟它?

1 个答案:

答案 0 :(得分:0)

答案似乎是

  1. 调用PyTango.server.Device方法set_change_event()告诉Tango设备正在处理自己的更改事件,而无需使用轮询循环。
  2. 使用更新主题中的Pytango.sever.Device方法push_change_event()。到目前为止似乎线程安全,即使具有非常高的更新速率也没有看到任何malarkey。如果有人能证实这一点会很好。
  3. 还缓存更新,以便轮询属性的客户端获取正确的值和时间戳。这似乎有点多余,有更好的方法吗?
  4. 不要设置对属性的轮询,因为它可能导致客户端轮询获得过时的值(Tango似乎以我无法弄清楚的方式从轮询中缓存属性值)。
  5. 此处使用外部更新的randomnumber属性

    的示例(python 2.7)服务器
    import time
    import threading
    import random
    
    from PyTango.server import server_run
    from PyTango.server import Device, DeviceMeta
    from PyTango.server import attribute, command
    
    from PyTango import AttrQuality
    
    
    class ExternallyUpdated(Device):
        __metaclass__ = DeviceMeta
    
        def __init__(self, *args, **kwargs):
            super(ExternallyUpdated, self).__init__(*args, **kwargs)
            self._randomnumber = (0, 0, AttrQuality.ATTR_VALID)
            # Tell Tango that we don't need a polling loop, we'll 
            # push change events explicitly
            self.set_change_event('randomnumber', True)
    
        @attribute(label="Random Number", dtype=int,
                   # Enables update events for absolute changes >= 1
                   abs_change=1)
        def randomnumber(self):
            return self._randomnumber
    
        def init_device(self):
            super(ExternallyUpdated, self).init_device()
            self.t = threading.Thread(target=self.update_loop)
            self.t.setDaemon(True)
            self.t.start()
    
        def update_loop(self):
            while True:
                try:
                    new_number = random.randint(0, 10000)
                    ts = time.time()
                    sleeptime = random.random()*10
                    print ('Timestamp: {:.5f}    New value: {}    sleeptime: {}'
                            .format(ts, new_number, sleeptime))
                    # Need to cache the value so that clients can poll the attribute
                    self._randomnumber = (new_number, ts, AttrQuality.ATTR_VALID)
                    self.push_change_event(
                        'randomnumber', new_number, ts, AttrQuality.ATTR_VALID)
                    time.sleep(sleeptime)
                except Exception:
                    logger.exception('Exception in update loop')
                    time.sleep(1)
    
    if __name__ == "__main__":
        server_run([ExternallyUpdated])
    

    下面是一个示例客户端,假设您将设备导出为so_example/external/1。每次更新randomnumber时都应该打印一条消息。

    import time
    import logging
    
    import PyTango
    
    
    logger = logging.getLogger()
    logging.basicConfig(
            format='%(asctime)s - %(name)s - %(levelname)s - %(module)s - '
            '%(pathname)s : %(lineno)d - %(message)s',
            level=logging.INFO)
    
    device_name = 'so_example/external/1'
    
    td = PyTango.DeviceProxy(device_name)
    attr_name = 'randomnumber'
    
    # Set up a listener
    def printer(event_data):
        try:
            print event_data # A PyTango.EventData instance
        except Exception:
            # Not handling exceptions seems to break the event update loop. Or I was
            # doing something else stupid, must still investigate :)
            logger.exception('Exception while handling event, event_data: {}'
                             .format(event_data))
    poll_event_id = td.subscribe_event(
        attr_name, PyTango.EventType.CHANGE_EVENT, printer)
    
    # Do something that blocks here, or just run in an interactive session
    # time.sleep(60)
    
    # This is how you would unsubscribe from the events.
    #td.unsubscribe_event(poll_event_id)