예제 #1
0
    def request_help(self, msg):
        """Return help on the available requests.

        Return a description of the available requests using a seqeunce of #help informs.

        Parameters
        ----------
        request : str, optional
            The name of the request to return help for (the default is to return help for all requests).

        Informs
        -------
        request : str
            The name of a request.
        description : str
            Documentation for the named request.

        Returns
        -------
        success : {'ok', 'fail'}
            Whether sending the help succeeded.
        informs : int
            Number of #help inform messages sent.

        Examples
        --------
        ::

            ?help
            #help halt ...description...
            #help help ...description...
            ...
            !help ok 5

            ?help halt
            #help halt ...description...
            !help ok 1
        """
        if msg.arguments:
            name = msg.arguments[0]
            meth = getattr(self, 'request_' + name.replace('-', '_'), None)
            if meth is None:
                return Message.reply('help', 'fail', 'Unknown request method.')
            doc = meth.__doc__
            if doc is not None:
                doc = doc.strip()
            self.send_message(Message.inform('help', name, doc))
            return Message.reply('help', 'ok', '1')
        count = 0
        for name in dir(self.__class__):
            item = getattr(self, name)
            if name.startswith('request_') and callable(item):
                sname = name[len('request_'):]
                doc = item.__doc__
                if doc is not None:
                    doc = doc.strip()
                self.send_message(Message.inform('help', sname, doc))
                count += 1
        return Message.reply(msg.name, "ok", str(count))
예제 #2
0
 def request_drop_connection(self, msg):
     """ drops connection to specified device, for demo purposes
     only
     """
     if not msg.arguments:
         return Message.reply('drop-connection', 'fail',
                              'Argument required')
     try:
         dev_name = msg.arguments[0]
         self.factory.devices[dev_name].transport.loseConnection()
         print dev_name, "disconnected"
         return Message.reply('drop-connection', 'ok')
     except KeyError:
         return Message.reply('drop-connection', 'fail',
                              'Unknown device %s' % dev_name)
예제 #3
0
 def request_foo(self, msg):
     """ This is called when ?foo is called from the other side.
     """
     # send one inform
     self.send_message(Message.inform('foo', 'fine'))
     # return reply
     return Message.reply('foo', 'ok', '1')
예제 #4
0
    def test_request_one(self):
        """Test request with no defaults."""
        req = mock.Mock()
        req.msg.name = 'one'
        self.assertEqual(str(self.device.request_one(req, Message.request(
                        "one", "2", "on", "0"))), "!one ok 2 on 0")
        self.assertRaises(FailReply, self.device.request_one, req,
                          Message.request("one", "14", "on", "0"))
        self.assertRaises(FailReply, self.device.request_one, req,
                          Message.request("one", "2", "dsfg", "0"))
        self.assertRaises(FailReply, self.device.request_one, req,
                          Message.request("one", "2", "on", "3"))
        self.assertRaises(FailReply, self.device.request_one, req,
                          Message.request("one", "2", "on", "0", "3"))

        self.assertRaises(FailReply, self.device.request_one, req,
                          Message.request("one", "2", "on"))

        self.assertEqual(str(self.device.request_one(req, Message.request(
                        "one", "3", "on", "0"))), "!one fail I\\_failed!")
        self.assertRaises(ValueError, self.device.request_one, req,
                          Message.request("one", "5", "on", "0"))
        self.assertRaises(ValueError, self.device.request_one, req,
                          Message.request("one", "6", "on", "0"))

        req.reset_mock()
        self.assertRaises(AsyncReply, self.device.request_one, req,
                          Message.request("one", "9", "on", "0"))
        self.assertEqual(req.reply_with_message.call_count, 1)
        req.reply_with_message.assert_called_once_with(Message.reply(
            'one', 'ok', '9', 'on', '0'))
예제 #5
0
 def callback(msg):
     if device.state is device.UNSYNCED:
         return Message.reply(dev_name + "-" + req_name, "fail",
                              "Device not synced")
     d = device.send_request(req_name, *msg.arguments)
     d.addCallbacks(request_returned, request_failed)
     raise AsyncReply()
예제 #6
0
 def callback((informs, reply)):
     self.assertEquals(informs[2:],
           [Message.inform('sensor-value', '1000', '1', 'device.sensor1',
                           'unknown', '0'),
            Message.inform('sensor-value', '0', '1', 'device.sensor2',
                           'unknown', '0')])
     self.assertEquals(reply, Message.reply('sensor-value', 'ok', '4'))
