Esempio n. 1
0
class WorkerWrapper(object):
    """Wrapper around a client to an FbfWorkerServer
    instance.
    """
    def __init__(self, hostname, port):
        """
        @brief  Create a new wrapper around a client to a worker server

        @params hostname The hostname for the worker server
        @params port     The port number that the worker server serves on
        """
        log.debug("Creating worker client to worker at {}:{}".format(hostname, port))
        self._client = KATCPClientResource(dict(
            name="worker-server-client",
            address=(hostname, port),
            controlled=True))
        self.hostname = hostname
        self.port = port
        self.priority = 0 # Currently no priority mechanism is implemented
        self._started = False

    def start(self):
        """
        @brief  Start the client to the worker server
        """
        log.debug("Starting client to worker at {}:{}".format(self.hostname, self.port))
        self._client.start()
        self._started = True

    def __repr__(self):
        return "<{} @ {}:{}>".format(self.__class__.__name__, self.hostname, self.port)

    def __hash__(self):
        # This has override is required to allow these wrappers
        # to be used with set() objects. The implication is that
        # the combination of hostname and port is unique for a
        # worker server
        return hash((self.hostname, self.port))

    def __eq__(self, other):
        # Also implemented to help with hashing
        # for sets
        return self.__hash__() == hash(other)

    def __del__(self):
        if self._started:
            try:
                self._client.stop()
            except Exception as error:
                log.exception(str(error))
Esempio n. 2
0
class KatcpSidecar(object):
    def __init__(self, host, port):
        """
        Constructs a new instance.

        :param      host:  The address of the server to sidecar
        :param      port:  The server port
        """
        log.debug("Constructing sidecar for {}:{}".format(host, port))
        self.rc = KATCPClientResource(
            dict(name="sidecar-client", address=(host, port), controlled=True))
        self._update_callbacks = set()
        self._previous_sensors = set()

    @coroutine
    def start(self):
        """
        @brief     Start the sidecar
        """
        @coroutine
        def _start():
            log.debug("Waiting on synchronisation with server")
            yield self.rc.until_synced()
            log.debug("Client synced")
            log.debug("Requesting version info")
            response = yield self.rc.req.version_list()
            log.info("response: {}".format(response))
            self.ioloop.add_callback(self.on_interface_changed)

        self.rc.start()
        self.ic = self.rc._inspecting_client
        self.ioloop = self.rc.ioloop
        self.ic.katcp_client.hook_inform(
            "interface-changed", lambda message: self.ioloop.add_callback(
                self.on_interface_changed))
        self.ioloop.add_callback(_start)

    def stop(self):
        """
        @brief      Stop the sidecar
        """
        self.rc.stop()

    @coroutine
    def on_interface_changed(self):
        """
        @brief    Synchronise with the sidecar'd servers new sensors
        """
        log.debug("Waiting on synchronisation with server")
        yield self.rc.until_synced()
        log.debug("Client synced")
        current_sensors = set(self.rc.sensor.keys())
        log.debug("Current sensor set: {}".format(current_sensors))
        removed = self._previous_sensors.difference(current_sensors)
        log.debug("Sensors removed since last update: {}".format(removed))
        added = current_sensors.difference(self._previous_sensors)
        log.debug("Sensors added since last update: {}".format(added))
        for name in list(added):
            log.debug("Setting sampling strategy and callbacks on sensor '{}'".
                      format(name))
            self.rc.set_sampling_strategy(name, "auto")
            self.rc.set_sensor_listener(name, self.on_sensor_update)
        self._previous_sensors = current_sensors

    @coroutine
    def on_sensor_update(self, sensor, reading):
        """
        @brief      Callback to be executed on a sensor being updated

        @param      sensor   A KATCP Sensor Object
        @param      reading  The sensor reading
        """
        log.debug("Recieved sensor update for sensor '{}': {}".format(
            sensor.name, repr(reading)))
        for callback in list(self._update_callbacks):
            try:
                callback(sensor, reading)
            except Exception as error:
                log.exception(
                    "Failed to call update callback {} with error: {}".format(
                        callback, str(error)))

    def add_sensor_update_callback(self, callback):
        """
        @brief    Add a sensor update callback.

        @param      callback:  The callback

        @note     The callback must have a call signature of
                  func(sensor, reading)
        """
        self._update_callbacks.add(callback)

    def remove_sensor_update_callback(self, callback):
        """
        @brief    Remove a sensor update callback.

        @param      callback:  The callback
        """
        self._update_callbacks.remove(callback)
    def configure(self, config):
        """
        Configure the roach2 product

        Args:
            config:  A dictionary containing configuration information.
                     The dictionary should have a form similar to::

                          {
                              "id": "roach2_spectrometer",
                              "type": "roach2",
                              "icom_id": "R2-EDD",
                              "firmware": "EDDFirmware",
                              "commands":
                              [
                                  ["program", []],
                                  ["start", []],
                                  ["set_integration_period", [1000.0]],
                                  ["set_destination_address", ["10.10.1.12", 60001]]
                              ]
                          }

        This method will request the specified roach2 board from the R2RM server
        and request a firmware deployment. The values of the 'icom_id' and 'firmware'
        must correspond to valid managed roach2 boards and firmwares as understood by
        the R2RM server.
        """
        log.debug("Syncing with R2RM server")
        yield self._r2rm_client.until_synced(2)
        self._icom_id = config["icom_id"]
        self._firmware = config["firmware"]
        log.debug("Trying to force deconfiguring board")
        response = yield self._r2rm_client.req.force_deconfigure_board(
            self._icom_id)
        if not response.reply.reply_ok():
            self.log.warning("Unable to deconfigure ROACH2 board: {}".format(
                response.reply.arguments[1]))
        log.debug("Sending configure request to R2RM server")
        response = yield self._r2rm_client.req.configure_board(self._icom_id,
                                                               EDD_R2RM_USER,
                                                               self._firmware,
                                                               timeout=20)
        if not response.reply.reply_ok():
            self.log.error("Error on configure request: {}".format(
                response.reply.arguments[1]))
            raise EddRoach2ProductError(response.reply.arguments[1])
        _, firmware_ip, firmware_port = response.reply.arguments
        log.debug(
            "Connecting client to activated firmware server @ {}:{}".format(
                firmware_ip, firmware_port))
        firmware_client = KATCPClientResource(
            dict(name="firmware-client",
                 address=(firmware_ip, firmware_port),
                 controlled=True))
        firmware_client.start()
        log.debug("Syncing with firmware client")
        yield firmware_client.until_synced(2)
        for command, args in config["commands"]:
            log.debug(
                "Sending firmware server request '{}' with args '{}'".format(
                    command, args))
            response = yield firmware_client.req[command](*args, timeout=20)
            if not response.reply.reply_ok():
                self.log.error("Error on {}->{} request: {}".format(
                    command, args, response.reply.arguments[1]))
                raise EddRoach2ProductError(response.reply.arguments[1])
        log.debug("Stopping client connection to firmware server")
        firmware_client.stop()
