def initServer(self): self.devices = MultiDict() # aliases -> device self.device_guids = {} # name -> guid self._next_guid = 0 # register a message handler for connect/disconnect messages handler = lambda c, data: self.handleDeviceMessage(*data) self._cxn.addListener(handler, ID=self.messageID) if self.deviceManager in self.client.servers: yield self.connectToDeviceManager()
def __init__(self, cxn, name, pyName, ID): self._cxn = cxn self._mgr = cxn._mgr self.name = self._labrad_name = name self._py_name = pyName self.ID = ID self.settings = MultiDict() self._cache = {} self._packetWrapperClass = makePacketWrapperClass(self) self._refreshLock = defer.DeferredLock()
def __init__(self, prot): self.servers = MultiDict() self._cache = {} self._cxn = prot self._mgr = manager.AsyncManager(self._cxn) self._next_context = 1 self._refreshLock = defer.DeferredLock()
class DeviceServer(LabradServer): """A server for devices. Creates a DeviceWrapper for each device it finds, based on a user-provided function. Provides standard settings for listing devices, selecting a device for the current context, and refreshing the list of devices. Also, provides for device-locking with timeouts. """ name = 'Generic Device Server' deviceWrapper = DeviceWrapper def initServer(self): self.devices = MultiDict() # aliases -> device self.device_guids = {} # name -> guid self._next_guid = 0 self._refreshLock = defer.DeferredLock() return self.refreshDeviceList() def stopServer(self): if hasattr(self, 'devices'): ds = [defer.maybeDeferred(dev.shutdown) for dev in self.devices.values()] return defer.DeferredList(ds, fireOnOneErrback=True) def findDevices(self): """Return a list of found devices. The result should be a list of (name, args, kw) tuples where args and kw are the arguments tuple and keyword dict that will be used to call the device's connect function. """ return [] def refreshDeviceList(self): """Refresh the list of available devices. Devices are assigned a unique identifying number that will persist between refreshes, so that clients that have selected devices in context will still be able to refer to them after the refresh. """ return self._refreshLock.run(self._doRefresh) def chooseDeviceWrapper(self, *args, **kw): """ Override in subclass to support multiple device wrapper classes args and kw come from findDevices (ie same as are passed into the device wrapper's connect method). """ return self.deviceWrapper @inlineCallbacks def _doRefresh(self): """Do the actual refreshing.""" self.log('refreshing device list...') all_found = yield self.findDevices() # If there are devices for which we don't have wrappers, # create them. If we have a wrapper for a # device that is no longer available, then that wrapper should # be marked somehow to indicate that it is no longer available. def fixFound(f): if isinstance(f, str): f = f, (), {} name = f[0] args = f[1] if len(f) > 1 else () kw = f[2] if len(f) > 2 else {} return name, args, kw all_found = [fixFound(f) for f in all_found] additions = [(name, args, kw) for (name, args, kw) in all_found if name not in self.devices] names_found = [name for (name, args, kw) in all_found] deletions = [name for name in self.device_guids if name in self.devices and name not in names_found] self.log('all_found: %s' % all_found) self.log('additions: %s' % additions) self.log('deletions: %s' % deletions) # start additions for name, args, kw in additions: if name in self.device_guids: # we've seen this device before # so we'll reuse the old guid guid = self.device_guids[name] else: guid = self.device_guids[name] = self._next_guid self._next_guid += 1 deviceWrapper = self.chooseDeviceWrapper(name, *args, **kw) dev = deviceWrapper(guid, name) yield dev.connect(*args, **kw) self.devices[guid, name] = dev # do deletions for name in deletions: # we delete the device, but not its guid, so that # if this device comes back, users who have # selected it by guid will reconnect seamlessly dev = self.devices[name] del self.devices[name] try: yield dev.shutdown() except Exception, e: self.log('Error while shutting down device "%s": %s' % (name, e))
class DeviceServer(LabradServer): """A server for devices. Creates a DeviceWrapper for each device it finds, based on a user-provided function. Provides standard settings for listing devices, selecting a device for the current context, and refreshing the list of devices. Also, provides for device-locking with timeouts. """ name = 'Generic Device Server' deviceWrapper = DeviceWrapper def initServer(self): self.devices = MultiDict() # aliases -> device self.device_guids = {} # name -> guid self._next_guid = 0 self._refreshLock = defer.DeferredLock() return self.refreshDeviceList() def stopServer(self): if hasattr(self, 'devices'): ds = [ defer.maybeDeferred(dev.shutdown) for dev in self.devices.values() ] return defer.DeferredList(ds, fireOnOneErrback=True) def findDevices(self): """Return a list of found devices. The result should be a list of (name, args, kw) tuples where args and kw are the arguments tuple and keyword dict that will be used to call the device's connect function. """ return [] def refreshDeviceList(self): """Refresh the list of available devices. Devices are assigned a unique identifying number that will persist between refreshes, so that clients that have selected devices in context will still be able to refer to them after the refresh. """ return self._refreshLock.run(self._doRefresh) def chooseDeviceWrapper(self, *args, **kw): """ Override in subclass to support multiple device wrapper classes args and kw come from findDevices (ie same as are passed into the device wrapper's connect method). """ return self.deviceWrapper @inlineCallbacks def _doRefresh(self): """Do the actual refreshing.""" self.log('refreshing device list...') all_found = yield self.findDevices() # If there are devices for which we don't have wrappers, # create them. If we have a wrapper for a # device that is no longer available, then that wrapper should # be marked somehow to indicate that it is no longer available. def fixFound(f): if isinstance(f, str): f = f, (), {} name = f[0] args = f[1] if len(f) > 1 else () kw = f[2] if len(f) > 2 else {} return name, args, kw all_found = [fixFound(f) for f in all_found] additions = [(name, args, kw) for (name, args, kw) in all_found if name not in self.devices] names_found = [name for (name, args, kw) in all_found] deletions = [ name for name in self.device_guids if name in self.devices and name not in names_found ] self.log('all_found: %s' % all_found) self.log('additions: %s' % additions) self.log('deletions: %s' % deletions) # start additions for name, args, kw in additions: if name in self.device_guids: # we've seen this device before # so we'll reuse the old guid guid = self.device_guids[name] else: guid = self.device_guids[name] = self._next_guid self._next_guid += 1 deviceWrapper = self.chooseDeviceWrapper(name, *args, **kw) dev = deviceWrapper(guid, name) yield dev.connect(*args, **kw) self.devices[guid, name] = dev # do deletions for name in deletions: # we delete the device, but not its guid, so that # if this device comes back, users who have # selected it by guid will reconnect seamlessly dev = self.devices[name] del self.devices[name] try: yield dev.shutdown() except Exception, e: self.log('Error while shutting down device "%s": %s' % (name, e))
class AsyncServerWrapper(object): """Represents a remote LabRAD server.""" def __init__(self, cxn, name, pyName, ID): self._cxn = cxn self._mgr = cxn._mgr self.name = self._labrad_name = name self._py_name = pyName self.ID = ID self.settings = MultiDict() self._cache = {} self._packetWrapperClass = makePacketWrapperClass(self) self._refreshLock = defer.DeferredLock() def refresh(self): return self._refreshLock.run(self._refresh) @inlineCallbacks def _refresh(self): """Update the list of available settings for this server.""" # get info about this server and its settings info = yield self._mgr.getServerInfoWithSettings(self.ID) self.__doc__, self.notes, settings = info names = [s[0] for s in settings] # determine what to add, update and delete to be current for s in settings: if s[0] not in self.settings: self._addSetting(*s) else: self._refreshSetting(*s) for n in self.settings: if n not in names: self._delSetting(n) _staticAttrs = ['settings', 'refresh', 'context', 'packet', 'sendMessage', 'addListener', 'removeListener'] def _fixName(self, name): pyName = mangle(name) if pyName in self._staticAttrs: pyName = 'lr_' + pyName return pyName def _addSetting(self, name, ID, info): """Add a wrapper for a new setting for this server. The wrapper will be pulled from the cache and reused if one has already been created for a setting with this name. Also add this setting to the packet wrapper class. """ if name in self._cache: setting = self._cache[name] setting._updateInfo(ID, info) else: pyName = self._fixName(name) setting = AsyncSettingWrapper(self, name, pyName, ID, info) self._cache[name] = setting self.settings[name, setting._py_name, ID] = setting setattr(self, setting._py_name, setting) # also add to the packet class self._packetWrapperClass._addSetting(setting) def _refreshSetting(self, name, ID, info): """Refresh the info about a particular setting. In particular, update the MultiDict mapping if the setting ID has changed. """ setting = self.settings[name] aliasChanged = (ID != setting.ID) setting._updateInfo(ID, info) if aliasChanged: # update aliases if the ID has changed self.settings._updateAliases(name, setting._py_name, ID) self._packetWrapperClass._refreshSetting(setting) def _delSetting(self, name): """Remove the wrapper for a setting.""" setting = self.settings[name] del self.settings[name] delattr(self, setting._py_name) # also remove from the packet class self._packetWrapperClass._delSetting(setting) def context(self): """Create a new context for talking to this server.""" return self._cxn.context() def packet(self, **kw): """Create a new packet for this server.""" return self._packetWrapperClass(**kw) def sendMessage(self, ID, *args, **kw): """Send a message to this server.""" tag = extractKey(kw, 'tag', []) if len(args) == 0: args = None elif len(args) == 1: args = args[0] self._cxn._sendMessage(self.ID, [(ID, args, tag)], **kw) def addListener(self, listener, **kw): """Add a listener for messages from this server.""" kw['source'] = self.ID self._cxn._addListener(listener, **kw) def removeListener(self, listener, **kw): """Remove a listener for messages from this server.""" kw['source'] = self.ID self._cxn._removeListener(listener, **kw) def __call__(self): return self # TODO this should make a clone that can have different # default keyword args, and, in particular, should talk # to the server in a different context def __getitem__(self, key): return self.settings[key] def _send(self, *args, **kw): """Send packet to this server.""" return self._cxn._send(self.ID, *args, **kw)
class DeviceServer(LabradServer): """A server for devices. Creates a DeviceWrapper for each device it finds, based on a user-provided function. Provides standard settings for listing devices, selecting a device for the current context, and refreshing the list of devices. Also, provides for device-locking with timeouts. """ name = 'Generic Device Server' deviceWrapper = DeviceWrapper def initServer(self): self.devices = MultiDict() # aliases -> device self.device_guids = {} # name -> guid self._next_guid = 0 self._refreshLock = defer.DeferredLock() return self.refreshDeviceList() def stopServer(self): if hasattr(self, 'devices'): ds = [defer.maybeDeferred(dev.shutdown) for dev in self.devices.values()] return defer.DeferredList(ds, fireOnOneErrback=True) def findDevices(self): """Return a list of found devices. The result should be a list of (name, args, kw) tuples where args and kw are the arguments tuple and keyword dict that will be used to call the device's connect function. """ return [] def refreshDeviceList(self): """Refresh the list of available devices. Devices are assigned a unique identifying number that will persist between refreshes, so that clients that have selected devices in context will still be able to refer to them after the refresh. """ return self._refreshLock.run(self._doRefresh) def chooseDeviceWrapper(self, *args, **kw): """ Override in subclass to support multiple device wrapper classes args and kw come from findDevices (ie same as are passed into the device wrapper's connect method). """ return self.deviceWrapper @inlineCallbacks def _doRefresh(self): """Do the actual refreshing.""" self.log('refreshing device list...') all_found = yield self.findDevices() # If there are devices for which we don't have wrappers, # create them. If we have a wrapper for a # device that is no longer available, then that wrapper should # be marked somehow to indicate that it is no longer available. def fixFound(f): if isinstance(f, str): f = f, (), {} name = f[0] args = f[1] if len(f) > 1 else () kw = f[2] if len(f) > 2 else {} return name, args, kw all_found = [fixFound(f) for f in all_found] additions = [(name, args, kw) for (name, args, kw) in all_found if name not in self.devices] names_found = [name for (name, args, kw) in all_found] deletions = [name for name in self.device_guids if name in self.devices and name not in names_found] self.log('all_found: %s' % all_found) self.log('additions: %s' % additions) self.log('deletions: %s' % deletions) # start additions for name, args, kw in additions: if name in self.device_guids: # we've seen this device before # so we'll reuse the old guid guid = self.device_guids[name] else: guid = self.device_guids[name] = self._next_guid self._next_guid += 1 deviceWrapper = self.chooseDeviceWrapper(name, *args, **kw) dev = deviceWrapper(guid, name) yield dev.connect(*args, **kw) self.devices[guid, name] = dev # do deletions for name in deletions: # we delete the device, but not its guid, so that # if this device comes back, users who have # selected it by guid will reconnect seamlessly dev = self.devices[name] del self.devices[name] try: yield dev.shutdown() except Exception as e: self.log('Error while shutting down device "%s": %s' % (name, e)) def serverConnected(self, ID, name): self.refreshDeviceList() def serverDisconnected(self, ID, name): self.refreshDeviceList() def expireContext(self, c): """Release selected/locked device when context expires.""" if 'device' in c: alias = c['device'] try: dev = self.devices[alias] if dev.lockedInContext(c): dev.unlock(c) dev.deselect(c) except KeyError: pass def deviceLists(self): """Get parallel lists of device names and IDs.""" guids = sorted(self.devices.keys()) names = [self.devices[g].name for g in guids] return guids, names def selectedDevice(self, context): """Get the selected device from the given context.""" if not len(self.devices): raise errors.NoDevicesAvailableError() try: key = context['device'] except KeyError: raise errors.DeviceNotSelectedError() try: dev = self.devices[key] except KeyError: raise errors.NoSuchDeviceError() if not dev.accessibleFrom(context.ID): raise DeviceLockedError() return dev def selectDevice(self, context, key=None): """Select a device in our current context.""" if not len(self.devices): raise errors.NoDevicesAvailableError() if key is None: # use the first device key = sorted(self.devices.keys())[0] try: dev = self.devices[key] except KeyError: raise errors.NoSuchDeviceError(key) if not dev.accessibleFrom(context.ID): raise DeviceLockedError() if 'device' in context: if context['device'] != dev.guid: try: oldDev = self.devices[context['device']] except KeyError: pass else: # we're trying to select a new device. # make sure to unlock previously selected device if oldDev.lockedInContext(context.ID): oldDev.unlock(context.ID) oldDev.deselect(context) context['device'] = dev.guid dev.select(context) else: context['device'] = dev.guid dev.select(context) return dev def deselectDevice(self, context): if 'device' in context: try: oldDev = self.devices[context['device']] except KeyError: pass else: # unlock and deselect device if oldDev.lockedInContext(context.ID): oldDev.unlock(context.ID) oldDev.deselect(context) del context['device'] def getDevice(self, context, key=None): if not len(self.devices): raise errors.NoDevicesAvailableError() if key is None: # use the first device key = sorted(self.devices.keys())[0] try: dev = self.devices[key] except KeyError: raise errors.NoSuchDeviceError() if not dev.accessibleFrom(context.ID): raise DeviceLockedError() return dev # server settings @setting(1, 'List Devices', returns=['*(ws)']) def list_devices(self, c): """List available devices.""" IDs, names = self.deviceLists() return list(zip(IDs, names)) @setting(2, 'Select Device', key=[': Select first device', 's: Select device by name', 'w: Select device by ID'], returns=['s: Name of the selected device']) def select_device(self, c, key=0): """Select a device for the current context.""" dev = self.selectDevice(c, key=key) return dev.name @setting(3, 'Deselect Device', returns=['']) def deselect_device(self, c): """Select a device for the current context.""" dev = self.deselectDevice(c) @setting(4, 'Refresh Devices', returns=['*(ws)']) def refresh_devices(self, c): """Refresh the list of available devices.""" yield self.refreshDeviceList() returnValue(self.list_devices(c)) @setting(1000001, 'Lock Device', timeout=[': Lock the selected device for default time', 'v[s]: Lock for specified time'], returns=['']) def lock_device(self, c, timeout): """Lock a device to be accessible only in this context.""" dev = self.selectedDevice(c) if timeout is not None: timeout = timeout['s'] dev.lock(c.ID, timeout) @setting(1000002, 'Release Device', returns=['']) def release_device(self, c): """Release the lock on the currently-locked device.""" dev = self.selectedDevice(c) dev.unlock(c.ID)
def initServer(self): self.devices = MultiDict() # aliases -> device self.device_guids = {} # name -> guid self._next_guid = 0 self._refreshLock = defer.DeferredLock() return self.refreshDeviceList()
class ManagedDeviceServer(LabradServer): """A server for devices. Creates a DeviceWrapper for each device it finds, based on a user-provided function. Provides standard settings for listing devices, selecting a device for the current context, and refreshing the list of devices. Also, provides for device-locking with timeouts. Device names and associated wrappers can be specified in two ways 1 (old way) Give a device name or list of device names: deviceName = "nameOfDevice" eg. "Acme XL123" or deviceName = ["nameOfDevice", "nameOfOtherDevice",...] and also give a single device wrapper. deviceWrapper=<a device Wrapper (sub)class> With this method the same device wrapper is used for all detected devices, regardless of eg. model. This works if all models use the same SCPI commands. 2 (new better way) Give a dict mapping device names to wrappers deviceWrappers = {"deviceName":wrapperForThisDevice,...} This allows you to use the same server for eg. devices of the same general type but from different manufacturers or of different models. 1. Old way example deviceName = "Acme Widget" deviceWrapper = AcmeWidgetWrapper 2. New way example deviceWrappers={"Acme Widget": AcmeWidgetExample} Optionally specify a device specific identication function deviceIdentFunc = 'identify_device' """ name = 'Generic Device Server' deviceManager = 'Device Manager' # Default device name and wrapper for backwards compatibility with servers # written before we supported multiple different device types, and which # do not explicitly set deviceWrapper and/or deviceName. deviceName = 'Generic Device' deviceWrapper = DeviceWrapper messageID = 21436587 def __init__(self): #Backward compatibility for servers that don't use a #deviceWrappers dict if not hasattr(self, 'deviceWrappers'): names = self.deviceName if isinstance(names, str): names = [names] self.deviceWrappers = dict((name, self.deviceWrapper) for name in names) LabradServer.__init__(self) @inlineCallbacks def initServer(self): self.devices = MultiDict() # aliases -> device self.device_guids = {} # name -> guid self._next_guid = 0 # register a message handler for connect/disconnect messages handler = lambda c, data: self.handleDeviceMessage(*data) self._cxn.addListener(handler, ID=self.messageID) if self.deviceManager in self.client.servers: yield self.connectToDeviceManager() @inlineCallbacks def stopServer(self): if hasattr(self, 'devices'): ds = [defer.maybeDeferred(dev.shutdown) for dev in self.devices.values()] yield defer.DeferredList(ds, fireOnOneErrback=True) def makeDeviceName(self, device, server, address): return server + ' - ' + address @inlineCallbacks def handleDeviceMessage(self, device, server, address, isConnected=True): print('Device message:', device, server, address, isConnected) name = self.makeDeviceName(device, server, address) if isConnected: # add device if name in self.devices: return # we already have this device if name in self.device_guids: # we've seen this device before # so we'll reuse the old guid guid = self.device_guids[name] else: guid = self.device_guids[name] = self._next_guid self._next_guid += 1 dev = self.deviceWrappers[device](guid, name) yield self.client.refresh() yield dev.connect(self.client[server], address) self.devices[guid, name] = dev else: # remove device yield self.removeDevice(name) @inlineCallbacks def removeDevice(self, name): # we delete the device, but not its guid, so that # if this device comes back, users who have # selected it by guid will reconnect seamlessly if name not in self.devices: return dev = self.devices[name] del self.devices[name] try: yield dev.shutdown() except Exception as e: self.log('Error while shutting down device "%s": %s' % (name, e)) @inlineCallbacks def connectToDeviceManager(self): """ """ yield self.client.refresh() manager = self.client[self.deviceManager] #If we have a device identification function register it with the device manager if hasattr(self, 'deviceIdentFunc'): yield manager.register_ident_function(self.deviceIdentFunc) #Register ourself as a server who cares about devices devs = yield manager.register_server(list(self.deviceWrappers.keys()), self.messageID) # the devs list is authoritative any devices we have # that are _not_ on this list should be removed names = [self.makeDeviceName(*dev[:3]) for dev in devs] additions = [self.handleDeviceMessage(*dev) for dev in devs] deletions = [self.removeDevice(name) for name in self.device_guids.keys() if name not in names] yield defer.DeferredList(additions + deletions) def serverConnected(self, ID, name): if name == self.deviceManager: self.connectToDeviceManager() def expireContext(self, c): """Release selected/locked device when context expires.""" if 'device' in c: alias = c['device'] try: dev = self.devices[alias] if dev.lockedInContext(c): dev.unlock(c) dev.deselect(c) except KeyError: pass def deviceLists(self): """Get parallel lists of device names and IDs.""" guids = sorted(self.devices.keys()) names = [self.devices[g].name for g in guids] return guids, names def selectedDevice(self, context): """Get the selected device from the given context.""" if not len(self.devices): raise errors.NoDevicesAvailableError() try: key = context['device'] except KeyError: raise errors.DeviceNotSelectedError() try: dev = self.devices[key] except KeyError: raise errors.NoSuchDeviceError() if not dev.accessibleFrom(context.ID): raise DeviceLockedError() return dev def selectDevice(self, context, key=None): """Select a device in our current context.""" if not len(self.devices): raise errors.NoDevicesAvailableError() if key is None: # use the first device key = sorted(self.devices.keys())[0] try: dev = self.devices[key] except KeyError: raise errors.NoSuchDeviceError() if not dev.accessibleFrom(context.ID): raise DeviceLockedError() if 'device' in context: if context['device'] != dev.guid: try: oldDev = self.devices[context['device']] except KeyError: pass else: # we're trying to select a new device. # make sure to unlock previously selected device if oldDev.lockedInContext(context.ID): oldDev.unlock(context.ID) oldDev.deselect(context) context['device'] = dev.guid dev.select(context) else: context['device'] = dev.guid dev.select(context) return dev def deselectDevice(self, context): if 'device' in context: try: oldDev = self.devices[context['device']] except KeyError: pass else: # unlock and deselect device if oldDev.lockedInContext(context.ID): oldDev.unlock(context.ID) oldDev.deselect(context) del context['device'] def getDevice(self, context, key=None): if not len(self.devices): raise errors.NoDevicesAvailableError() if key is None: # use the first device key = sorted(self.devices.keys())[0] try: dev = self.devices[key] except KeyError: raise errors.NoSuchDeviceError() if not dev.accessibleFrom(context.ID): raise DeviceLockedError() return dev # server settings @setting(1, 'List Devices', returns='*(ws)') def list_devices(self, c): """Get a list of available devices. The list entries have a numerical ID and a string name. It is recommended to use the names wherever possible to identify devices, since this contains human-readable information about the device, however for speed numerical IDs may sometimes be desirable. The ID number for a device is persistent, so it can be used even if the device disconnects and later reconnects under the same name. """ IDs, names = self.deviceLists() return zip(IDs, names) @setting(2, 'Select Device', key=[': Select first device', 's: Select device by name', 'w: Select device by ID'], returns='s: Name of the selected device') def select_device(self, c, key=0): """Select a device for the current context.""" dev = self.selectDevice(c, key=key) return dev.name @setting(3, 'Deselect Device', returns='') def deselect_device(self, c): """Deselect a device in the current context.""" dev = self.deselectDevice(c) @setting(1000001, 'Lock Device', timeout=[': Lock the selected device for default time', 'v[s]: Lock for specified time'], returns=['']) def lock_device(self, c, timeout): """Lock a device to be accessible only in this context.""" dev = self.selectedDevice(c) if timeout is not None: timeout = timeout['s'] dev.lock(c.ID, timeout) @setting(1000002, 'Release Device', returns='') def release_device(self, c): """Release the lock on the currently-locked device.""" dev = self.selectedDevice(c) dev.unlock(c.ID)
class ManagedDeviceServer(LabradServer): """A server for devices. Creates a DeviceWrapper for each device it finds, based on a user-provided function. Provides standard settings for listing devices, selecting a device for the current context, and refreshing the list of devices. Also, provides for device-locking with timeouts. Device names and associated wrappers can be specified in two ways 1 (old way) Give a device name or list of device names: deviceName = "nameOfDevice" eg. "Acme XL123" or deviceName = ["nameOfDevice", "nameOfOtherDevice",...] and also give a single device wrapper. deviceWrapper=<a device Wrapper (sub)class> With this method the same device wrapper is used for all detected devices, regardless of eg. model. This works if all models use the same SCPI commands. 2 (new better way) Give a dict mapping device names to wrappers deviceWrappers = {"deviceName":wrapperForThisDevice,...} This allows you to use the same server for eg. devices of the same general type but from different manufacturers or of different models. 1. Old way example deviceName = "Acme Widget" deviceWrapper = AcmeWidgetWrapper 2. New way example deviceWrappers={"Acme Widget": AcmeWidgetExample} Optionally specify a device specific identication function deviceIdentFunc = 'identify_device' """ name = 'Generic Device Server' deviceManager = 'Device Manager' # Default device name and wrapper for backwards compatibility with servers # written before we supported multiple different device types, and which # do not explicitly set deviceWrapper and/or deviceName. deviceName = 'Generic Device' deviceWrapper = DeviceWrapper messageID = 21436587 def __init__(self): #Backward compatibility for servers that don't use a #deviceWrappers dict if not hasattr(self, 'deviceWrappers'): names = self.deviceName if isinstance(names, str): names = [names] self.deviceWrappers = dict((name, self.deviceWrapper) for name in names) LabradServer.__init__(self) @inlineCallbacks def initServer(self): self.devices = MultiDict() # aliases -> device self.device_guids = {} # name -> guid self._next_guid = 0 # register a message handler for connect/disconnect messages handler = lambda c, data: self.handleDeviceMessage(*data) self._cxn.addListener(handler, ID=self.messageID) if self.deviceManager in self.client.servers: yield self.connectToDeviceManager() @inlineCallbacks def stopServer(self): if hasattr(self, 'devices'): ds = [defer.maybeDeferred(dev.shutdown) for dev in self.devices.values()] yield defer.DeferredList(ds, fireOnOneErrback=True) def makeDeviceName(self, device, server, address): return server + ' - ' + address @inlineCallbacks def handleDeviceMessage(self, device, server, address, isConnected=True): print 'Device message:', device, server, address, isConnected name = self.makeDeviceName(device, server, address) if isConnected: # add device if name in self.devices: return # we already have this device if name in self.device_guids: # we've seen this device before # so we'll reuse the old guid guid = self.device_guids[name] else: guid = self.device_guids[name] = self._next_guid self._next_guid += 1 dev = self.deviceWrappers[device](guid, name) yield self.client.refresh() yield dev.connect(self.client[server], address) self.devices[guid, name] = dev else: # remove device yield self.removeDevice(name) @inlineCallbacks def removeDevice(self, name): # we delete the device, but not its guid, so that # if this device comes back, users who have # selected it by guid will reconnect seamlessly if name not in self.devices: return dev = self.devices[name] del self.devices[name] try: yield dev.shutdown() except Exception, e: self.log('Error while shutting down device "%s": %s' % (name, e))
class ManagedDeviceServer(LabradServer): """A server for devices. Creates a DeviceWrapper for each device it finds, based on a user-provided function. Provides standard settings for listing devices, selecting a device for the current context, and refreshing the list of devices. Also, provides for device-locking with timeouts. Device names and associated wrappers can be specified in two ways 1 (old way) Give a device name or list of device names: deviceName = "nameOfDevice" eg. "Acme XL123" or deviceName = ["nameOfDevice", "nameOfOtherDevice",...] and also give a single device wrapper. deviceWrapper=<a device Wrapper (sub)class> With this method the same device wrapper is used for all detected devices, regardless of eg. model. This works if all models use the same SCPI commands. 2 (new better way) Give a dict mapping device names to wrappers deviceWrappers = {"deviceName":wrapperForThisDevice,...} This allows you to use the same server for eg. devices of the same general type but from different manufacturers or of different models. 1. Old way example deviceName = "Acme Widget" deviceWrapper = AcmeWidgetWrapper 2. New way example deviceWrappers={"Acme Widget": AcmeWidgetExample} Optionally specify a device specific identication function deviceIdentFunc = 'identify_device' """ name = 'Generic Device Server' deviceManager = 'Device Manager' # Default device name and wrapper for backwards compatibility with servers # written before we supported multiple different device types, and which # do not explicitly set deviceWrapper and/or deviceName. deviceName = 'Generic Device' deviceWrapper = DeviceWrapper messageID = 21436587 def __init__(self): #Backward compatibility for servers that don't use a #deviceWrappers dict if not hasattr(self, 'deviceWrappers'): names = self.deviceName if isinstance(names, str): names = [names] self.deviceWrappers = dict((name, self.deviceWrapper) for name in names) LabradServer.__init__(self) @inlineCallbacks def initServer(self): self.devices = MultiDict() # aliases -> device self.device_guids = {} # name -> guid self._next_guid = 0 # register a message handler for connect/disconnect messages handler = lambda c, data: self.handleDeviceMessage(*data) self._cxn.addListener(handler, ID=self.messageID) if self.deviceManager in self.client.servers: yield self.connectToDeviceManager() @inlineCallbacks def stopServer(self): if hasattr(self, 'devices'): ds = [defer.maybeDeferred(dev.shutdown) for dev in self.devices.values()] yield defer.DeferredList(ds, fireOnOneErrback=True) def makeDeviceName(self, device, server, address): return server + ' - ' + address @inlineCallbacks def handleDeviceMessage(self, device, server, address, isConnected=True): print('Device message:', device, server, address, isConnected) name = self.makeDeviceName(device, server, address) if isConnected: # add device if name in self.devices: return # we already have this device if name in self.device_guids: # we've seen this device before # so we'll reuse the old guid guid = self.device_guids[name] else: guid = self.device_guids[name] = self._next_guid self._next_guid += 1 dev = self.deviceWrappers[device](guid, name) yield self.client.refresh() yield dev.connect(self.client[server], address) self.devices[guid, name] = dev else: # remove device yield self.removeDevice(name) @inlineCallbacks def removeDevice(self, name): # we delete the device, but not its guid, so that # if this device comes back, users who have # selected it by guid will reconnect seamlessly if name not in self.devices: return dev = self.devices[name] del self.devices[name] try: yield dev.shutdown() except Exception as e: self.log('Error while shutting down device "%s": %s' % (name, e)) @inlineCallbacks def connectToDeviceManager(self): """ """ yield self.client.refresh() manager = self.client[self.deviceManager] #If we have a device identification function register it with the device manager if hasattr(self, 'deviceIdentFunc'): yield manager.register_ident_function(self.deviceIdentFunc) #Register ourself as a server who cares about devices devs = yield manager.register_server(list(self.deviceWrappers.keys()), self.messageID) # the devs list is authoritative any devices we have # that are _not_ on this list should be removed names = [self.makeDeviceName(*dev[:3]) for dev in devs] additions = [self.handleDeviceMessage(*dev) for dev in devs] deletions = [self.removeDevice(name) for name in self.device_guids.keys() if name not in names] yield defer.DeferredList(additions + deletions) def serverConnected(self, ID, name): if name == self.deviceManager: self.connectToDeviceManager() def expireContext(self, c): """Release selected/locked device when context expires.""" if 'device' in c: alias = c['device'] try: dev = self.devices[alias] if dev.lockedInContext(c): dev.unlock(c) dev.deselect(c) except KeyError: pass def deviceLists(self): """Get parallel lists of device names and IDs.""" guids = sorted(self.devices.keys()) names = [self.devices[g].name for g in guids] return guids, names def selectedDevice(self, context): """Get the selected device from the given context.""" if not len(self.devices): raise errors.NoDevicesAvailableError() try: key = context['device'] except KeyError: raise errors.DeviceNotSelectedError() try: dev = self.devices[key] except KeyError: raise errors.NoSuchDeviceError() if not dev.accessibleFrom(context.ID): raise DeviceLockedError() return dev def selectDevice(self, context, key=None): """Select a device in our current context.""" if not len(self.devices): raise errors.NoDevicesAvailableError() if key is None: # use the first device key = sorted(self.devices.keys())[0] try: dev = self.devices[key] except KeyError: raise errors.NoSuchDeviceError() if not dev.accessibleFrom(context.ID): raise DeviceLockedError() if 'device' in context: if context['device'] != dev.guid: try: oldDev = self.devices[context['device']] except KeyError: pass else: # we're trying to select a new device. # make sure to unlock previously selected device if oldDev.lockedInContext(context.ID): oldDev.unlock(context.ID) oldDev.deselect(context) context['device'] = dev.guid dev.select(context) else: context['device'] = dev.guid dev.select(context) return dev def deselectDevice(self, context): if 'device' in context: try: oldDev = self.devices[context['device']] except KeyError: pass else: # unlock and deselect device if oldDev.lockedInContext(context.ID): oldDev.unlock(context.ID) oldDev.deselect(context) del context['device'] def getDevice(self, context, key=None): if not len(self.devices): raise errors.NoDevicesAvailableError() if key is None: # use the first device key = sorted(self.devices.keys())[0] try: dev = self.devices[key] except KeyError: raise errors.NoSuchDeviceError() if not dev.accessibleFrom(context.ID): raise DeviceLockedError() return dev # server settings @setting(1, 'List Devices', returns='*(ws)') def list_devices(self, c): """Get a list of available devices. The list entries have a numerical ID and a string name. It is recommended to use the names wherever possible to identify devices, since this contains human-readable information about the device, however for speed numerical IDs may sometimes be desirable. The ID number for a device is persistent, so it can be used even if the device disconnects and later reconnects under the same name. """ IDs, names = self.deviceLists() return list(zip(IDs, names)) @setting(2, 'Select Device', key=[': Select first device', 's: Select device by name', 'w: Select device by ID'], returns='s: Name of the selected device') def select_device(self, c, key=0): """Select a device for the current context.""" dev = self.selectDevice(c, key=key) return dev.name @setting(3, 'Deselect Device', returns='') def deselect_device(self, c): """Deselect a device in the current context.""" dev = self.deselectDevice(c) @setting(1000001, 'Lock Device', timeout=[': Lock the selected device for default time', 'v[s]: Lock for specified time'], returns=['']) def lock_device(self, c, timeout): """Lock a device to be accessible only in this context.""" dev = self.selectedDevice(c) if timeout is not None: timeout = timeout['s'] dev.lock(c.ID, timeout) @setting(1000002, 'Release Device', returns='') def release_device(self, c): """Release the lock on the currently-locked device.""" dev = self.selectedDevice(c) dev.unlock(c.ID)
class DeviceServer(LabradServer): """A server for devices. Creates a DeviceWrapper for each device it finds, based on a user-provided function. Provides standard settings for listing devices, selecting a device for the current context, and refreshing the list of devices. Also, provides for device-locking with timeouts. """ name = 'Generic Device Server' deviceWrapper = DeviceWrapper def initServer(self): self.devices = MultiDict() # aliases -> device self.device_guids = {} # name -> guid self._next_guid = 0 self._refreshLock = defer.DeferredLock() return self.refreshDeviceList() def stopServer(self): if hasattr(self, 'devices'): ds = [ defer.maybeDeferred(dev.shutdown) for dev in self.devices.values() ] return defer.DeferredList(ds, fireOnOneErrback=True) def findDevices(self): """Return a list of found devices. The result should be a list of (name, args, kw) tuples where args and kw are the arguments tuple and keyword dict that will be used to call the device's connect function. """ return [] def refreshDeviceList(self): """Refresh the list of available devices. Devices are assigned a unique identifying number that will persist between refreshes, so that clients that have selected devices in context will still be able to refer to them after the refresh. """ return self._refreshLock.run(self._doRefresh) def chooseDeviceWrapper(self, *args, **kw): """ Override in subclass to support multiple device wrapper classes args and kw come from findDevices (ie same as are passed into the device wrapper's connect method). """ return self.deviceWrapper @inlineCallbacks def _doRefresh(self): """Do the actual refreshing.""" self.log('refreshing device list...') all_found = yield self.findDevices() # If there are devices for which we don't have wrappers, # create them. If we have a wrapper for a # device that is no longer available, then that wrapper should # be marked somehow to indicate that it is no longer available. def fixFound(f): if isinstance(f, str): f = f, (), {} name = f[0] args = f[1] if len(f) > 1 else () kw = f[2] if len(f) > 2 else {} return name, args, kw all_found = [fixFound(f) for f in all_found] additions = [(name, args, kw) for (name, args, kw) in all_found if name not in self.devices] names_found = [name for (name, args, kw) in all_found] deletions = [ name for name in self.device_guids if name in self.devices and name not in names_found ] self.log('all_found: %s' % all_found) self.log('additions: %s' % additions) self.log('deletions: %s' % deletions) # start additions for name, args, kw in additions: if name in self.device_guids: # we've seen this device before # so we'll reuse the old guid guid = self.device_guids[name] else: guid = self.device_guids[name] = self._next_guid self._next_guid += 1 deviceWrapper = self.chooseDeviceWrapper(name, *args, **kw) dev = deviceWrapper(guid, name) yield dev.connect(*args, **kw) self.devices[guid, name] = dev # do deletions for name in deletions: # we delete the device, but not its guid, so that # if this device comes back, users who have # selected it by guid will reconnect seamlessly dev = self.devices[name] del self.devices[name] try: yield dev.shutdown() except Exception as e: self.log('Error while shutting down device "%s": %s' % (name, e)) def serverConnected(self, ID, name): self.refreshDeviceList() def serverDisconnected(self, ID, name): self.refreshDeviceList() def expireContext(self, c): """Release selected/locked device when context expires.""" if 'device' in c: alias = c['device'] try: dev = self.devices[alias] if dev.lockedInContext(c): dev.unlock(c) dev.deselect(c) except KeyError: pass def deviceLists(self): """Get parallel lists of device names and IDs.""" guids = sorted(self.devices.keys()) names = [self.devices[g].name for g in guids] return guids, names def selectedDevice(self, context): """Get the selected device from the given context.""" if not len(self.devices): raise errors.NoDevicesAvailableError() try: key = context['device'] except KeyError: raise errors.DeviceNotSelectedError() try: dev = self.devices[key] except KeyError: raise errors.NoSuchDeviceError() if not dev.accessibleFrom(context.ID): raise DeviceLockedError() return dev def selectDevice(self, context, key=None): """Select a device in our current context.""" if not len(self.devices): raise errors.NoDevicesAvailableError() if key is None: # use the first device key = sorted(self.devices.keys())[0] try: dev = self.devices[key] except KeyError: raise errors.NoSuchDeviceError(key) if not dev.accessibleFrom(context.ID): raise DeviceLockedError() if 'device' in context: if context['device'] != dev.guid: try: oldDev = self.devices[context['device']] except KeyError: pass else: # we're trying to select a new device. # make sure to unlock previously selected device if oldDev.lockedInContext(context.ID): oldDev.unlock(context.ID) oldDev.deselect(context) context['device'] = dev.guid dev.select(context) else: context['device'] = dev.guid dev.select(context) return dev def deselectDevice(self, context): if 'device' in context: try: oldDev = self.devices[context['device']] except KeyError: pass else: # unlock and deselect device if oldDev.lockedInContext(context.ID): oldDev.unlock(context.ID) oldDev.deselect(context) del context['device'] def getDevice(self, context, key=None): if not len(self.devices): raise errors.NoDevicesAvailableError() if key is None: # use the first device key = sorted(self.devices.keys())[0] try: dev = self.devices[key] except KeyError: raise errors.NoSuchDeviceError() if not dev.accessibleFrom(context.ID): raise DeviceLockedError() return dev # server settings @setting(1, 'List Devices', returns=['*(ws)']) def list_devices(self, c): """List available devices.""" IDs, names = self.deviceLists() return zip(IDs, names) @setting(2, 'Select Device', key=[ ': Select first device', 's: Select device by name', 'w: Select device by ID' ], returns=['s: Name of the selected device']) def select_device(self, c, key=0): """Select a device for the current context.""" dev = self.selectDevice(c, key=key) return dev.name @setting(3, 'Deselect Device', returns=['']) def deselect_device(self, c): """Select a device for the current context.""" dev = self.deselectDevice(c) @setting(4, 'Refresh Devices', returns=['*(ws)']) def refresh_devices(self, c): """Refresh the list of available devices.""" yield self.refreshDeviceList() returnValue(self.list_devices(c)) @setting(1000001, 'Lock Device', timeout=[ ': Lock the selected device for default time', 'v[s]: Lock for specified time' ], returns=['']) def lock_device(self, c, timeout): """Lock a device to be accessible only in this context.""" dev = self.selectedDevice(c) if timeout is not None: timeout = timeout['s'] dev.lock(c.ID, timeout) @setting(1000002, 'Release Device', returns=['']) def release_device(self, c): """Release the lock on the currently-locked device.""" dev = self.selectedDevice(c) dev.unlock(c.ID)
class CustomPacketWrapper(AsyncPacketWrapper): settings = MultiDict() _server = server _cache = {}