예제 #7
0
    def test_request_one(self):
        """Test request with no defaults."""
        req = mock.Mock()
        req.msg.name = 'one'
        self.assertEqual(str(self.device.request_one(req, Message.request(
                        "one", "2", "on", "0"))), "!one ok 2 on 0")
        self.assertRaises(FailReply, self.device.request_one, req,
                          Message.request("one", "14", "on", "0"))
        self.assertRaises(FailReply, self.device.request_one, req,
                          Message.request("one", "2", "dsfg", "0"))
        self.assertRaises(FailReply, self.device.request_one, req,
                          Message.request("one", "2", "on", "3"))
        self.assertRaises(FailReply, self.device.request_one, req,
                          Message.request("one", "2", "on", "0", "3"))

        self.assertRaises(FailReply, self.device.request_one, req,
                          Message.request("one", "2", "on"))

        self.assertEqual(str(self.device.request_one(req, Message.request(
                        "one", "3", "on", "0"))), "!one fail I\\_failed!")
        self.assertRaises(ValueError, self.device.request_one, req,
                          Message.request("one", "5", "on", "0"))
        self.assertRaises(ValueError, self.device.request_one, req,
                          Message.request("one", "6", "on", "0"))

        req.reset_mock()
        self.assertRaises(AsyncReply, self.device.request_one, req,
                          Message.request("one", "9", "on", "0"))
        self.assertEqual(req.reply_with_message.call_count, 1)
        req.reply_with_message.assert_called_once_with(Message.reply(
            'one', 'ok', '9', 'on', '0'))
예제 #8
0
    def request_client_list(self, msg):
        """Request the list of connected clients.

        The list of clients is sent as a sequence of #client-list informs.

        Informs
        -------
        addr : str
            The address of the client as host:port with host in dotted quad
            notation. If the address of the client could not be determined
            (because, for example, the client disconnected suddenly) then
            a unique string representing the client is sent instead.

        Returns
        -------
        success : {'ok', 'fail'}
            Whether sending the client list succeeded.
        informs : int
            Number of #client-list inform messages sent.

        Examples
        --------
        ::

            ?client-list
            #client-list 127.0.0.1:53600
            !client-list ok 1
        """
        for ip, port in self.factory.clients:
            self.send_message(Message.inform(msg.name, "%s:%s" % (ip, port)))
        return Message.reply(msg.name, "ok", len(self.factory.clients))
예제 #9
0
 def callback(msg):
     if device.state is device.UNSYNCED:
         return Message.reply(dev_name + "-" + req_name, "fail",
                              "Device not synced")
     d = device.send_request(req_name, *msg.arguments)
     d.addCallbacks(request_returned, request_failed)
     raise AsyncReply()
예제 #10
0
 def callback((informs, reply)):
     self.assertEquals(informs[2:], [
         Message.inform('sensor-value', '1000', '1', 'device.sensor1',
                        'unknown', '0'),
         Message.inform('sensor-value', '0', '1', 'device.sensor2',
                        'unknown', '0')
     ])
     self.assertEquals(reply, Message.reply('sensor-value', 'ok', '4'))
예제 #11
0
 def request_add_sensor(self, msg):
     self.factory.add_sensor(
         Sensor(int,
                'int_sensor%d' % len(self.factory.sensors),
                'descr',
                'unit',
                params=[-10, 10]))
     return Message.reply('add-sensor', 'ok')
예제 #12
0
 def handle_request(self, msg):
     name = msg.name
     name = name.replace('-', '_')
     try:
         rep_msg = getattr(self, 'request_' + name, self._request_unknown)(msg)
         if not isinstance(rep_msg, Message):
             raise ShouldReturnMessage('request_' + name + ' should return a'
                                       'message or raise AsyncReply, instead'
                                       'it returned %r' % rep_msg)
         self.send_message(rep_msg)
     except FailReply, fr:
         self.send_message(Message.reply(name, "fail", str(fr)))
예제 #13
0
    def __getattr__(self, attr):
        # TODO: It would be cleaner if a new request method / callback
        # wasn't created for every proxied request.
        # TODO: These proxied methods should appear in the ?help for the proxy
        # but currently don't.

        def request_returned((informs, reply), reqmsg):
            assert informs == []  # for now
            # we *could* in theory just change message name, but let's copy
            # just in case
            self.send_reply(Message.reply(dev_name + "-" + req_name,
                                          *reply.arguments), reqmsg)