Esempio n. 4
0
class WorkerWrapper(object):
    """Wrapper around a client to an FbfWorkerServer
    instance.
    """
    def __init__(self, hostname, port):
        """
        @brief  Create a new wrapper around a client to a worker server

        @params hostname The hostname for the worker server
        @params port     The port number that the worker server serves on
        """
        log.debug("Creating worker client to worker at {}:{}".format(
            hostname, port))
        self._client = KATCPClientResource(
            dict(name="worker-server-client",
                 address=(hostname, port),
                 controlled=True))
        self.hostname = hostname
        self.port = port
        self.priority = 0  # Currently no priority mechanism is implemented
        self._started = False

    @coroutine
    def get_sensor_value(self, sensor_name):
        """
        @brief  Retrieve a sensor value from the worker
        """
        yield self._client.until_synced()
        response = yield self._client.req.sensor_value(sensor_name)
        if not response.reply.reply_ok():
            raise WorkerRequestError(response.reply.arguments[1])
        raise Return(response.informs[0].arguments[-1])

    def start(self):
        """
        @brief  Start the client to the worker server
        """
        log.debug("Starting client to worker at {}:{}".format(
            self.hostname, self.port))
        self._client.start()
        self._started = True

    @coroutine
    def reset(self):
        yield self._client.until_synced()
        response = yield self._client.req.reset()
        if not response.reply.reply_ok():
            raise WorkerRequestError(response.reply.arguments[1])

    def is_connected(self):
        return self._client.is_connected()

    def __repr__(self):
        return "<{} @ {}:{} (connected = {})>".format(self.__class__.__name__,
                                                      self.hostname, self.port,
                                                      self.is_connected())

    def __hash__(self):
        # This has override is required to allow these wrappers
        # to be used with set() objects. The implication is that
        # the combination of hostname and port is unique for a
        # worker server
        return hash((self.hostname, self.port))

    def __eq__(self, other):
        # Also implemented to help with hashing
        # for sets
        return self.__hash__() == hash(other)

    def __del__(self):
        if self._started:
            try:
                self._client.stop()
            except Exception as error:
                log.exception(str(error))
