Esempio n. 1
0
 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()
Esempio n. 2
0
 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()
Esempio n. 3
0
 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()
Esempio n. 4
0
 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()
Esempio n. 5
0
 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()
Esempio n. 6
0
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))
Esempio n. 7
0
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))
Esempio n. 8
0
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)
Esempio n. 9
0
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)
Esempio n. 10
0
 def initServer(self):
     self.devices = MultiDict()  # aliases -> device
     self.device_guids = {}  # name -> guid
     self._next_guid = 0
     self._refreshLock = defer.DeferredLock()
     return self.refreshDeviceList()
Esempio n. 11
0
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)
Esempio n. 12
0
 def initServer(self):
     self.devices = MultiDict() # aliases -> device
     self.device_guids = {} # name -> guid
     self._next_guid = 0
     self._refreshLock = defer.DeferredLock()
     return self.refreshDeviceList()
Esempio n. 13
0
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))
Esempio n. 14
0
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)
Esempio n. 15
0
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)
Esempio n. 16
0
 class CustomPacketWrapper(AsyncPacketWrapper):
     settings = MultiDict()
     _server = server
     _cache = {}
Esempio n. 17
0
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)