예제 #14
0
    def request_version_list(self, msg):
        """Request the list of versions of roles and subcomponents.

        Informs
        -------
        name : str
            Name of the role or component.
        version : str
            A string identifying the version of the component. Individual
            components may define the structure of this argument as they
            choose. In the absence of other information clients should
            treat it as an opaque string.
        build_state_or_serial_number : str
            A unique identifier for a particular instance of a component.
            This should change whenever the component is replaced or updated.

        Returns
        -------
        success : {'ok', 'fail'}
            Whether sending the version list succeeded.
        informs : int
            Number of #version-list inform messages sent.

        Examples
        --------
        ::

            ?version-list
            #version-list katcp-protocol 5.0-MI
            #version-list katcp-library katcp-python-tx-0.4 katcp-python-0.4.1
            #version-list katcp-device foodevice-1.0 foodevice-1.0.0rc1
            !version-list ok 3
        """
        versions = [
            ("katcp-protocol", (self.PROTOCOL_INFO, None)),
            ("katcp-library", ("katcp-python-tx-%d.%d" % VERSION[:2],
                               VERSION_STR)),
            ("katcp-device", (self.version(), self.build_state())),
        ]
        extra_versions = sorted(self.extra_versions.items())

        for name, (version, build_state) in versions + extra_versions:
            if build_state is None:
                inform = Message.inform(msg.name, name, version)
            else:
                inform = Message.inform(msg.name, name, version, build_state)
            self.send_reply_inform(inform, msg)

        num_versions = len(versions) + len(extra_versions)
        return Message.reply(msg.name, "ok", str(num_versions))
예제 #15
0
    def request_version_list(self, msg):
        """Request the list of versions of roles and subcomponents.

        Informs
        -------
        name : str
            Name of the role or component.
        version : str
            A string identifying the version of the component. Individual
            components may define the structure of this argument as they
            choose. In the absence of other information clients should
            treat it as an opaque string.
        build_state_or_serial_number : str
            A unique identifier for a particular instance of a component.
            This should change whenever the component is replaced or updated.

        Returns
        -------
        success : {'ok', 'fail'}
            Whether sending the version list succeeded.
        informs : int
            Number of #version-list inform messages sent.

        Examples
        --------
        ::

            ?version-list
            #version-list katcp-protocol 5.0-MI
            #version-list katcp-library katcp-python-tx-0.4 katcp-python-0.4.1
            #version-list katcp-device foodevice-1.0 foodevice-1.0.0rc1
            !version-list ok 3
        """
        versions = [
            ("katcp-protocol", (self.PROTOCOL_INFO, None)),
            ("katcp-library", ("katcp-python-tx-%d.%d" % VERSION[:2],
                               VERSION_STR)),
            ("katcp-device", (self.version(), self.build_state())),
            ]
        extra_versions = sorted(self.extra_versions.items())

        for name, (version, build_state) in versions + extra_versions:
            if build_state is None:
                inform = Message.inform(msg.name, name, version)
            else:
                inform = Message.inform(msg.name, name, version, build_state)
            self.send_reply_inform(inform, msg)

        num_versions = len(versions) + len(extra_versions)
        return Message.reply(msg.name, "ok", str(num_versions))
예제 #16
0
    def request_watchdog(self, msg):
        """Check that the server is still alive.

        Returns
        -------
            success : {'ok'}

        Examples
        --------
        ::

            ?watchdog
            !watchdog ok
        """
        return Message.reply("watchdog", "ok")
예제 #17
0
    def request_halt(self, msg):
        """ drops connection to specified device
        """
        def got_halt((informs, reply)):
            self.send_reply(Message.reply('halt', dev_name, 'ok'), msg)

        if not msg.arguments:
            return DeviceProtocol.halt(self, msg)
        try:
            dev_name = msg.arguments[0]
            device = self.factory.devices[dev_name]
        except KeyError:
            return Message.reply('halt', 'fail',
                                 'Unknown device %s' % dev_name)
        else:
            self.factory.unregister_device(dev_name)
            device.send_request('halt').addCallback(got_halt)
            raise AsyncReply()