Esempio n. 5
0
class DigitiserPacketiserClient(object):
    def __init__(self, host, port=7147):
        """
        Wraps katcp commands to control a digitiser/packetiser.

        Args:
            host:  The host IP or name for the desired packetiser KATCP interface
            port:  The port number for the desired packetiser KATCP interface
        """
        self._host = host
        self._port = port
        self._client = KATCPClientResource(
            dict(name="digpack-client",
                 address=(self._host, self._port),
                 controlled=True))
        self._client.start()
        self._capture_started = False

        self._sampling_modes = {
            4096000000: ("virtex7_dk769b", "4.096GHz", 3),
            4000000000: ("virtex7_dk769b", "4.0GHz", 5),
            3600000000: ("virtex7_dk769b", "3.6GHz", 7),
            3520000000: ("virtex7_dk769b", "3.52GHz", 7),
            3500000000: ("virtex7_dk769b", "3.5GHz", 7),
            3200000000: ("virtex7_dk769b", "3.2GHz", 9),
            2600000000: ("virtex7_dk769b", "2.6GHz", 3),
            2560000000: ("virtex7_dk769b", "2.56GHz", 2),
            1750000000: (
                "virtex7_dk769b_test146.mkt", "3.5GHz", 7
            )  # This is  a special mode for the meerkat digitial filter cores inside the edd.
            # An effective 1750 Mhz sampling rate/ 875MHz
            # bandwidth  is achieved by digitial filtering of
            # the 3.5GHz sampled rate!
        }  # This is quite hacky, and the design of this client has to be  has to be improved. Possibly by ahving a client per firmware
        self.__firmware = None

    def stop(self):
        self._client.stop()

    @coroutine
    def _safe_request(self, request_name, *args):
        """
        Send a request to client and prints response ok /  error message.

        Args:
            request_name: Name of the request
            *args:        Arguments passed to the request.
        """
        _log.info("Sending packetiser request '{}' with arguments {}".format(
            request_name, args))
        yield self._client.until_synced()
        response = yield self._client.req[request_name](*args)
        if not response.reply.reply_ok():
            _log.error("'{}' request failed with error: {}".format(
                request_name, response.reply.arguments[1]))
            raise DigitiserPacketiserError(response.reply.arguments[1])
        else:
            _log.debug("'{}' request successful".format(request_name))
            raise Return(response)

    @coroutine
    def _check_interfaces(self, interfaces=['iface00', 'iface01']):
        """
        Check if interface of digitizer is in error state.
        """
        _log.debug("Checking status of 40 GbE interfaces")
        yield self._client.until_synced()

        @coroutine
        def _check_interface(name):
            _log.debug("Checking status of '{}'".format(name))
            sensor = self._client.sensor[
                'rxs_packetizer_40g_{}_am_lock_status'.format(name)]
            status = yield sensor.get_value()
            if not status == 0x0f:
                _log.warning("Interface '{}' in error state".format(name))
                raise PacketiserInterfaceError(
                    "40-GbE interface '{}' did not boot".format(name))
            else:
                _log.debug("Interface '{}' is healthy".format(name))

        for iface in interfaces:
            yield _check_interface(iface)

    @coroutine
    def set_predecimation(self, factor):
        """
        Set a predecimation factor for the packetizer - for e.g. factor=2 only every second sample is used.
        """
        allowedFactors = [1, 2, 4, 8,
                          16]  # Eddy Nussbaum, private communication
        if factor not in allowedFactors:
            raise RuntimeError(
                "predicimation factor {} not in allowed factors {}".format(
                    factor, allowedFactors))
        yield self._safe_request("rxs_packetizer_edd_predecimation", factor)

    @coroutine
    def set_noise_diode_frequency(self, frequency):
        """
        Set noise diode frequency to given value.
        """
        if frequency == 0:
            yield self.set_noise_diode_firing_pattern(0.0, 0.0, "now")
        else:
            yield self.set_noise_diode_firing_pattern(0.5, 1. / frequency,
                                                      "now")

    @coroutine
    def set_noise_diode_firing_pattern(self, percentage, period, start="now"):
        """
        Set noise diode frequency to given value.

        Args:
            percentage: Percentage of period which the noise diode is turned on.
            period:     Period of fireing [s].
        """
        _log.debug("Set noise diode firing pattern")
        yield self._safe_request("noise_source", start, percentage, period)

    @coroutine
    def set_sampling_rate(self, rate, retries=3):
        """
        Sets the sampling rate.

        Args:
            rate:    The sampling rate in samples per second (e.g. 2.6 GHz should be passed as 2600000000.0)


        To allow time for reinitialisation of the packetiser firmware during this call we enforce a 10
        second sleep before the function returns.
        """

        try:
            args = self._sampling_modes[int(rate)]
        except KeyError as error:
            pos_freqs = "\n".join(
                ["  - {} Hz ".format(f) for f in self._sampling_modes.keys()])
            error_msg = "Frequency {} Hz not in possible frequencies:\n{}".format(
                rate, pos_freqs)
            _log.error(error_msg)
            raise DigitiserPacketiserError(error_msg)

        attempts = 0
        while True:
            _log.debug("Reinit packetizer with firmware: {}".format(args[0]))

            response = yield self._safe_request("rxs_packetizer_system_reinit",
                                                *args)
            self.__firmware = args[0]
            yield sleep(20)
            try:
                _log.warning(
                    "Hard coded firmware names in interface checks. This is a shortterm hack!"
                )
                if args[0] == "virtex7_dk769b":
                    yield self._check_interfaces()
                elif args[0] == "virtex7_dk769b_test146.mkt":
                    yield self._check_interfaces(["iface00"])
                else:
                    RuntimeError("Unknown core")

            except PacketiserInterfaceError as error:
                if attempts >= retries:
                    raise error
                else:
                    _log.warning("Retrying system initalisation")
                    attempts += 1
                    continue
            else:
                break

    @coroutine
    def set_digitial_filter(self, filter_number):
        """
        Sets the digital filter number.

        """
        yield self._safe_request("rxs_packetizer_40g_filter_selection_set",
                                 filter_number)

    @coroutine
    def set_bit_width(self, nbits):
        """
        Sets the number of bits per sample out of the packetiser

        Args:
            nbits:  The desired number of bits per sample (e.g. 8 or 12)
        """
        valid_modes = {8: "edd08", 10: "edd10", 12: "edd12"}
        _log.warning("Firmware switch for bit set mode!")
        if self.__firmware == "virtex7_dk769b_test146.mkt":
            _log.debug("Firmware does not support setting bit rate!")
            return
        try:
            mode = valid_modes[int(nbits)]
        except KeyError as error:
            msg = "Invalid bit depth, valid bit depths are: {}".format(
                valid_modes.keys())
            _log.error(msg)
            raise DigitiserPacketiserError(msg)
        yield self._safe_request("rxs_packetizer_edd_switchmode", mode)

    @coroutine
    def flip_spectrum(self, flip):
        """
        Flip spectrum flip = True/False to adjust for even/odd nyquist zone
        """
        if flip:
            yield self._safe_request("rxs_packetizer_edd_flipsignalspectrum",
                                     "on")
        else:
            yield self._safe_request("rxs_packetizer_edd_flipsignalspectrum",
                                     "off")

    @coroutine
    def set_destinations(self, v_dest, h_dest):
        """
        Sets the multicast destinations for data out of the packetiser

        Args:
            v_dest:  The vertical polarisation channel destinations
            h_dest:  The horizontal polarisation channel destinations

        The destinations should be provided as composite stream definition
        strings, e.g. 225.0.0.152+3:7148 (this defines four multicast groups:
        225.0.0.152, 225.0.0.153, 225.0.0.154 and 225.0.0.155, all using
        port 7148). Currently the packetiser only accepts contiguous IP
        ranges for each set of destinations.
        """
        yield self._safe_request("capture_destination", "v", v_dest)
        yield self._safe_request("capture_destination", "h", h_dest)

    @coroutine
    def set_mac_address(self, intf, mac):
        """
        Sets the mac adresses of the source NICs of the packetiser

        Args:
            intf: The number of the NIC
            mac:  The mac of the NIC
        """
        yield self._safe_request("rxs_packetizer_40g_source_mac_set", intf,
                                 mac)

    @coroutine
    def set_predecimation_factor(self, factor):
        """
        Sets the predecimation_factorfor data out of the packetiser

        Args:
            factor: (e.g. 1,2,4,8)

        """
        yield self._safe_request("rxs_packetizer_edd_predecimation", factor)

    @coroutine
    def enable_snapshot(self, time=5):
        yield self._safe_request("rxs_packetizer_snapshot_enable_spec", time)
        yield self._safe_request("rxs_packetizer_snapshot_enable_spec")

    @coroutine
    def set_flipsignalspectrum(self, value):
        """
        Sets the rxs-packetizer-edd-flipsignalspectrum data out of the packetiser

        Args:
            value: (e.g. 0, 1)

        """
        yield self._safe_request("rxs_packetizer_edd_flipsignalspectrum",
                                 value)

    @coroutine
    def set_interface_address(self, intf, ip):
        """
        Set the interface address for a packetiser qsfp interface

        Args:

            intf:   The interface specified as a string integer, e.g. '0' or '1'
            ip:     The IP address to assign to the interface
        """
        yield self._safe_request("rxs_packetizer_40g_source_ip_set", intf, ip)

    @coroutine
    def capture_start(self):
        """
        Start data transmission for both polarisation channels

        This method uses the packetisers 'capture-start' method which is an
        aggregate command that ensures all necessary flags on the packetiser
        and set for data transmission.  This includes the 1PPS flag required by
        the ROACH2 boards.
        """
        if not self._capture_started:
            """
            Only start capture once and not twice if received configure
            """
            self._capture_started = True
            yield self._safe_request("capture_start", "vh")

    @coroutine
    def configure(self, config):
        """
        Applying configuration recieved in dictionary
        """
        self._capture_started = False
        yield self._safe_request("capture_stop", "vh")
        yield self.set_sampling_rate(config["sampling_rate"])
        yield self.set_predecimation(config["predecimation_factor"])
        yield self.flip_spectrum(config["flip_spectrum"])
        yield self.set_bit_width(config["bit_width"])
        yield self.set_destinations(config["v_destinations"],
                                    config["h_destinations"])
        if "noise_diode_frequency" in config:
            yield self.set_noise_diode_frequency(
                config["noise_diode_frequency"])

        for interface, ip_address in config["interface_addresses"].items():
            yield self.set_interface_address(interface, ip_address)
        if "sync_time" in config:
            yield self.synchronize(config["sync_time"])
        else:
            yield self.synchronize()
        yield self.capture_start()

    @coroutine
    def deconfigure(self):
        """
        Deconfigure. Not doing anythin
        """
        raise Return()

    @coroutine
    def measurement_start(self):
        """
        """
        raise Return()

    @coroutine
    def measurement_stop(self):
        """
        """
        raise Return()

    @coroutine
    def measurement_prepare(self, config={}):
        """
        """
        if "noise_diode_frequency" in config:
            yield self.set_noise_diode_frequency(
                config["noise_diode_frequency"])
        elif "noise_diode_pattern" in config:
            c = config["noise_diode_pattern"]
            yield self.set_noise_diode_firing_pattern(c["percentage"],
                                                      c["period"])

        raise Return()

    @coroutine
    def capture_stop(self):
        """
        Stop data transmission for both polarisation channels
        """
        _log.warning("Not stopping data transmission")
        raise Return()
        #yield self._safe_request("capture_stop", "vh")

    @coroutine
    def get_sync_time(self):
        """
        Get the current packetiser synchronisation epoch

        Return:
            The synchronisation epoch as a unix time float
        """
        response = yield self._safe_request("rxs_packetizer_40g_get_zero_time")
        sync_epoch = float(response.informs[0].arguments[0])
        raise Return(sync_epoch)

    @coroutine
    def get_snapshot(self):
        """
        Returns dictionary with snapshot data from the packetizer.
        """
        response = yield self._safe_request("rxs_packetizer_snapshot_get_spec")
        res = {}
        for message in response.informs:
            key = message.arguments[0]
            if 'header' in key:
                res[key] = dict(band_width=float(message.arguments[1]) * 1e3,
                                integration_time=float(message.arguments[2]),
                                num_channels=int(message.arguments[3]),
                                band_width_adc=float(message.arguments[4]),
                                spec_counter=int(message.arguments[5]),
                                timestamp=message.arguments[6])
            elif 'adc' in key:
                res[key] = np.fromstring(message.arguments[1],
                                         dtype=np.float32)
            elif 'level' in key:
                res[key] = np.fromstring(message.arguments[1], dtype=np.int32)

        raise Return(res)

    @coroutine
    def synchronize(self, unix_time=None):
        """
        Set the synchronisation epoch for the packetiser

        Args:
            unix_time:  The unix time to synchronise at. If no value is provided a
                               resonable value will be selected.

        When explicitly setting the synchronisation time it should be a second
        or two into the future allow enough time for communication with the
        packetiser. If the time is in the past by the time the request reaches
        the packetiser the next 1PPS tick will be selected.  Users *must* call
        get_sync_time to get the actual time that was set.  This call will
        block until the sync epoch has passed (i.e. if a sync epoch is chosen
        that is 10 second in the future, the call will block for 10 seconds).

        """
        if not unix_time:
            unix_time = round(time.time() + 2)
        yield self._safe_request("synchronise", 0, unix_time)
        sync_epoch = yield self.get_sync_time()
        if sync_epoch != unix_time:
            _log.warning(
                "Requested sync time {} not equal to actual sync time {}".
                format(unix_time, sync_epoch))

    @coroutine
    def populate_data_store(self, host, port):
        """
        Populate the data store

        Args:
            host:     ip of the data store to use
            port:     port of the data store
        """
        _log.debug("Populate data store @ {}:{}".format(host, port))
        dataStore = EDDDataStore(host, port)
        _log.debug("Adding output formats to known data formats")

        descr = {
            "description": "Digitizer/Packetizer spead. One heap per packet.",
            "ip": None,
            "port": None,
            "bit_depth": None,  # Dynamic Parameter
            "sample_rate": None,
            "sync_time": None,
            "samples_per_heap": 4096
        }

        dataStore.addDataFormatDefinition("MPIFR_EDD_Packetizer:1", descr)
        raise Return()