예제 #18
0
    def request_halt(self, msg):
        """ drops connection to specified device
        """
        def got_halt((informs, reply)):
            self.send_message(Message.reply('halt', dev_name, 'ok'))

        if not msg.arguments:
            return DeviceProtocol.halt(self, msg)
        try:
            dev_name = msg.arguments[0]
            device = self.factory.devices[dev_name]
        except KeyError:
            return Message.reply('halt', 'fail',
                                 'Unknown device %s' % dev_name)
        else:
            self.factory.unregister_device(dev_name)
            device.send_request('halt').addCallback(got_halt)
            raise AsyncReply()
예제 #19
0
    def request_halt(self, msg):
        """Halt the device server.

        Returns
        -------
        success : {'ok', 'fail'}
            Whether scheduling the halt succeeded.

        Examples
        --------
        ::

            ?halt
            !halt ok
        """
        self.send_message(Message.reply("halt", "ok"))
        self.factory.stop()
        raise AsyncReply()
예제 #20
0
 def callback((informs, reply)):
     assert len(informs) == 2
     self.assertEquals(reply, Message.reply("device-list", "ok", "2"))
예제 #21
0
 def callback((informs, reply)):
     assert len(informs) == 4
     assert reply == Message.reply('sensor-list', 'ok', '4')
예제 #22
0
 def callback((informs, reply)):
     self.assertEquals(reply, Message.reply('device2-req', 'fail',
                                            'Device not synced'))
예제 #23
0
    def request_sensor_sampling(self, msg):
        """Configure or query the way a sensor is sampled.

        Sampled values are reported asynchronously using the #sensor-status
        message.

        Parameters
        ----------
        name : str
            Name of the sensor whose sampling strategy to query or configure.
        strategy : {'none', 'auto', 'event', 'differential', 'period'}, optional
            Type of strategy to use to report the sensor value. The differential
            strategy type may only be used with integer or float sensors.
        params : list of str, optional
            Additional strategy parameters (dependent on the strategy type).
            For the differential strategy, the parameter is an integer or float
            giving the amount by which the sensor value may change before an
            updated value is sent. For the period strategy, the parameter is the
            period to sample at in milliseconds. For the event strategy, an
            optional minimum time between updates in milliseconds may be given.

        Returns
        -------
        success : {'ok', 'fail'}
            Whether the sensor-sampling request succeeded.
        name : str
            Name of the sensor queried or configured.
        strategy : {'none', 'auto', 'event', 'differential', 'period'}
            Name of the new or current sampling strategy for the sensor.
        params : list of str
            Additional strategy parameters (see description under Parameters).

        Examples
        --------
        ::

            ?sensor-sampling cpu.power.on
            !sensor-sampling ok cpu.power.on none

            ?sensor-sampling cpu.power.on period 500
            !sensor-sampling ok cpu.power.on period 500
        """
        if not msg.arguments:
            return Message.reply(msg.name, "fail", "No sensor name given.")
        sensor = self.factory.sensors.get(msg.arguments[0], None)
        if sensor is None:
            return Message.reply(msg.name, "fail", "Unknown sensor name.")
        if len(msg.arguments) == 1:
            StrategyClass = NoStrategy
        else:
            StrategyClass = self.SAMPLING_STRATEGIES.get(msg.arguments[1], None)
            if StrategyClass is None:
                return Message.reply(msg.name, "fail", "Unknown strategy name.")
        # stop the previous strategy
        try:
            self.strategies[sensor.name].cancel()
        except KeyError:
            pass
        strategy = StrategyClass(self, sensor)
        strategy.run(*msg.arguments[2:])
        self.strategies[sensor.name] = strategy
        if len(msg.arguments) == 1:
            msg.arguments.append('none')
        return Message.reply(msg.name, "ok", *msg.arguments)
예제 #24
0
 def callback((informs, reply)):
     self.assertEquals(reply, Message.reply('halt', 'device', 'ok'))
     assert self.proxy.devices.keys() == ['device2']
 def request_sparkling_new(self, req, msg):
     """A new command."""
     return Message.reply(msg.name, "ok", "bling1", "bling2")
예제 #26
0
 def callback((informs, reply)):
     self.assertEquals(
         reply, Message.reply('device2-req', 'fail',
                              'Device not synced'))
예제 #27
0
 def callback((informs, reply)):
     assert len(informs) == 2
     self.assertEquals(reply, Message.reply("device-list", "ok", "2"))
예제 #28
0
 def request_returned((informs, reply)):
     assert informs == []  # for now
     # we *could* in theory just change message name, but let's copy
     # just in case
     self.send_message(
         Message.reply(dev_name + "-" + req_name, *reply.arguments))
예제 #29
0
 def _request_unknown(self, msg):
     return Message.reply(msg.name, "invalid", "Unknown request.")
예제 #30
0
 def callback((informs, reply)):
     assert len(informs) == 2
     self.assertEquals(reply, Message.reply('sensor-list', 'ok', '2'))
예제 #31
0
 def issue_request(self, *args, **kwargs):
     reply_msg = Message.reply('fake', 'ok')
     reply = KATCPReply(reply_msg, [])
     fut = Future()
     fut.set_result(reply)
     return fut
예제 #32
0
 def request_add_sensor(self, sock, msg):
     """ add a sensor
     """
     self.add_sensor(Sensor(int, 'int_sensor%d' % len(self._sensors),
                            'descr', 'unit', params=[-10, 10]))
     return Message.reply('add-sensor', 'ok')
예제 #33
0
 def callback((informs, reply)):
     self.assertEquals(reply, Message.reply('halt', 'device', 'ok'))
     assert self.proxy.devices.keys() == ['device2']
예제 #34
0
 def works((informs, reply)):
     self.assertEquals(reply, Message.reply('device-watchdog', 'ok'))
     self.port.stopListening()
     self.proxy.stop()
     self.client.transport.loseConnection()
     self.finish.callback(None)
예제 #35
0
 def callback((informs, reply)):
     self.assertEquals(reply, Message.reply("device-req", "ok", "3"))
예제 #36
0
 def callback((informs, reply)):
     assert len(informs) == 2
     self.assertEquals(reply, Message.reply('sensor-list', 'ok', '2'))
예제 #37
0
 def handler(self, req, msg):
     """A new command."""
     return Message.reply(msg.name, "ok", "bling1", "bling2")
예제 #38
0
 def request_failed(failure):
     self.send_message(
         Message.reply(dev_name + '-' + req_name, "fail",
                       failure.getErrorMessage()))
예제 #39
0
 def callback((informs, reply)):
     assert len(informs) == 4
     assert reply == Message.reply('sensor-list', 'ok', '4')
예제 #40
0
 def wrapped_request(request_name, *args, **kwargs):
     f = tornado.concurrent.Future()
     retval = resource.KATCPReply(Message.reply(request_name, 'ok'), [])
     f.set_result(retval)
     return f
예제 #41
0
 def works((informs, reply)):
     self.assertEquals(reply, Message.reply('device-watchdog', 'ok'))
     self.port.stopListening()
     self.proxy.stop()
     self.client.transport.loseConnection()
     self.finish.callback(None)
예제 #42
0
 def all_finished(lst):
     # XXX write a test where lst is not-empty so we can fail
     self.send_message(Message.reply(msg.name, "ok",
                                     len(sensors)))
예제 #43
0
 def request_sparkling_new(self, req, msg):
     """A new command."""
     return Message.reply(msg.name, "ok", "bling1", "bling2")
예제 #44
0
    def request_sensor_list(self, msg):
        """Request the list of sensors.

        The list of sensors is sent as a sequence of #sensor-list informs.

        Parameters
        ----------
        name : str, optional
            Name of the sensor to list (the default is to list all sensors).
            If name starts and ends with '/' it is treated as a regular expression and
            all sensors whose names contain the regular expression are returned.

        Informs
        -------
        name : str
            The name of the sensor being described.
        description : str
            Description of the named sensor.
        units : str
            Units for the value of the named sensor.
        type : str
            Type of the named sensor.
        params : list of str, optional
            Additional sensor parameters (type dependent). For integer and float
            sensors the additional parameters are the minimum and maximum sensor
            value. For discrete sensors the additional parameters are the allowed
            values. For all other types no additional parameters are sent.

        Returns
        -------
        success : {'ok', 'fail'}
            Whether sending the sensor list succeeded.
        informs : int
            Number of #sensor-list inform messages sent.

        Examples
        --------
        ::

            ?sensor-list
            #sensor-list psu.voltage PSU\_voltage. V float 0.0 5.0
            #sensor-list cpu.status CPU\_status. \@ discrete on off error
            ...
            !sensor-list ok 5

            ?sensor-list cpu.power.on
            #sensor-list cpu.power.on Whether\_CPU\_hase\_power. \@ boolean
            !sensor-list ok 1
        """
        exact, name_filter = construct_name_filter(msg.arguments[0]
                    if msg.arguments else None)
        sensors = [(name, sensor) for name, sensor in
                    sorted(self.factory.sensors.iteritems()) if name_filter(name)]

        if exact and not sensors:
            return Message.reply(msg.name, "fail", "Unknown sensor name.")

        for name, sensor in sensors:
            self.send_message(Message.inform(msg.name, name, sensor.description,
                                             sensor.units, sensor.stype,
                                             *sensor.formatted_params))
        return Message.reply(msg.name, "ok", len(sensors))