Esempio n. 6
0
class DigitiserPacketiserClient(object):
    def __init__(self, host, port=7147):
        """
        @brief      Class for digitiser packetiser client.

        @param      host   The host IP or name for the desired packetiser KATCP interface
        @param      port   The port number for the desired packetiser KATCP interface
        """
        self._host = host
        self._port = port
        self._client = KATCPClientResource(
            dict(name="digpack-client",
                 address=(self._host, self._port),
                 controlled=True))
        self._client.start()

    def stop(self):
        self._client.stop()

    @coroutine
    def _safe_request(self, request_name, *args):
        log.info("Sending packetiser request '{}' with arguments {}".format(
            request_name, args))
        yield self._client.until_synced()
        response = yield self._client.req[request_name](*args)
        if not response.reply.reply_ok():
            log.error("'{}' request failed with error: {}".format(
                request_name, response.reply.arguments[1]))
            raise DigitiserPacketiserError(response.reply.arguments[1])
        else:
            log.debug("'{}' request successful".format(request_name))
            raise Return(response)

    @coroutine
    def _check_interfaces(self):
        log.debug("Checking status of 40 GbE interfaces")
        yield self._client.until_synced()

        @coroutine
        def _check_interface(name):
            log.debug("Checking status of '{}'".format(name))
            sensor = self._client.sensor[
                'rxs_packetizer_40g_{}_am_lock_status'.format(name)]
            status = yield sensor.get_value()
            if not status == 0x0f:
                log.warning("Interface '{}' in error state".format(name))
                raise PacketiserInterfaceError(
                    "40-GbE interface '{}' did not boot".format(name))
            else:
                log.debug("Interface '{}' is healthy".format(name))

        yield _check_interface('iface00')
        yield _check_interface('iface01')

    @coroutine
    def set_sampling_rate(self, rate, retries=3):
        """
        @brief      Sets the sampling rate.

        @param      rate    The sampling rate in samples per second (e.g. 2.6 GHz should be passed as 2600000000.0)

        @detail     To allow time for reinitialisation of the packetiser firmware during this call we enforce a 10
                    second sleep before the function returns.
        """
        valid_modes = {
            4000000000: ("virtex7_dk769b", "4.0GHz", 5),
            2600000000: ("virtex7_dk769b", "2.6GHz", 3)
        }
        try:
            args = valid_modes[rate]
        except KeyError as error:
            msg = "Invalid sampling rate, valid sampling rates are: {}".format(
                valid_modes.keys())
            log.error(msg)
            raise DigitiserPacketiserError(msg)

        attempts = 0
        while True:
            response = yield self._safe_request("rxs_packetizer_system_reinit",
                                                *args)
            yield sleep(10)
            try:
                yield self._check_interfaces()
            except PacketiserInterfaceError as error:
                if attempts >= retries:
                    raise error
                else:
                    log.warning("Retrying system initalisation")
                    attempts += 1
                    continue
            else:
                break

    @coroutine
    def set_bit_width(self, nbits):
        """
        @brief      Sets the number of bits per sample out of the packetiser

        @param      nbits  The desired number of bits per sample (e.g. 8 or 12)
        """
        valid_modes = {8: "edd08", 12: "edd12"}
        try:
            mode = valid_modes[nbits]
        except KeyError as error:
            msg = "Invalid bit depth, valid bit depths are: {}".format(
                valid_modes.keys())
            log.error(msg)
            raise DigitiserPacketiserError(msg)
        yield self._safe_request("rxs_packetizer_edd_switchmode", mode)

    @coroutine
    def set_destinations(self, v_dest, h_dest):
        """
        @brief      Sets the multicast destinations for data out of the packetiser

        @param      v_dest  The vertical polarisation channel destinations
        @param      h_dest  The horizontal polarisation channel destinations

        @detail     The destinations should be provided as composite stream definition
                    strings, e.g. 225.0.0.152+3:7148 (this defines four multicast groups:
                    225.0.0.152, 225.0.0.153, 225.0.0.154 and 225.0.0.155, all using
                    port 7148). Currently the packetiser only accepts contiguous IP
                    ranges for each set of destinations.
        """
        yield self._safe_request("capture_destination", "v", v_dest)
        yield self._safe_request("capture_destination", "h", h_dest)

    @coroutine
    def set_interface_address(self, intf, ip):
        """
        @brief      Set the interface address for a packetiser qsfp interface

        @param      intf   The interface specified as a string integer, e.g. '0' or '1'
        @param      ip     The IP address to assign to the interface
        """
        yield self._safe_request("rxs_packetizer_40g_source_ip_set", intf, ip)

    @coroutine
    def capture_start(self):
        """
        @brief      Start data transmission for both polarisation channels

        @detail     This method uses the packetisers 'capture-start' method
                    which is an aggregate command that ensures all necessary
                    flags on the packetiser and set for data transmission.
                    This includes the 1PPS flag required by the ROACH2 boards.
        """
        yield self._safe_request("capture_start", "vh")

    @coroutine
    def capture_stop(self):
        """
        @brief      Stop data transmission for both polarisation channels
        """
        yield self._safe_request("capture_stop", "vh")

    @coroutine
    def get_sync_time(self):
        """
        @brief      Get the current packetiser synchronisation epoch

        @return     The synchronisation epoch as a unix time float
        """
        response = yield self._safe_request("rxs_packetizer_40g_get_zero_time")
        sync_epoch = float(response.informs[0].arguments[0])
        raise Return(sync_epoch)

    @coroutine
    def synchronize(self, unix_time=None):
        """
        @brief      Set the synchronisation epoch for the packetiser

        @param      unix_time  The unix time to synchronise at. If no value is provided a
                               resonable value will be selected.

        @detail     When explicitly setting the synchronisation time it should be a
                    second or two into the future allow enough time for communication
                    with the packetiser. If the time is in the past by the time the request
                    reaches the packetiser the next 1PPS tick will be selected.
                    Users *must* call get_sync_time to get the actual time that was set.
                    This call will block until the sync epoch has passed (i.e. if a sync epoch
                    is chosen that is 10 second in the future, the call will block for 10 seconds).

        @note       The packetiser rounds to the nearest 1 PPS tick so it is recommended to
                    set the
        """
        if not unix_time:
            unix_time = round(time.time() + 2)
        yield self._safe_request("synchronise", 0, unix_time)
        sync_epoch = yield self.get_sync_time()
        if sync_epoch != unix_time:
            log.warning(
                "Requested sync time {} not equal to actual sync time {}".
                format(unix_time, sync_epoch))