예제 #45
0
 def request_add_sensor(self, sock, msg):
     """ add a sensor
     """
     self.add_sensor(Sensor(int, 'int_sensor%d' % len(self._sensors),
                            'descr', 'unit', params=[-10, 10]))
     return Message.reply('add-sensor', 'ok')
예제 #46
0
 def callback((informs, reply)):
     self.assertEquals(reply, Message.reply("device-req", "ok", "3"))
예제 #47
0
                raise ShouldReturnMessage('request_' + name + ' should return a'
                                          'message or raise AsyncReply, instead'
                                          'it returned %r' % rep_msg)
            self.send_message(rep_msg)
        except FailReply, fr:
            self.send_message(Message.reply(name, "fail", str(fr)))
        except AsyncReply:
            return
        except Exception:
            e_type, e_value, trace = sys.exc_info()
            log.err()
            reason = "\n".join(traceback.format_exception(
                e_type, e_value, trace, TB_LIMIT
                ))

            self.send_message(Message.reply(msg.name, "fail", reason))

    def handle_reply(self, msg):
        if not self.queries:
            raise NoQuerriesProcessed()
        name, d, queue = self.queries[0]
        if name != msg.name:
            d.errback("Wrong request order")
        self.queries.pop(0) # hopefully it's not large
        d.callback((queue, msg))

    def send_message(self, msg):
        # just serialize a message
        self.transport.write(str(msg) + self.delimiter)

    def connectionLost(self, failure):
예제 #48
0
    def request_sensor_value(self, msg):
        """Request the value of a sensor or sensors.

        A list of sensor values as a sequence of #sensor-value informs.

        Parameters
        ----------
        name : str, optional
            Name of the sensor to poll (the default is to send values for all sensors).
            If name starts and ends with '/' it is treated as a regular expression and
            all sensors whose names contain the regular expression are returned.

        Informs
        -------
        timestamp : float
            Timestamp of the sensor reading in milliseconds since the Unix epoch.
        count : {1}
            Number of sensors described in this #sensor-value inform. Will always
            be one. It exists to keep this inform compatible with #sensor-status.
        name : str
            Name of the sensor whose value is being reported.
        value : object
            Value of the named sensor. Type depends on the type of the sensor.

        Returns
        -------
        success : {'ok', 'fail'}
            Whether sending the list of values succeeded.
        informs : int
            Number of #sensor-value inform messages sent.

        Examples
        --------
        ::

            ?sensor-value
            #sensor-value 1244631611415.231 1 psu.voltage 4.5
            #sensor-value 1244631611415.200 1 cpu.status off
            ...
            !sensor-value ok 5

            ?sensor-value cpu.power.on
            #sensor-value 1244631611415.231 1 cpu.power.on 0
            !sensor-value ok 1
        """
        exact, name_filter = construct_name_filter(msg.arguments[0]
                    if msg.arguments else None)
        sensors = [(name, sensor) for name, sensor in
                    sorted(self.factory.sensors.iteritems()) if name_filter(name)]

        if exact and not sensors:
            return Message.reply(msg.name, "fail", "Unknown sensor name.")

        def one_ok(sensor, timestamp_ms, status, value):
            self.send_message(Message.inform(msg.name, timestamp_ms, "1",
                                             sensor.name, status, value))

        def one_fail(failure, sensor):
            self.send_message(Message.inform(msg.name, sensor.name,
                                             "Sensor reading failed."))

        def all_finished(lst):
            # XXX write a test where lst is not-empty so we can fail
            self.send_message(Message.reply(msg.name, "ok",
                                            len(sensors)))

        lst = []
        for name, sensor in sensors:
            self.read_formatted_from_sensor(sensor, one_ok, one_fail, lst)
        DeferredList(lst).addCallback(all_finished)
        raise AsyncReply()