Esempio n. 7
0
class KATCPToIGUIConverter(object):
    def __init__(self, host, port, igui_host, igui_user, igui_pass,
                 igui_device_id):
        """
        @brief      Class for katcp to igui converter.

        @param   host             KATCP host address
        @param   port             KATCP port number
        @param   igui_host        iGUI server hostname
        @param   igui_user        iGUI username
        @param   igui_pass        iGUI password
        @param   igui_device_id   iGUI device ID
        """
        self.rc = KATCPClientResource(
            dict(name="test-client", address=(host, port), controlled=True))
        self.host = host
        self.port = port
        self.igui_host = igui_host
        self.igui_user = igui_user
        self.igui_pass = igui_pass
        self.igui_group_id = None
        self.igui_device_id = igui_device_id
        self.igui_connection = IGUIConnection(self.igui_host, self.igui_user,
                                              self.igui_pass)
        self.igui_task_id = None
        self.igui_rxmap = None
        self.ioloop = None
        self.ic = None
        self.api_version = None
        self.implementation_version = None
        self.previous_sensors = set()

    def start(self):
        """
        @brief      Start the instance running

        @detail     This call will trigger connection of the KATCPResource client and
                    will login to the iGUI server. Once both connections are established
                    the instance will retrieve a mapping of the iGUI receivers, devices
                    and tasks and will try to identify the parent of the device_id
                    provided in the constructor.

        @param      self  The object

        @return     { description_of_the_return_value }
        """
        @tornado.gen.coroutine
        def _start():
            log.debug("Waiting on synchronisation with server")
            yield self.rc.until_synced()
            log.debug("Client synced")
            log.debug("Requesting version info")
            # This information can be used to get an iGUI device ID
            response = yield self.rc.req.version_list()
            log.info("response {}".format(response))
            # for internal device KATCP server, response.informs[2].arguments return index out of range
            #_, api, implementation = response.informs[2].arguments
            #self.api_version = api
            #self.implementation_version = implementation
            #log.info("katcp-device API: {}".format(self.api_version))
            #log.info("katcp-device implementation: {}".format(self.implementation_version))
            self.ioloop.add_callback(self.update)

        log.debug("Starting {} instance".format(self.__class__.__name__))
        # self.igui_connection.login()
        #self.igui_connection.login(self.igui_user, self.igui_pass)
        self.igui_rxmap = self.igui_connection.build_igui_representation()
        #log.debug(self.igui_rxmap)
        # Here we do a look up to find the parent of this device
        for rx in self.igui_rxmap:
            log.debug(rx.id)
            if self.igui_device_id in rx.devices._by_id.keys():
                log.debug(self.igui_device_id)
                log.debug(rx.id)
                self.igui_rx_id = rx.id
                log.debug("Found Rx parent: {}".format(self.igui_rx_id))
                break
        else:
            log.debug("Device '{}' is not a child of any receiver".format(
                self.igui_device_id))
            raise IGUIMappingException(
                "Device '{}' is not a child of any receiver".format(
                    self.igui_device_id))

        #log.debug("iGUI representation:\n{}".format(self.igui_rxmap))
        self.rc.start()
        self.ic = self.rc._inspecting_client
        self.ioloop = self.rc.ioloop
        self.ic.katcp_client.hook_inform(
            "interface-changed",
            lambda message: self.ioloop.add_callback(self.update))
        self.ioloop.add_callback(_start)

    @tornado.gen.coroutine
    def update(self):
        """
        @brief    Synchronise with the KATCP servers sensors and register new listners
        """
        log.debug("Waiting on synchronisation with server")
        yield self.rc.until_synced()
        log.debug("Client synced")
        current_sensors = set(self.rc.sensor.keys())
        log.debug("Current sensor set: {}".format(current_sensors))
        removed = self.previous_sensors.difference(current_sensors)
        log.debug("Sensors removed since last update: {}".format(removed))
        added = current_sensors.difference(self.previous_sensors)
        log.debug("Sensors added since last update: {}".format(added))
        for name in list(added):
            log.debug("Setting sampling strategy and callbacks on sensor '{}'".
                      format(name))
            # strat3 = ('event-rate', 2.0, 3.0)              #event-rate doesn't work
            # self.rc.set_sampling_strategy(name, strat3)    #KATCPSensorError:
            # Error setting strategy
            # not sure that auto means here
            self.rc.set_sampling_strategy(name, "auto")
            #self.rc.set_sampling_strategy(name, ["period", (10)])
            #self.rc.set_sampling_strategy(name, "event")
            self.rc.set_sensor_listener(name, self._sensor_updated)
        self.previous_sensors = current_sensors

    def _sensor_updated(self, sensor, reading):
        """
        @brief      Callback to be executed on a sensor being updated

        @param      sensor   The sensor
        @param      reading  The sensor reading
        """
        log.debug("Recieved sensor update for sensor '{}': {}".format(
            sensor.name, repr(reading)))
        try:
            rx = self.igui_rxmap.by_id(self.igui_rx_id)
        except KeyError:
            raise Exception("No iGUI receiver with ID {}".format(
                self.igui_rx_id))
        try:
            device = rx.devices.by_id(self.igui_device_id)
        except KeyError:
            raise Exception("No iGUI device with ID {}".format(
                self.igui_device_id))
        try:
            #self.igui_rxmap = self.igui_connection.build_igui_representation()
            #device = self.igui_rxmap.by_id(self.igui_rx_id).devices.by_id(self.igui_device_id)
            task = device.tasks.by_name(sensor.name)
        except KeyError:
            if (sensor.name[-3:] == 'PNG'):
                task = json.loads(
                    self.igui_connection.create_task(
                        device, (sensor.name, "NONE", "", "IMAGE", "GET_SET",
                                 "0", "0", "0", "-10000000000000000",
                                 "10000000000000000", "300")))
            else:
                task = json.loads(
                    self.igui_connection.create_task(
                        device, (sensor.name, "NONE", "", "GETSET", "GET", "0",
                                 "0", "0", "-10000000000000000",
                                 "10000000000000000", "300")))
            self.igui_task_id = str(task[0]['rx_task_id'])
            self.igui_connection.update_group_task_privileges(
                [self.igui_connection.igui_group_id, self.igui_task_id], "Y")
            self.igui_connection.update_group_task_privileges([
                self.igui_connection.igui_group_id, self.igui_task_id, "update"
            ], "Y")
            self.igui_rxmap = self.igui_connection.build_igui_representation()
            device = self.igui_rxmap.by_id(self.igui_rx_id).devices.by_id(
                self.igui_device_id)
            task = device.tasks.by_id(self.igui_task_id)

        if (sensor.name[-3:] == 'PNG'
            ):  # or some image type that we finally agreed on
            log.debug(sensor.name)
            log.debug(sensor.value)
            log.debug(len(sensor.value))
            self.igui_connection.set_task_blob(task, reading.value)
        else:
            self.igui_connection.set_task_value(task, sensor.value)

    def stop(self):
        """
        @brief      Stop the client
        """
        self.rc.stop()