class MyServer(AsyncDeviceServer):

    VERSION_INFO = ("example-api", 1, 0)
    BUILD_INFO = ("example-implementation", 0, 1, "")

    # Optionally set the KATCP protocol version and features. Defaults to
    # the latest implemented version of KATCP, with all supported optional
    # featuresthat's all of the receivers
    PROTOCOL_INFO = ProtocolFlags(5, 0, set([
        ProtocolFlags.MULTI_CLIENT,
        ProtocolFlags.MESSAGE_IDS,
    ]))

    FRUIT = [
        "apple", "banana", "pear", "kiwi",
    ]

    def setup_sensors(self):
        """Setup some server sensors."""
	
        self._add_result = Sensor.float("add.result",
                                        "Last ?add result.", "", [-10000, 10000])

        self._time_result = Sensor.timestamp("time.result",
                                             "Last ?time result.", "")

        self._eval_result = Sensor.string("eval.result",
                                          "Last ?eval result.", "")

        self._fruit_result = Sensor.discrete("fruit.result",
                                             "Last ?pick-fruit result.", "", self.FRUIT)
        self._device_armed = Sensor.boolean(
            "device-armed",
            description="Is the CAM server armed?",
            initial_status=Sensor.NOMINAL,
            default=True)
        self._bandwidth = Sensor.float("bandwidth", default=300)
        self._sourcename = Sensor.string("sourcename", default="none")
        self._source_ra = Sensor.string("source_RA", default=0)
        self._source_dec = Sensor.string("source_DEC", default=0)
        self._exposure_time = Sensor.float("EXP_time", default=0)

        self.add_sensor(self._sourcename)
        self.add_sensor(self._source_ra)
        self.add_sensor(self._source_dec)
        self.add_sensor(self._exposure_time)

        self.add_sensor(self._bandwidth)
        self.add_sensor(self._device_armed)
        self.add_sensor(self._add_result)
        self.add_sensor(self._time_result)
        self.add_sensor(self._eval_result)
        self.add_sensor(self._fruit_result)

        self._systemp_result = Sensor.float("add.result",
                                            "Last ?add result.", "", [-10000, 10000])
        self.add_sensor(self._systemp_result)
	
	##self._bandwidth = Sensor.float("bandwidth", default=300)
	#self.add_sensor(self._bandwidth)

    @request()
    @return_reply(Str())
    def request_bandwidth(self, req, bw):
        """Return the Bandwidth"""
        #req.inform("checking armed status", self._device_armed.value())
        req.reply("ok", bw)
        raise AsyncReply
    @request()
    @return_reply(Str())
    def request_status_armed(self, req):
        """Return the state of the Armed/Disarmed"""
        req.inform("checking armed status", self._device_armed.value())
        req.reply("ok", self._device_armed.value())
        raise AsyncReply

    @request(Float())
    @return_reply()
    def request_long_action(self, req, t):
        """submit a long action command for testing using coroutine"""
        @tornado.gen.coroutine
        def wait():
            yield tornado.gen.sleep(t)
            req.reply("slept for", t, "second")
        self.ioloop.add_callback(wait)
        raise AsyncReply

    @request(Float(), Float())
    @return_reply(Str())
    def request_radec(self, req, ra, dec):
        """testing to read in the RA DEC fomr a client"""
        # test=ra+dec
        self.ra = ra
        self.dec = dec
        return ("ok", "%f %f" % (self.ra, self.dec))

    @request(Float(), Float())
    @return_reply(Float())
    def request_add(self, req, x, y):
        """Add two numbers"""
        r = x + y
        self._add_result.set_value(r)
        return ("ok", r)

    @request()
    @return_reply(Str())
    def request_arm(self, req):
        """Arm the controller"""
        @tornado.gen.coroutine
        def start_controller():
            req.inform("processing", "command processing")
            try:
                yield tornado.gen.sleep(10)
            except Exception as error:
                req.reply("fail", "Unknown error: {0}".format(str(error)))
            else:
                req.reply("ok", "effcam armed")
                self._device_armed.set_value(True)
        if self._device_armed.value():
            return ("fail", "Effcam is already armed")
        self.ioloop.add_callback(start_controller)
        raise AsyncReply

    @request()
    @return_reply(Str())
    def request_disarm(self, req):
        """disarm the controller"""
        @tornado.gen.coroutine
        # @coroutine
        def stop_controller():
            req.inform("processing", "processing command")
            try:
                yield tornado.gen.sleep(10)
                # yield self._controller.stop()
            except Exception as error:
                req.reply("fail", "Unknown error: {0}".format(str(error)))
            else:
                req.reply("ok", "effcam disarmed")
                self._device_armed.set_value(False)
        if self._device_armed.value() == False:
            return ("fail", "Effcam is already disarmed")
        self.ioloop.add_callback(stop_controller)
        raise AsyncReply

    @request()
    @return_reply(Str())
    def request_status_temp(self, req):
        """Return the current temp"""
        #r = time.time()
        t = "36"
        # self._time_result.set_value(r)
        return ("ok", t)

    @request()
    @return_reply(Timestamp())
    def request_status_time(self, req):
        """Return the current time in seconds since the Unix Epoch."""
        req.inform("processing", "processing command")
        r = time.time()
        # self._time_result.set_value(r)
        req.reply("ok", r)
        raise AsyncReply
        # return ("ok", r)

    @request()
    @return_reply(Timestamp(), Str())
    def request_status_time_and_temp(self, req):
        """Return the current time in seconds since the Unix Epoch."""
        req.inform("processing", "processing command")
        r = time.time()
        # self._time_result.set_value(r)
        t = "36"
        req.reply("ok", r, t)
        raise AsyncReply

    @request(Str())
    @return_reply()
    def request_configure(self, req, config):
        """Return ok."""
	print "{} received configuration {}".format(Time.now(),config)
        self.config = config
	time.sleep(1)
	req.reply("ok",)
        raise AsyncReply

    @request(Str())
    @return_reply()
    def request_provision(self, req, config):
        """Return ok."""
        print "{} received provision {}".format(Time.now(),config)
        self.config = config
        time.sleep(1)
        req.reply("ok",)
        raise AsyncReply

    @request(Str())
    @return_reply()
    def request_measurement_prepare(self, req, config):
        """Return ok."""
        print "{} received measurement prepare {}".format(Time.now(),config)
        self.config = config
        time.sleep(1)
        req.reply("ok",)
        raise AsyncReply

    @request(Str())
    @return_reply()
    def request_configure(self, req, config):
        """Return ok."""
        print "{} received configuration {}".format(Time.now(),config)
        self.config = config
        time.sleep(1)
        req.reply("ok",)
        raise AsyncReply

    @request()
    @return_reply(Str())
    def request_status_config(self, req):
        """Return ok."""
        req.reply("ok", "{}".format(self.config))
        raise AsyncReply

    @request()
    @return_reply()
    def request_capture_start(self, req):
        """Return ok."""
        print "{} received capture start request on port :{}".format(Time.now(), server_port)
        req.reply("ok")
        raise AsyncReply

    @request()
    @return_reply()
    def request_capture_stop(self, req):
        """Return ok."""
        print "{} received capture stop request on port :{}".format(Time.now(), server_port)
        req.reply("ok")
        raise AsyncReply

    @request()
    @return_reply()
    def request_measurement_start(self, req):
        """Return ok."""
        print "{} received measurement start request on port :{}".format(Time.now(), server_port)
        req.reply("ok")
        raise AsyncReply

    @request()
    @return_reply()
    def request_measurement_stop(self, req):
        """Return ok."""
        print "{} received measurement stop request on port :{}".format(Time.now(), server_port)
        req.reply("ok")
        raise AsyncReply

    @request()
    @return_reply()
    def request_deconfigure(self, req):
        """Return ok."""
        print "{} received deconfigure request on port :{}".format(Time.now(), server_port)
        req.reply("ok")
        raise AsyncReply

    @request()
    @return_reply()
    def request_deprovision(self, req):
        """Return ok."""
        print "{} received deprovision request on port :{}".format(Time.now(), server_port)
        req.reply("ok")
        raise AsyncReply()

    @return_reply()
    def request_start(self, req):
        """Return ok."""
	print "{} received start request on port :{}".format(Time.now(), server_port)
        req.reply("ok")
        raise AsyncReply

    @request()
    @return_reply()
    def request_stop(self, req):
        """Return ok."""
	print "{} received stop request on port :{}".format(Time.now(), server_port)
        req.reply("ok")
        raise AsyncReply
class MyServer(DeviceServer):

    VERSION_INFO = ("example-api", 1, 0)
    BUILD_INFO = ("example-implementation", 0, 1, "")

    # Optionally set the KATCP protocol version and features. Defaults to
    # the latest implemented version of KATCP, with all supported optional
    # features
    PROTOCOL_INFO = ProtocolFlags(5, 0, set([
        ProtocolFlags.MULTI_CLIENT,
        ProtocolFlags.MESSAGE_IDS,
    ]))

    FRUIT = [
        "apple", "banana", "pear", "kiwi",
    ]

    def setup_sensors(self):
        """Setup some server sensors."""
        self._add_result = Sensor.float("add.result",
            "Last ?add result.", "", [-10000, 10000])
        self._add_result.set_value(0, Sensor.UNREACHABLE)

        self._time_result = Sensor.timestamp("time.result",
            "Last ?time result.", "")
        self._time_result.set_value(0, Sensor.INACTIVE)

        self._eval_result = Sensor.string("eval.result",
            "Last ?eval result.", "")
        self._eval_result.set_value('', Sensor.UNKNOWN)

        self._fruit_result = Sensor.discrete("fruit.result",
            "Last ?pick-fruit result.", "", self.FRUIT)
        self._fruit_result.set_value('apple', Sensor.ERROR)

        self.add_sensor(self._add_result)
        self.add_sensor(self._time_result)
        self.add_sensor(self._eval_result)
        self.add_sensor(self._fruit_result)

    @request(Float(), Float())
    @return_reply(Float())
    def request_add(self, req, x, y):
        """Add two numbers"""
        r = x + y
        self._add_result.set_value(r)
        return ("ok", r)

    @request()
    @return_reply(Timestamp())
    def request_time(self, req):
        """Return the current time in ms since the Unix Epoch."""
        r = time.time()
        self._time_result.set_value(r)
        return ("ok", r)

    @request(Str())
    @return_reply(Str())
    def request_eval(self, req, expression):
        """Evaluate a Python expression."""
        r = str(eval(expression))
        self._eval_result.set_value(r)
        return ("ok", r)

    @request()
    @return_reply(Discrete(FRUIT))
    def request_pick_fruit(self, req):
        """Pick a random fruit."""
        r = random.choice(self.FRUIT + [None])
        if r is None:
            return ("fail", "No fruit.")
        delay = random.randrange(1,5)
        req.inform("Picking will take %d seconds" % delay)

        def pick_handler():
            self._fruit_result.set_value(r)
            req.reply("ok", r)

        handle_timer = threading.Timer(delay, pick_handler)
        handle_timer.start()

        raise AsyncReply

    @request(Str())
    @return_reply()
    def request_set_sensor_inactive(self, req, sensor_name):
        """Set sensor status to inactive"""
        sensor = self.get_sensor(sensor_name)
        ts, status, value = sensor.read()
        sensor.set_value(value, sensor.INACTIVE, ts)
        return('ok',)

    @request(Str())
    @return_reply()
    def request_set_sensor_unreachable(self, req, sensor_name):
        """Set sensor status to unreachable"""
        sensor = self.get_sensor(sensor_name)
        ts, status, value = sensor.read()
        sensor.set_value(value, sensor.UNREACHABLE, ts)
        return('ok',)

    def request_raw_reverse(self, req, msg):
        """
        A raw request handler to demonstrate the calling convention if
        @request decoraters are not used. Reverses the message arguments.
        """
        # msg is a katcp.Message.request object
        reversed_args = msg.arguments[::-1]
        # req.make_reply() makes a katcp.Message.reply using the correct request
        # name and message ID
        return req.make_reply(*reversed_args)
Exemple #3
0
class KATCPServer (DeviceServer):

  VERSION_INFO = ("ptuse-api", 1, 0)
  BUILD_INFO = ("ptuse-implementation", 0, 1, "")

  # Optionally set the KATCP protocol version and features. Defaults to
  # the latest implemented version of KATCP, with all supported optional
  # features
  PROTOCOL_INFO = ProtocolFlags(5, 0, set([
    ProtocolFlags.MULTI_CLIENT,
    ProtocolFlags.MESSAGE_IDS,
  ]))

  def __init__ (self, server_host, server_port, script):
    self.script = script
    self._host_sensors = {}
    self._beam_sensors = {}
    self._data_product = {}
    self._data_product["id"] = "None"

    self.data_product_res = []
    self.data_product_res.append(re.compile ("^[a-zA-Z]+_1"))
    self.data_product_res.append(re.compile ("^[a-zA-Z]+_2"))
    self.data_product_res.append(re.compile ("^[a-zA-Z]+_3"))
    self.data_product_res.append(re.compile ("^[a-zA-Z]+_4"))

    self.script.log(2, "KATCPServer::__init__ starting DeviceServer on " + server_host + ":" + str(server_port))
    DeviceServer.__init__(self, server_host, server_port)

  DEVICE_STATUSES = ["ok", "degraded", "fail"]

  def setup_sensors(self):
    """Setup server sensors."""
    self.script.log(2, "KATCPServer::setup_sensors()")

    self._device_status = Sensor.discrete("device-status",
      description="Status of entire system",
      params=self.DEVICE_STATUSES,
      default="ok")
    self.add_sensor(self._device_status)

    self._beam_name = Sensor.string("beam-name",
      description="name of configured beam",
      unit="",
      default="")
    self.add_sensor(self._beam_name)

    # setup host based sensors   
    self._host_name = Sensor.string("host-name",
      description="hostname of this server",
      unit="",
      default="")
    self.add_sensor(self._host_name)

    self.script.log(2, "KATCPServer::setup_sensors lmc="+str(self.script.lmc))
    (host, port) = self.script.lmc.split(":")
    self.setup_sensors_host (host, port)

    self.script.log(2, "KATCPServer::setup_sensors beams="+str(self.script.beam))
    self.setup_sensors_beam (self.script.beam_name)

  # add sensors based on the reply from the specified host
  def setup_sensors_host (self, host, port):

    self.script.log(2, "KATCPServer::setup_sensors_host ("+host+","+port+")")
    sock = sockets.openSocket (DL, host, int(port), 1)

    if sock:
      self.script.log(2, "KATCPServer::setup_sensors_host sock.send(" + self.script.lmc_cmd + ")") 
      sock.send (self.script.lmc_cmd + "\r\n")
      lmc_reply = sock.recv (65536)
      sock.close()
      xml = xmltodict.parse(lmc_reply)

      self._host_sensors = {}

      # Disk sensors
      self.script.log(2, "KATCPServer::setup_sensors_host configuring disk sensors")
      disk_prefix = host+".disk"
      self._host_sensors["disk_size"] = Sensor.float(disk_prefix+".size",
        description=host+": disk size",
        unit="MB",
        params=[8192,1e9],
        default=0)
      self._host_sensors["disk_available"] = Sensor.float(disk_prefix+".available",
        description=host+": disk available space",
        unit="MB",
        params=[1024,1e9],
        default=0)
      self.add_sensor(self._host_sensors["disk_size"])
      self.add_sensor(self._host_sensors["disk_available"])

      # Server Load sensors
      self.script.log(2, "KATCPServer::setup_sensors_host configuring load sensors")
      self._host_sensors["num_cores"] = Sensor.integer (host+".num_cores",
        description=host+": disk available space",
        unit="MB",
        params=[1,64],
        default=0)

      self._host_sensors["load1"] = Sensor.float(host+".load.1min",
        description=host+": 1 minute load ",
        unit="",
        default=0)

      self._host_sensors["load5"] = Sensor.float(host+".load.5min",
        description=host+": 5 minute load ",
        unit="",
        default=0)
      
      self._host_sensors["load15"] = Sensor.float(host+".load.15min",
        description=host+": 15 minute load ",
        unit="",
        default=0)

      self.add_sensor(self._host_sensors["num_cores"])
      self.add_sensor(self._host_sensors["load1"])
      self.add_sensor(self._host_sensors["load5"])
      self.add_sensor(self._host_sensors["load15"])

      cpu_temp_pattern  = re.compile("cpu[0-9]+_temp")
      fan_speed_pattern = re.compile("fan[0-9,a-z]+")
      power_supply_pattern = re.compile("ps[0-9]+_status")
        
      self.script.log(2, "KATCPServer::setup_sensors_host configuring other metrics")

      if not xml["lmc_reply"]["sensors"] == None:

        for sensor in xml["lmc_reply"]["sensors"]["metric"]:
          name = sensor["@name"]
          if name == "system_temp":
            self._host_sensors[name] = Sensor.float((host+".system_temp"),
              description=host+": system temperature",
              unit="C",
              params=[-20,150],
              default=0)
            self.add_sensor(self._host_sensors[name])

          if cpu_temp_pattern.match(name):
            (cpu, junk) = name.split("_")
            self._host_sensors[name] = Sensor.float((host+"." + name),
              description=host+": "+ cpu +" temperature",
              unit="C",
              params=[-20,150],
              default=0)
            self.add_sensor(self._host_sensors[name])

          if fan_speed_pattern.match(name):
            self._host_sensors[name] = Sensor.float((host+"." + name),
              description=host+": "+name+" speed",
              unit="RPM",
              params=[0,20000],
              default=0)
            self.add_sensor(self._host_sensors[name])

          if power_supply_pattern.match(name):
            self._host_sensors[name] = Sensor.boolean((host+"." + name),
              description=host+": "+name,
              unit="",
              default=0)
            self.add_sensor(self._host_sensors[name])

          # TODO consider adding power supply sensors: e.g.
          #   device-status-kronos1-powersupply1
          #   device-status-kronos1-powersupply2
          #   device-status-kronos2-powersupply1
          #   device-status-kronos2-powersupply2

          # TODO consider adding raid/disk sensors: e.g.
          #   device-status-<host>-raid
          #   device-status-<host>-raid-disk1
          #   device-status-<host>-raid-disk2

        self.script.log(2, "KATCPServer::setup_sensors_host done!")

      else:
        self.script.log(2, "KATCPServer::setup_sensors_host no sensors found")

    else:
      self.script.log(-2, "KATCPServer::setup_sensors_host: could not connect to LMC")

  # setup sensors for each beam
  def setup_sensors_beam (self, beam):

    b = str(beam)
    self._beam_sensors = {}

    self.script.log(2, "KATCPServer::setup_sensors_beam ="+b)

    self._beam_sensors["observing"] = Sensor.boolean("observing",
      description="Beam " + b + " is observing",
      unit="",
      default=0)
    self.add_sensor(self._beam_sensors["observing"])

    self._beam_sensors["snr"] = Sensor.float("snr",
      description="SNR of Beam "+b,
      unit="",
      params=[0,1e9],
      default=0)
    self.add_sensor(self._beam_sensors["snr"])

    self._beam_sensors["power"] = Sensor.float("power",
      description="Power Level of Beam "+b,
      unit="",
      default=0)
    self.add_sensor(self._beam_sensors["power"])

    self._beam_sensors["integrated"] = Sensor.float("integrated",
      description="Length of integration for Beam "+b,
      unit="",
      default=0)
    self.add_sensor(self._beam_sensors["integrated"])

  @request()
  @return_reply(Str())
  def request_beam(self, req):
    """Return the configure beam name."""
    return ("ok", self._beam_name.value())

  @request()
  @return_reply(Str())
  def request_host_name(self, req):
    """Return the name of this server."""
    return ("ok", self._host_name.value())

  @request()
  @return_reply(Float())
  def request_snr(self, req):
    """Return the SNR for this beam."""
    return ("ok", self._beam_sensors["snr"].value())

  @request()
  @return_reply(Float())
  def request_power(self, req):
    """Return the standard deviation of the 8-bit power level."""
    return ("ok", self._beam_sensors["power"].value())

  @request(Str(), Float())
  @return_reply(Str())
  def request_sync_time (self, req, data_product_id, adc_sync_time):
    """Set the ADC_SYNC_TIME for beam of the specified data product."""
    if not data_product_id == self._data_product["id"]:
      return ("fail", "data product " + str (data_product_id) + " was not configured")
    self.script.beam_config["lock"].acquire()
    self.script.beam_config["ADC_SYNC_TIME"] = str(adc_sync_time)
    self.script.beam_config["lock"].release()
    return ("ok", "")

  @request(Str(), Str())
  @return_reply(Str())
  def request_target_start (self, req, data_product_id, target_name):
    """Commence data processing on specific data product and beam using target."""
    self.script.log (1, "request_target_start(" + data_product_id + "," + target_name+")")

    self.script.beam_config["lock"].acquire()
    self.script.beam_config["ADC_SYNC_TIME"] = self.script.cam_config["ADC_SYNC_TIME"]
    self.script.beam_config["OBSERVER"] = self.script.cam_config["OBSERVER"]
    self.script.beam_config["ANTENNAE"] = self.script.cam_config["ANTENNAE"]
    self.script.beam_config["SCHEDULE_BLOCK_ID"] = self.script.cam_config["SCHEDULE_BLOCK_ID"]
    self.script.beam_config["EXPERIMENT_ID"] = self.script.cam_config["EXPERIMENT_ID"]
    self.script.beam_config["DESCRIPTION"] = self.script.cam_config["DESCRIPTION"]
    self.script.beam_config["lock"].release()

    # check the pulsar specified is listed in the catalog
    (result, message) = self.test_pulsar_valid (target_name)
    if result != "ok":
      return (result, message)

    # check the ADC_SYNC_TIME is valid for this beam
    if self.script.beam_config["ADC_SYNC_TIME"] == "0":
      return ("fail", "ADC Synchronisation Time was not valid")
  
    # set the pulsar name, this should include a check if the pulsar is in the catalog
    self.script.beam_config["lock"].acquire()
    if self.script.beam_config["MODE"] == "CAL":
      target_name = target_name + "_R"
    self.script.beam_config["SOURCE"] = target_name
    self.script.beam_config["lock"].release()

    host = self.script.tcs_host
    port = self.script.tcs_port

    self.script.log (2, "request_target_start: opening socket for beam " + beam_id + " to " + host + ":" + str(port))
    sock = sockets.openSocket (DL, host, int(port), 1)
    if sock:
      xml = self.script.get_xml_config()
      sock.send(xml + "\r\n")
      reply = sock.recv (65536)

      xml = self.script.get_xml_start_cmd()
      sock.send(xml + "\r\n")
      reply = sock.recv (65536)

      sock.close()
      return ("ok", "")
    else:
      return ("fail", "could not connect to TCS")

  @request(Str())
  @return_reply(Str())
  def request_target_stop (self, req, data_product_id):
    """Cease data processing with target_name."""
    self.script.log (1, "request_target_stop(" + data_product_id+")")

    self.script.beam_config["lock"].acquire()
    self.script.beam_config["SOURCE"] = ""
    self.script.beam_config["lock"].release()

    host = self.script.tcs_host
    port = self.script.tcs_port
    sock = sockets.openSocket (DL, host, int(port), 1)
    if sock:
      xml = self.script.get_xml_stop_cmd ()
      sock.send(xml + "\r\n")
      reply = sock.recv (65536)
      sock.close()
      return ("ok", "")
    else:
      return ("fail", "could not connect to tcs[beam]")

  @request(Str())
  @return_reply(Str())
  def request_capture_init (self, req, data_product_id):
    """Prepare the ingest process for data capture."""
    self.script.log (1, "request_capture_init: " + str(data_product_id) )
    if not data_product_id == self._data_product["id"]:
      return ("fail", "data product " + str (data_product_id) + " was not configured")
    return ("ok", "")

  @request(Str())
  @return_reply(Str())
  def request_capture_done(self, req, data_product_id):
    """Terminte the ingest process for the specified data_product_id."""
    self.script.log (1, "request_capture_done: " + str(data_product_id))
    if not data_product_id == self._data_product["id"]:
      return ("fail", "data product " + str (data_product_id) + " was not configured")
    return ("ok", "")

  @return_reply(Str())
  def request_configure(self, req, msg):
    """Prepare and configure for the reception of the data_product_id."""
    self.script.log (1, "request_configure: nargs= " + str(len(msg.arguments)) + " msg=" + str(msg))
    if len(msg.arguments) == 0:
      self.script.log (-1, "request_configure: no arguments provided")
      return ("ok", "configured data products: TBD")

    # the sub-array identifier
    data_product_id = msg.arguments[0]

    if len(msg.arguments) == 1:
      self.script.log (1, "request_configure: request for configuration of " + str(data_product_id))
      if data_product_id == self._data_product["id"]:
        configuration = str(data_product_id) + " " + \
                        str(self._data_product['antennas']) + " " + \
                        str(self._data_product['n_channels']) + " " + \
                        str(self._data_product['cbf_source'])
        self.script.log (1, "request_configure: configuration of " + str(data_product_id) + "=" + configuration)
        return ("ok", configuration)
      else:
        self.script.log (-1, "request_configure: no configuration existed for " + str(data_product_id))
        return ("fail", "no configuration existed for " + str(data_product_id))

    if len(msg.arguments) == 4:
      # if the configuration for the specified data product matches extactly the 
      # previous specification for that data product, then no action is required
      self.script.log (1, "configure: configuring " + str(data_product_id))

      if data_product_id == self._data_product["id"] and \
          self._data_product['antennas'] == msg.arguments[1] and \
          self._data_product['n_channels'] == msg.arguments[2] and \
          self._data_product['cbf_source'] == msg.arguments[3]:
        response = "configuration for " + str(data_product_id) + " matched previous"
        self.script.log (1, "configure: " + response)
        return ("ok", response)

      # the data product requires configuration
      else:
        self.script.log (1, "configure: new data product " + data_product_id)

        # determine which sub-array we are matched against
        the_sub_array = -1
        for i in range(4):
          self.script.log (1, "configure: testing self.data_product_res[" + str(i) +"].match(" + data_product_id +")")
          if self.data_product_res[i].match (data_product_id):
            the_sub_array = i + 1

        if the_sub_array == -1:
          self.script.log (1, "configure: could not match subarray from " + data_product_id)
          return ("fail", "could not data product to sub array")

        self.script.log (1, "configure: restarting pubsub for subarray " + str(the_sub_array))
        self.script.pubsub.set_sub_array (the_sub_array, self.script.beam_name)
        self.script.pubsub.restart()

        antennas = msg.arguments[1]
        n_channels = msg.arguments[2]
        cbf_source = msg.arguments[3]

        # check if the number of existing + new beams > available
        (cfreq, bwd, nchan) = self.script.cfg["SUBBAND_CONFIG_0"].split(":")
        if nchan != n_channels:
          self._data_product.pop(data_product_id, None)
          response = "PTUSE configured for " + nchan + " channels"
          self.script.log (-1, "configure: " + response)
          return ("fail", response)

        self._data_product['id'] = data_product_id
        self._data_product['antennas'] = antennas
        self._data_product['n_channels'] = n_channels
        self._data_product['cbf_source'] = cbf_source

        # parse the CBF_SOURCE to determine multicast groups
        (addr, port) = cbf_source.split(":")
        (mcast, count) = addr.split("+")

        self.script.log (2, "configure: parsed " + mcast + "+" + count + ":" + port)
        if not count == "1":
          response = "CBF source did not match ip_address+1:port"
          self.script.log (-1, "configure: " + response)
          return ("fail", response)

        mcasts = ["",""]
        ports = [0, 0]

        quartets = mcast.split(".")
        mcasts[0] = ".".join(quartets)
        quartets[3] = str(int(quartets[3])+1)
        mcasts[1] = ".".join(quartets)

        ports[0] = int(port)
        ports[1] = int(port)

        self.script.log (1, "configure: connecting to RECV instance to update configuration")

        for istream in range(int(self.script.cfg["NUM_STREAM"])):
          (host, beam_idx, subband) = self.script.cfg["STREAM_" + str(istream)].split(":")
          beam = self.script.cfg["BEAM_" + beam_idx]
          if beam == self.script.beam_name:

            # reset ADC_SYNC_TIME on the beam
            self.script.beam_config["lock"].acquire()
            self.script.beam_config["ADC_SYNC_TIME"] = "0";
            self.script.beam_config["lock"].release()

            port = int(self.script.cfg["STREAM_RECV_PORT"]) + istream
            self.script.log (3, "configure: connecting to " + host + ":" + str(port))
            sock = sockets.openSocket (DL, host, port, 1)
            if sock:
              req =  "<?req version='1.0' encoding='ISO-8859-1'?>"
              req += "<recv_cmd>"
              req +=   "<command>configure</command>"
              req +=   "<params>"
              req +=     "<param key='DATA_MCAST_0'>" + mcasts[0] + "</param>"
              req +=     "<param key='DATA_MCAST_1'>" + mcasts[1] + "</param>"
              req +=     "<param key='DATA_PORT_0'>" + str(ports[0]) + "</param>"
              req +=     "<param key='DATA_PORT_1'>" + str(ports[1]) + "</param>"
              req +=     "<param key='META_MCAST_0'>" + mcasts[0] + "</param>"
              req +=     "<param key='META_MCAST_1'>" + mcasts[1] + "</param>"
              req +=     "<param key='META_PORT_0'>" + str(ports[0]) + "</param>"
              req +=     "<param key='META_PORT_1'>" + str(ports[1]) + "</param>"
              req +=   "</params>"
              req += "</recv_cmd>"

              self.script.log (1, "configure: sending XML req")
              sock.send(req)
              recv_reply = sock.recv (65536)
              self.script.log (1, "configure: received " + recv_reply)
              sock.close()

      return ("ok", "data product " + str (data_product_id) + " configured")

    else:
      response = "expected 0, 1 or 4 arguments"
      self.script.log (-1, "configure: " + response)
      return ("fail", response)

  @return_reply(Str())
  def request_deconfigure(self, req, msg):
    """Deconfigure for the data_product."""

    if len(msg.arguments) == 0:
      self.script.log (-1, "request_configure: no arguments provided")
      return ("fail", "expected 1 argument")

    # the sub-array identifier
    data_product_id = msg.arguments[0]

    self.script.log (1, "configure: deconfiguring " + str(data_product_id))

    # check if the data product was previously configured
    if not data_product_id == self._data_product["id"]:
      response = str(data_product_id) + " did not match configured data product [" + self._data_product["id"] + "]"
      self.script.log (-1, "configure: " + response)
      return ("fail", response)

    for istream in range(int(self.script.cfg["NUM_STREAM"])):
      (host, beam_idx, subband) = self.script.cfg["STREAM_" + str(istream)].split(":")
      if self.script.beam_name == self.script.cfg["BEAM_" + beam_idx]:

        # reset ADC_SYNC_TIME on the beam
        self.script.beam_config["lock"].acquire()
        self.script.beam_config["ADC_SYNC_TIME"] = "0";
        self.script.beam_config["lock"].release()

        port = int(self.script.cfg["STREAM_RECV_PORT"]) + istream
        self.script.log (3, "configure: connecting to " + host + ":" + str(port))
        sock = sockets.openSocket (DL, host, port, 1)
        if sock:

          req =  "<?req version='1.0' encoding='ISO-8859-1'?>"
          req += "<recv_cmd>"
          req +=   "<command>deconfigure</command>"
          req += "</recv_cmd>"

          sock.send(req)
          recv_reply = sock.recv (65536)
          sock.close()

      # remove the data product
      self._data_product["id"] = "None"

    response = "data product " + str(data_product_id) + " deconfigured"
    self.script.log (1, "configure: " + response)
    return ("ok", response)

  @request(Int())
  @return_reply(Str())
  def request_output_channels (self, req, nchannels):
    """Set the number of output channels."""
    if not self.test_power_of_two (nchannels):
      return ("fail", "number of channels not a power of two")
    if nchannels < 64 or nchannels > 4096:
      return ("fail", "number of channels not within range 64 - 2048")
    self.script.beam_config["OUTNCHAN"] = str(nchannels)
    return ("ok", "")

  @request(Int())
  @return_reply(Str())
  def request_output_bins(self, req, nbin):
    """Set the number of output phase bins."""
    if not self.test_power_of_two(nbin):
      return ("fail", "nbin not a power of two")
    if nbin < 64 or nbin > 2048:
      return ("fail", "nbin not within range 64 - 2048")
    self.script.beam_config["OUTNBIN"] = str(nbin)
    return ("ok", "")

  @request(Int())
  @return_reply(Str())
  def request_output_tsubint (self, req, tsubint):
    """Set the length of output sub-integrations."""
    if tsubint < 10 or tsubint > 60:
      return ("fail", "length of output subints must be between 10 and 600 seconds")
    self.script.beam_config["OUTTSUBINT"] = str(tsubint)
    return ("ok", "")

  @request(Float())
  @return_reply(Str())
  def request_dm(self, req, dm):
    """Set the value of dispersion measure to be removed"""
    if dm < 0 or dm > 2000:
      return ("fail", "dm not within range 0 - 2000")
    self.script.beam_config["DM"] = str(dm)
    return ("ok", "")

  @request(Float())
  @return_reply(Str())
  def request_cal_freq(self, req, cal_freq):
    """Set the value of noise diode firing frequecny in Hz."""
    if cal_freq < 0 or cal_freq > 1000:
      return ("fail", "CAL freq not within range 0 - 1000")
    self.script.beam_config["CALFREQ"] = str(cal_freq)
    if cal_freq == 0:
      self.script.beam_config["MODE"] = "PSR"
    else:
      self.script.beam_config["MODE"] = "CAL"
    return ("ok", "")

  # test if a number is a power of two
  def test_power_of_two (self, num):
    return num > 0 and not (num & (num - 1))

  # test whether the specified target exists in the pulsar catalog
  def test_pulsar_valid (self, target):

    self.script.log (2, "test_pulsar_valid: get_psrcat_param (" + target + ", jname)")
    (reply, message) = self.get_psrcat_param (target, "jname")
    if reply != "ok":
      return (reply, message)

    self.script.log (2, "test_pulsar_valid: get_psrcat_param () reply=" + reply + " message=" + message)
    if message == target:
      return ("ok", "")
    else:
      return ("fail", "pulsar " + target + " did not exist in catalog")

  def get_psrcat_param (self, target, param):
    cmd = "psrcat -all " + target + " -c " + param + " -nohead -o short"
    rval, lines = self.script.system (cmd, 3)
    if rval != 0 or len(lines) <= 0:
      return ("fail", "could not use psrcat")

    if lines[0].startswith("WARNING"):
      return ("fail", "pulsar " + target_name + " did not exist in catalog")

    parts = lines[0].split()
    if len(parts) == 2 and parts[0] == "1":
      return ("ok", parts[1])
Exemple #4
0
class FitsInterfaceServer(AsyncDeviceServer):
    """
    Class providing an interface between EDD processes and the
    Effelsberg FITS writer
    """
    VERSION_INFO = ("edd-fi-server-api", 1, 0)
    BUILD_INFO = ("edd-fi-server-implementation", 0, 1, "")
    DEVICE_STATUSES = ["ok", "degraded", "fail"]
    PROTOCOL_INFO = ProtocolFlags(
        5, 0, set([
            ProtocolFlags.MULTI_CLIENT,
            ProtocolFlags.MESSAGE_IDS,
        ]))

    def __init__(self, interface, port, capture_interface, capture_port, fw_ip,
                 fw_port):
        """
        @brief Initialization of the FitsInterfaceServer object

        @param  interface          Interface address to serve on
        @param  port               Port number to serve on
        @param  capture_interface  Interface to capture data on
        @param  capture_port       Port to capture data on from instruments
        @param  fw_ip              IP address of the FITS writer
        @param  fw_port            Port number to connected to on FITS writer
        """
        self._configured = False
        self._no_active_beams = None
        self._nchannels = None
        self._integ_time = None
        self._blank_phase = None
        self._capture_interface = capture_interface
        self._capture_port = capture_port
        self._fw_connection_manager = FitsWriterConnectionManager(
            fw_ip, fw_port)
        self._capture_thread = None
        self._shutdown = False
        super(FitsInterfaceServer, self).__init__(interface, port)

    def start(self):
        """
        @brief   Start the server
        """
        self._fw_connection_manager.start()
        super(FitsInterfaceServer, self).start()

    def stop(self):
        """
        @brief   Stop the server
        """
        self._shutdown = True
        self._stop_capture()
        self._fw_connection_manager.stop()
        super(FitsInterfaceServer, self).stop()

    @property
    def nbeams(self):
        return self._active_beams_sensor.value()

    @nbeams.setter
    def nbeams(self, value):
        self._active_beams_sensor.set_value(value)

    @property
    def nchannels(self):
        return self._nchannels_sensor.value()

    @nchannels.setter
    def nchannels(self, value):
        self._nchannels_sensor.set_value(value)

    @property
    def integration_time(self):
        return self._integration_time_sensor.value()

    @integration_time.setter
    def integration_time(self, value):
        self._integration_time_sensor.set_value(value)

    @property
    def nblank_phases(self):
        return self._nblank_phases_sensor.value()

    @nblank_phases.setter
    def nblank_phases(self, value):
        self._nblank_phases_sensor.set_value(value)

    def setup_sensors(self):
        """
        @brief   Setup monitoring sensors
        """
        self._device_status_sensor = Sensor.discrete(
            "device-status",
            description="Health status of FIServer",
            params=self.DEVICE_STATUSES,
            default="ok",
            initial_status=Sensor.UNKNOWN)
        self.add_sensor(self._device_status_sensor)
        self._active_beams_sensor = Sensor.float(
            "nbeams",
            description="Number of beams that are currently active",
            default=1,
            initial_status=Sensor.UNKNOWN)
        self.add_sensor(self._active_beams_sensor)
        self._nchannels_sensor = Sensor.float(
            "nchannels",
            description="Number of channels in each beam",
            default=1,
            initial_status=Sensor.UNKNOWN)
        self.add_sensor(self._nchannels_sensor)
        self._integration_time_sensor = Sensor.float(
            "integration-time",
            description="The integration time for each beam",
            default=1,
            initial_status=Sensor.UNKNOWN)
        self.add_sensor(self._integration_time_sensor)
        self._nblank_phases_sensor = Sensor.integer(
            "nblank-phases",
            description="The number of blank phases",
            default=1,
            initial_status=Sensor.UNKNOWN)
        self.add_sensor(self._nblank_phases_sensor)

    def _stop_capture(self):
        if self._capture_thread:
            log.debug("Cleaning up capture thread")
            self._capture_thread.stop()
            self._capture_thread.join()
            self._capture_thread = None
            log.debug("Capture thread cleaned")

    @request(Int(), Int(), Int(), Int())
    @return_reply()
    def request_configure(self, req, beams, channels, int_time, blank_phases):
        """
        @brief    Configure the FITS interface server

        @param   beams          The number of beams expected
        @param   channels       The number of channels expected
        @param   int_time       The integration time (milliseconds int)
        @param   blank_phases   The number of blank phases (1-4)

        @return     katcp reply object [[[ !configure ok | (fail [error description]) ]]]
        """

        message = ("nbeams={}, nchannels={}, integration_time={},"
                   " nblank_phases={}").format(beams, channels, int_time,
                                               blank_phases)
        log.info("Configuring FITS interface server with params: {}".format(
            message))
        self.nbeams = beams
        self.nchannels = channels
        self.integration_time = int_time
        self.nblank_phases = blank_phases
        self._fw_connection_manager.drop_connection()
        self._stop_capture()
        self._configured = True
        return ("ok", )

    @request()
    @return_reply()
    def request_start(self, req):
        """
        @brief    Start the FITS interface server capturing data

        @return     katcp reply object [[[ !configure ok | (fail [error description]) ]]]
        """
        log.info("Received start request")
        if not self._configured:
            msg = "FITS interface server is not configured"
            log.error(msg)
            return ("fail", msg)
        try:
            fw_socket = self._fw_connection_manager.get_transmit_socket()
        except Exception as error:
            log.exception(str(error))
            return ("fail", str(error))
        log.info("Starting FITS interface capture")
        self._stop_capture()
        buffer_size = 4 * (self.nchannels + 2)
        handler = R2SpectrometerHandler(2, self.nchannels,
                                        self.integration_time,
                                        self.nblank_phases, fw_socket)
        self._capture_thread = CaptureData(self._capture_interface,
                                           self._capture_port, buffer_size,
                                           handler)
        self._capture_thread.start()
        return ("ok", )

    @request()
    @return_reply()
    def request_stop(self, req):
        """
        @brief    Stop the FITS interface server capturing data

        @return     katcp reply object [[[ !configure ok | (fail [error description]) ]]]
        """
        log.info("Received stop request")
        if not self._configured:
            msg = "FITS interface server is not configured"
            log.error(msg)
            return ("fail", msg)
        log.info("Stopping FITS interface capture")
        self._stop_capture()
        self._fw_connection_manager.drop_connection()
        return ("ok", )
class ScamSimulator(DeviceServer):

    VERSION_INFO = ("scam-simulator-version", 1, 0)
    BUILD_INFO = ("scam-simulator-build", 0, 1, "")

    # Optionally set the KATCP protocol version and features. Defaults to
    # the latest implemented version of KATCP, with all supported optional
    # features
    PROTOCOL_INFO = ProtocolFlags(4, 0, set([
        ProtocolFlags.MULTI_CLIENT,
        #ProtocolFlags.MESSAGE_IDS,
    ]))

    def setup_sensors(self):
        # Position sensors
        self._SCM_request_azim = Sensor.float("SCM.request-azim", "Sky-space requested azimuth position.", "Degrees CW of north")
        self.add_sensor(self._SCM_request_azim)

        self._SCM_request_elev = Sensor.float("SCM.request-elev", "Sky-space requested elevation position.", "Degrees CW of north")
        self.add_sensor(self._SCM_request_elev)

        self._SCM_desired_azim = Sensor.float("SCM.desired-azim", "Sky-space desired azimuth position.", "Degrees CW of north")
        self.add_sensor(self._SCM_desired_azim)

        self._SCM_desired_elev = Sensor.float("SCM.desired-elev", "Sky-space desired elevation position.", "Degrees CW of north")
        self.add_sensor(self._SCM_desired_elev)

        self._SCM_actual_azim = Sensor.float("SCM.actual-azim", "Sky-space actual azimuth position.", "Degrees CW of north")
        self.add_sensor(self._SCM_actual_azim)

        self._SCM_actual_elev = Sensor.float("SCM.actual-elev", "Sky-space actual elevation position.", "Degrees CW of north")
        self.add_sensor(self._SCM_actual_elev)

        # Pointing model
        self._SCM_pmodel1 = Sensor.float("SCM.pmodel1", "Pointing model parameter 1")
        self.add_sensor(self._SCM_pmodel1)
        self._SCM_pmodel2 = Sensor.float("SCM.pmodel2", "Pointing model parameter 2")
        self.add_sensor(self._SCM_pmodel2)
        self._SCM_pmodel3 = Sensor.float("SCM.pmodel3", "Pointing model parameter 3")
        self.add_sensor(self._SCM_pmodel3)
        self._SCM_pmodel4 = Sensor.float("SCM.pmodel4", "Pointing model parameter 4")
        self.add_sensor(self._SCM_pmodel4)
        self._SCM_pmodel5 = Sensor.float("SCM.pmodel5", "Pointing model parameter 5")
        self.add_sensor(self._SCM_pmodel5)
        self._SCM_pmodel6 = Sensor.float("SCM.pmodel6", "Pointing model parameter 6")
        self.add_sensor(self._SCM_pmodel6)
        self._SCM_pmodel7 = Sensor.float("SCM.pmodel7", "Pointing model parameter 7")
        self.add_sensor(self._SCM_pmodel7)
        self._SCM_pmodel8 = Sensor.float("SCM.pmodel8", "Pointing model parameter 8")
        self.add_sensor(self._SCM_pmodel8)
        self._SCM_pmodel9 = Sensor.float("SCM.pmodel9", "Pointing model parameter 9")
        self.add_sensor(self._SCM_pmodel9)
        self._SCM_pmodel10 = Sensor.float("SCM.pmodel10", "Pointing model parameter 10")
        self.add_sensor(self._SCM_pmodel10)
        self._SCM_pmodel11 = Sensor.float("SCM.pmodel11", "Pointing model parameter 11")
        self.add_sensor(self._SCM_pmodel11)
        self._SCM_pmodel12 = Sensor.float("SCM.pmodel12", "Pointing model parameter 12")
        self.add_sensor(self._SCM_pmodel12)
        self._SCM_pmodel13 = Sensor.float("SCM.pmodel13", "Pointing model parameter 13")
        self.add_sensor(self._SCM_pmodel13)
        self._SCM_pmodel14= Sensor.float("SCM.pmodel14", "Pointing model parameter 14")
        self.add_sensor(self._SCM_pmodel14)
        self._SCM_pmodel15= Sensor.float("SCM.pmodel15", "Pointing model parameter 15")
        self.add_sensor(self._SCM_pmodel15)
        self._SCM_pmodel16 = Sensor.float("SCM.pmodel16", "Pointing model parameter 16")
        self.add_sensor(self._SCM_pmodel16)
        self._SCM_pmodel17 = Sensor.float("SCM.pmodel17", "Pointing model parameter 17")
        self.add_sensor(self._SCM_pmodel17)
        self._SCM_pmodel18 = Sensor.float("SCM.pmodel18", "Pointing model parameter 18")
        self.add_sensor(self._SCM_pmodel18)
        self._SCM_pmodel19 = Sensor.float("SCM.pmodel19", "Pointing model parameter 19")
        self.add_sensor(self._SCM_pmodel19)
        self._SCM_pmodel20 = Sensor.float("SCM.pmodel20", "Pointing model parameter 20")
        self.add_sensor(self._SCM_pmodel20)
        self._SCM_pmodel21 = Sensor.float("SCM.pmodel21", "Pointing model parameter 21")
        self.add_sensor(self._SCM_pmodel21)
        self._SCM_pmodel22 = Sensor.float("SCM.pmodel22", "Pointing model parameter 22")
        self.add_sensor(self._SCM_pmodel22)
        self._SCM_pmodel23 = Sensor.float("SCM.pmodel23", "Pointing model parameter 23")
        self.add_sensor(self._SCM_pmodel23)
        self._SCM_pmodel24 = Sensor.float("SCM.pmodel24", "Pointing model parameter 24")
        self.add_sensor(self._SCM_pmodel24)
        self._SCM_pmodel25 = Sensor.float("SCM.pmodel25", "Pointing model parameter 25")
        self.add_sensor(self._SCM_pmodel25)
        self._SCM_pmodel26 = Sensor.float("SCM.pmodel26", "Pointing model parameter 26")
        self.add_sensor(self._SCM_pmodel26)
        self._SCM_pmodel27 = Sensor.float("SCM.pmodel27", "Pointing model parameter 27")
        self.add_sensor(self._SCM_pmodel27)
        self._SCM_pmodel28 = Sensor.float("SCM.pmodel28", "Pointing model parameter 28")
        self.add_sensor(self._SCM_pmodel28)
        self._SCM_pmodel29 = Sensor.float("SCM.pmodel29", "Pointing model parameter 29")
        self.add_sensor(self._SCM_pmodel29)
        self._SCM_pmodel30 = Sensor.float("SCM.pmodel30", "Pointing model parameter 30")
        self.add_sensor(self._SCM_pmodel30)

        # # Target
        self._SCM_Target = Sensor.string("SCM.Target", "Target description string in katpoint format")
        self.add_sensor(self._SCM_Target)

        # Antenna activity
        self._SCM_Antenna_Activity = Sensor.string("SCM.AntennaActivity", "Antenna activity label")
        self.add_sensor(self._SCM_Antenna_Activity)

        # RF sensor information
        self._SCM_LcpAttenuation = Sensor.float("SCM.LcpAttenuation", "Variable attenuator setting on LCP")
        self.add_sensor(self._SCM_LcpAttenuation)
        self._SCM_RcpAttenuation = Sensor.float("SCM.RcpAttenuation", "Variable attenuator setting on RCP")
        self.add_sensor(self._SCM_RcpAttenuation)
        self._RFC_LcpFreqSel = Sensor.boolean("RFC.LcpFreqSel", "LCP Frequency Select Switch")
        self.add_sensor(self._RFC_LcpFreqSel)
        self._RFC_RcpFreqSel = Sensor.boolean("RFC.RcpFreqSel", "RCP Frequency Select Switch")
        self.add_sensor(self._RFC_RcpFreqSel)
        self._RFC_IntermediateStage_5GHz = Sensor.float("RFC.IntermediateStage_5GHz", "5 GHz Intermediate Stage LO frequency")
        self.add_sensor(self._RFC_IntermediateStage_5GHz)
        self._RFC_IntermediateStage_6_7GHz = Sensor.float("RFC.IntermediateStage_6_7GHz", "6.7 GHz Intermediate Stage LO frequency")
        self.add_sensor(self._RFC_IntermediateStage_6_7GHz)
        self._RFC_FinalStage = Sensor.float("RFC.FinalStage", "Final Stage LO frequency")
        self.add_sensor(self._RFC_FinalStage)

        # Noise diode sensor information
        self._RFC_NoiseDiode_1 = Sensor.integer("RFC.NoiseDiode_1", "All noise diode data (bitfield)")
        self.add_sensor(self._RFC_NoiseDiode_1)

        # EMS information
        self._EMS_WindDirection = Sensor.float("EMS.WindDirection", "Wind direction")
        self.add_sensor(self._EMS_WindDirection)
        self._EMS_WindSpeed = Sensor.float("EMS.WindSpeed", "Wind speed")
        self.add_sensor(self._EMS_WindSpeed)
        self._EMS_AirTemperature = Sensor.float("EMS.AirTemperature", "Air temperature")
        self.add_sensor(self._EMS_AirTemperature)
        self._EMS_AbsolutePressure = Sensor.float("EMS.AbsolutePressure", "Air pressure")
        self.add_sensor(self._EMS_AbsolutePressure)
        self._EMS_RelativeHumidity = Sensor.float("EMS.RelativeHumidity", "Ambient relative humidity")
        self.add_sensor(self._EMS_RelativeHumidity)

        self.animation_thread = threading.Thread(target=self.sensor_value_thread_function)
        self.animation_thread.start()


    def sensor_value_thread_function(self):

        antenna_str = "ant1, 5:45:2.48, -0:18:17.92, 116, 32.0, 0 0 0, %s" % ("0 " * 23)
        antenna = katpoint.Antenna(antenna_str)
        target_str = "name1 | *name 2, radec, 12:34:56.7, -04:34:34.2, (1000.0 2000.0 1.0)"
        self._SCM_Target.set_value(target_str)
        self._SCM_Antenna_Activity.set_value("idle")
        my_target = AVNTarget(target_str, antenna=antenna)

        self._SCM_pmodel1.set_value(random.random())
        self._SCM_pmodel2.set_value(random.random())
        self._SCM_pmodel3.set_value(random.random())
        self._SCM_pmodel4.set_value(random.random())
        self._SCM_pmodel5.set_value(random.random())
        self._SCM_pmodel6.set_value(random.random())
        self._SCM_pmodel7.set_value(random.random())
        self._SCM_pmodel8.set_value(random.random())
        self._SCM_pmodel9.set_value(random.random())
        self._SCM_pmodel10.set_value(random.random())
        self._SCM_pmodel11.set_value(random.random())
        self._SCM_pmodel12.set_value(random.random())
        self._SCM_pmodel13.set_value(random.random())
        self._SCM_pmodel14.set_value(random.random())
        self._SCM_pmodel15.set_value(random.random())
        self._SCM_pmodel16.set_value(random.random())
        self._SCM_pmodel17.set_value(random.random())
        self._SCM_pmodel18.set_value(random.random())
        self._SCM_pmodel19.set_value(random.random())
        self._SCM_pmodel20.set_value(random.random())
        self._SCM_pmodel21.set_value(random.random())
        self._SCM_pmodel22.set_value(random.random())
        self._SCM_pmodel23.set_value(random.random())
        self._SCM_pmodel24.set_value(random.random())
        self._SCM_pmodel25.set_value(random.random())
        self._SCM_pmodel26.set_value(random.random())
        self._SCM_pmodel27.set_value(random.random())
        self._SCM_pmodel28.set_value(random.random())
        self._SCM_pmodel29.set_value(random.random())
        self._SCM_pmodel30.set_value(random.random())

        # There shouldn't be step-changes in the environment data, so set initial values.
        WindDirection = 360*random.random()
        WindSpeed = 50*random.random()
        AirTemperature = 50*random.random()
        AbsolutePressure = 10*random.random() + 1010.0
        RelativeHumidity = 100*random.random()

        while True:
            target_azel = my_target.azel()
            self._SCM_request_azim.set_value(np.degrees(target_azel[0]))
            self._SCM_request_elev.set_value(np.degrees(target_azel[1]))
            self._SCM_desired_azim.set_value(np.trunc(10*self._SCM_request_azim.value())/10)
            self._SCM_desired_elev.set_value(np.trunc(10*self._SCM_request_elev.value())/10)
            self._SCM_actual_azim.set_value(self._SCM_desired_azim.value() + random.random()/25)
            self._SCM_actual_elev.set_value(self._SCM_desired_elev.value() + random.random()/25)

            if random.random() > 0.5:
                coin_toss = int(4*random.random())
                if coin_toss == 0:
                    self._SCM_Antenna_Activity.set_value("idle")
                elif coin_toss == 1:
                    self._SCM_Antenna_Activity.set_value("slew")
                elif coin_toss == 2:
                    self._SCM_Antenna_Activity.set_value("track")
                elif coin_toss == 3:
                    self._SCM_Antenna_Activity.set_value("scan")
                else:
                    self._SCM_Antenna_Activity.set_value("stop")  # Shouldn't happen, but for logical completeness...

            attenuation = float(random.randint(0, 63))/2
            self._SCM_LcpAttenuation.set_value(attenuation)
            self._SCM_RcpAttenuation.set_value(attenuation)

            # Frequency band doesn't need to be changed as frequently as the other stuff.
            if random.random() > 0.925:
                freq_sel = bool(random.randint(0, 1))
                self._RFC_LcpFreqSel.set_value(freq_sel)
                self._RFC_RcpFreqSel.set_value(freq_sel)
                self._RFC_IntermediateStage_5GHz.set_value(50e6*random.random() + 1.5e9)
                self._RFC_IntermediateStage_6_7GHz.set_value(50e6*random.random() + 3.2e9)
                self._RFC_FinalStage.set_value(50e6*random.random() + 2.85e9)

            # Noise diode info also doesn't need to change every tick.
            if random.random() > 0.6:
                input_source = random.randint(0, 3)
                bit_2 = 0
                enable = random.randint(0, 1)
                noise_diode_select = random.randint(1, 15)  # Not actually sure if this is supposed to be one-hot
                pwm_mark = random.randint(0, 63)
                freq_sel = random.randint(0, 3)
                bitfield = input_source + bit_2*2**2 + enable*2**3 + noise_diode_select*2**4 + pwm_mark*2**8\
                           + freq_sel*2**14
                self._RFC_NoiseDiode_1.set_value(bitfield)

            # Climate information only needs to change occasionally too
            if random.random() > 0.35:
                WindDirection += 2*random.random() - 1
                self._EMS_WindDirection.set_value(WindDirection)
                WindSpeed += random.random() - 0.5
                self._EMS_WindSpeed.set_value(WindSpeed)
                AirTemperature += 0.5*random.random() - 0.25
                self._EMS_AirTemperature.set_value(AirTemperature)
                AbsolutePressure += 0.1*random.random() - 0.05
                self._EMS_AbsolutePressure.set_value(AbsolutePressure)
                RelativeHumidity += 0.1*random.random() - 0.05
                self._EMS_RelativeHumidity.set_value(RelativeHumidity)

            print "\n\nSensors as at {}".format(time.time())
            print "============================================"
            for element_name in iter(dir(self)):
                element = getattr(self, element_name)
                #print "\nElement: {}".format(element)
                #print "Type: {}".format(type(element))
                #print "Isinstance: {}".format(isinstance(element, Sensor))
                if isinstance(element, Sensor):
                    print "{} {} {}".format(element._timestamp, element.name, element._value)
            sys.stdout.flush()
            time.sleep(random.random()*4 + 1)
Exemple #6
0
class KATCPServer(DeviceServer):

    VERSION_INFO = ("ptuse-api", 2, 0)
    BUILD_INFO = ("ptuse-implementation", 0, 1, "")

    # Optionally set the KATCP protocol version and features. Defaults to
    # the latest implemented version of KATCP, with all supported optional
    # features
    PROTOCOL_INFO = ProtocolFlags(
        5, 0, set([
            ProtocolFlags.MULTI_CLIENT,
            ProtocolFlags.MESSAGE_IDS,
        ]))

    def __init__(self, server_host, server_port, script):
        self.script = script
        self._host_sensors = {}
        self._beam_sensors = {}
        self._data_product = {}
        self._data_product["id"] = "None"
        self._data_product["state"] = "None"

        self.data_product_res = []
        self.data_product_res.append(re.compile("^[a-zA-Z]+_1"))
        self.data_product_res.append(re.compile("^[a-zA-Z]+_2"))
        self.data_product_res.append(re.compile("^[a-zA-Z]+_3"))
        self.data_product_res.append(re.compile("^[a-zA-Z]+_4"))

        self.script.log(
            2, "KATCPServer::__init__ starting DeviceServer on " +
            server_host + ":" + str(server_port))
        DeviceServer.__init__(self, server_host, server_port)

    DEVICE_STATUSES = ["ok", "degraded", "fail"]

    def setup_sensors(self):
        """Setup server sensors."""
        self.script.log(2, "KATCPServer::setup_sensors()")

        self._device_status = Sensor.discrete(
            "device-status",
            description="Status of entire system",
            params=self.DEVICE_STATUSES,
            default="ok")
        self.add_sensor(self._device_status)

        self._beam_name = Sensor.string("beam-name",
                                        description="name of configured beam",
                                        unit="",
                                        default="")
        self.add_sensor(self._beam_name)

        # setup host based sensors
        self._host_name = Sensor.string("host-name",
                                        description="hostname of this server",
                                        unit="",
                                        default="")
        self.add_sensor(self._host_name)

        # GUI URL TODO remove hardcoding
        guis = [{
            "title": "PTUSE Web Interface",
            "description": "Live Pulsar timing monitoring plots",
            "href": self.script.cfg["SPIP_ADDRESS"]
        }]
        encoded = json.dumps(guis)
        self._gui_urls = Sensor.string("gui-urls",
                                       description="PTUSE GUI URL",
                                       unit="",
                                       default=encoded)
        self.add_sensor(self._gui_urls)
        self._gui_urls.set_value(encoded)

        # give LMC some time to prepare the socket
        time.sleep(5)

        self.script.log(
            1, "KATCPServer::setup_sensors lmc=" + str(self.script.lmc))
        (host, port) = self.script.lmc.split(":")
        self.setup_sensors_host(host, port)

        self.script.log(
            2, "KATCPServer::setup_sensors beams=" + str(self.script.beam))
        self.setup_sensors_beam(self.script.beam_name)

    # add sensors based on the reply from the specified host
    def setup_sensors_host(self, host, port):

        self.script.log(
            1, "KATCPServer::setup_sensors_host (" + host + "," + port + ")")
        sock = sockets.openSocket(DL, host, int(port), 1)

        if sock:
            self.script.log(
                2, "KATCPServer::setup_sensors_host sock.send(" +
                self.script.lmc_cmd + ")")
            sock.send(self.script.lmc_cmd + "\r\n")
            lmc_reply = sock.recv(65536)
            sock.close()
            xml = xmltodict.parse(lmc_reply)
            self.script.log(
                2, "KATCPServer::setup_sensors_host sock.recv=" + str(xml))

            self._host_sensors = {}

            # Disk sensors
            self.script.log(
                2, "KATCPServer::setup_sensors_host configuring disk sensors")
            disk_prefix = host + ".disk"
            self._host_sensors["disk_size"] = Sensor.float(
                disk_prefix + ".size",
                description=host + ": disk size",
                unit="MB",
                params=[8192, 1e9],
                default=0)
            self._host_sensors["disk_available"] = Sensor.float(
                disk_prefix + ".available",
                description=host + ": disk available space",
                unit="MB",
                params=[1024, 1e9],
                default=0)
            self.add_sensor(self._host_sensors["disk_size"])
            self.add_sensor(self._host_sensors["disk_available"])

            # Server Load sensors
            self.script.log(
                2, "KATCPServer::setup_sensors_host configuring load sensors")
            self._host_sensors["num_cores"] = Sensor.integer(
                host + ".num_cores",
                description=host + ": disk available space",
                unit="MB",
                params=[1, 64],
                default=0)

            self._host_sensors["load1"] = Sensor.float(host + ".load.1min",
                                                       description=host +
                                                       ": 1 minute load ",
                                                       unit="",
                                                       default=0)

            self._host_sensors["load5"] = Sensor.float(host + ".load.5min",
                                                       description=host +
                                                       ": 5 minute load ",
                                                       unit="",
                                                       default=0)

            self._host_sensors["load15"] = Sensor.float(host + ".load.15min",
                                                        description=host +
                                                        ": 15 minute load ",
                                                        unit="",
                                                        default=0)

            self._host_sensors["local_time_synced"] = Sensor.boolean(
                "local_time_synced",
                description=host + ": NTP server synchronisation",
                unit="",
                default=0)

            self.add_sensor(self._host_sensors["num_cores"])
            self.add_sensor(self._host_sensors["num_cores"])
            self.add_sensor(self._host_sensors["load1"])
            self.add_sensor(self._host_sensors["load5"])
            self.add_sensor(self._host_sensors["load15"])
            self.add_sensor(self._host_sensors["local_time_synced"])

            cpu_temp_pattern = re.compile("cpu[0-9]+_temp")
            fan_speed_pattern = re.compile("fan[0-9,a-z]+")
            power_supply_pattern = re.compile("ps[0-9]+_status")

            self.script.log(
                2, "KATCPServer::setup_sensors_host configuring other metrics")

            if not xml["lmc_reply"]["sensors"] == None:

                for sensor in xml["lmc_reply"]["sensors"]["metric"]:
                    name = sensor["@name"]
                    if name == "system_temp":
                        self._host_sensors[name] = Sensor.float(
                            (host + ".system_temp"),
                            description=host + ": system temperature",
                            unit="C",
                            params=[-20, 150],
                            default=0)
                        self.add_sensor(self._host_sensors[name])

                    if cpu_temp_pattern.match(name):
                        (cpu, junk) = name.split("_")
                        self._host_sensors[name] = Sensor.float(
                            (host + "." + name),
                            description=host + ": " + cpu + " temperature",
                            unit="C",
                            params=[-20, 150],
                            default=0)
                        self.add_sensor(self._host_sensors[name])

                    if fan_speed_pattern.match(name):
                        self._host_sensors[name] = Sensor.float(
                            (host + "." + name),
                            description=host + ": " + name + " speed",
                            unit="RPM",
                            params=[0, 20000],
                            default=0)
                        self.add_sensor(self._host_sensors[name])

                    if power_supply_pattern.match(name):
                        self._host_sensors[name] = Sensor.boolean(
                            (host + "." + name),
                            description=host + ": " + name,
                            unit="",
                            default=0)
                        self.add_sensor(self._host_sensors[name])

                    # TODO consider adding power supply sensors: e.g.
                    #   device-status-kronos1-powersupply1
                    #   device-status-kronos1-powersupply2
                    #   device-status-kronos2-powersupply1
                    #   device-status-kronos2-powersupply2

                    # TODO consider adding raid/disk sensors: e.g.
                    #   device-status-<host>-raid
                    #   device-status-<host>-raid-disk1
                    #   device-status-<host>-raid-disk2

                self.script.log(2, "KATCPServer::setup_sensors_host done!")

            else:
                self.script.log(
                    2, "KATCPServer::setup_sensors_host no sensors found")

        else:
            self.script.log(
                -2,
                "KATCPServer::setup_sensors_host: could not connect to LMC")

    # setup sensors for each beam
    def setup_sensors_beam(self, beam):

        b = str(beam)
        self._beam_sensors = {}

        self.script.log(2, "KATCPServer::setup_sensors_beam beam=" + b)

        self._beam_sensors["observing"] = Sensor.boolean("observing",
                                                         description="Beam " +
                                                         b + " is observing",
                                                         unit="",
                                                         default=0)
        self.add_sensor(self._beam_sensors["observing"])

        self._beam_sensors["snr"] = Sensor.float("snr",
                                                 description="SNR of Beam " +
                                                 b,
                                                 unit="",
                                                 params=[0, 1e9],
                                                 default=0)
        self.add_sensor(self._beam_sensors["snr"])

        self._beam_sensors["beamformer_stddev_polh"] = Sensor.float(
            "beamformer_stddev_polh",
            description="Standard deviation of beam voltages for pol H",
            unit="",
            params=[0, 127],
            default=0)
        self.add_sensor(self._beam_sensors["beamformer_stddev_polh"])

        self._beam_sensors["beamformer_stddev_polv"] = Sensor.float(
            "beamformer_stddev_polv",
            description="Standard deviation of beam voltages for pol V",
            unit="",
            params=[0, 127],
            default=0)
        self.add_sensor(self._beam_sensors["beamformer_stddev_polv"])

        self._beam_sensors["integrated"] = Sensor.float(
            "integrated",
            description="Length of integration for Beam " + b,
            unit="",
            default=0)
        self.add_sensor(self._beam_sensors["integrated"])

        self._beam_sensors["input_channels"] = Sensor.integer(
            "input_channels",
            description="Number of configured input channels for Beam " + b,
            unit="",
            default=0)
        self.add_sensor(self._beam_sensors["input_channels"])

    @request()
    @return_reply(Str())
    def request_beam(self, req):
        """Return the configure beam name."""
        return ("ok", self._beam_name.value())

    @request()
    @return_reply(Str())
    def request_host_name(self, req):
        """Return the name of this server."""
        return ("ok", self._host_name.value())

    @request()
    @return_reply(Float())
    def request_snr(self, req):
        """Return the SNR for this beam."""
        return ("ok", self._beam_sensors["snr"].value())

    @request()
    @return_reply(Float())
    def request_beamformer_stddev_polh(self, req):
        """Return the standard deviation of the 8-bit power level of pol H."""
        return ("ok", self._beam_sensors["beamformer_stddev_polh"].value())

    @request()
    @return_reply(Float())
    def request_beamformer_stddev_polv(self, req):
        """Return the standard deviation of the 8-bit power level of pol V."""
        return ("ok", self._beam_sensors["beamformer_stddev_polv"].value())

    @request()
    @return_reply(Float())
    def request_local_time_synced(self, req):
        """Return the sychronisation with NTP time"""
        return ("ok", self._beam_sensors["local_time_synced"].value())

    @request(Float())
    @return_reply(Str())
    def request_sync_time(self, req, adc_sync_time):
        """Set the ADC_SYNC_TIME for beam of the data product."""
        if self._data_product["id"] == "None":
            return ("fail", "data product was not configured")
        self.script.beam_config["lock"].acquire()
        self.script.beam_config["ADC_SYNC_TIME"] = str(adc_sync_time)
        self.script.beam_config["lock"].release()
        return ("ok", "")

    @request(Str())
    @return_reply(Str())
    def request_proposal_id(self, req, proposal_id):
        """Set the PROPOSAL_ID for the data product."""
        if self._data_product["id"] == "None":
            return ("fail", "data product was not configured")
        self.script.beam_config["lock"].acquire()
        self.script.beam_config["PROPOSAL_ID"] = proposal_id
        self.script.beam_config["lock"].release()
        return ("ok", "")

    # ensure state changes work
    def change_state(self, command):

        state = self._data_product["state"]

        reply = "ok"
        message = ""

        if command == "configure":
            if state != "unconfigured":
                message = "received " + command + " command when in " + state + " state"
            else:
                new_state = "configured"

        elif command == "capture_init":
            if state != "configured":
                message = "received " + command + " command when in " + state + " state"
            else:
                new_state = "ready"

        elif command == "target_start":
            if state != "ready":
                message = "received " + command + " command when in " + state + " state"
            else:
                new_state = "recording"

        elif command == "target_stop":
            if state != "recording":
                message = "received " + command + " command when in " + state + " state"
            else:
                new_state = "ready"

        elif command == "capture_done":
            if state != "ready" and state != "configured":
                message = "received " + command + " command when in " + state + " state"
            else:
                new_state = "configured"

        elif command == "deconfigure":
            if state != "configured":
                message = "received " + command + " command when in " + state + " state"
            else:
                new_state = "unconfigured"

        if message == "":
            self.script.log(
                1, "change_state: " + self._data_product["state"] + " -> " +
                new_state)
            self._data_product["state"] = new_state
        else:
            self.script.log(-1, "change_state: " + message)
            reply = "fail"

        return (reply, message)

    @request(Str())
    @return_reply(Str())
    def request_target_start(self, req, target_name):
        """Commence data processing using target."""
        self.script.log(1, "request_target_start(" + target_name + ")")
        if self._data_product["id"] == "None":
            return ("fail", "data product was not configured")

        self.script.log(
            1, "request_target_start ADC_SYNC_TIME=" +
            self.script.cam_config["ADC_SYNC_TIME"])

        self.script.beam_config["lock"].acquire()
        self.script.beam_config["TARGET"] = self.script.cam_config["TARGET"]
        if self.script.cam_config["ADC_SYNC_TIME"] != "0":
            self.script.beam_config["ADC_SYNC_TIME"] = self.script.cam_config[
                "ADC_SYNC_TIME"]

        self.script.beam_config["NCHAN_PER_STREAM"] = self.script.cam_config[
            "NCHAN_PER_STREAM"]
        self.script.beam_config[
            "PRECISETIME_FRACTION_POLV"] = self.script.cam_config[
                "PRECISETIME_FRACTION_POLV"]
        self.script.beam_config[
            "PRECISETIME_FRACTION_POLH"] = self.script.cam_config[
                "PRECISETIME_FRACTION_POLH"]
        self.script.beam_config[
            "PRECISETIME_UNCERTAINTY_POLV"] = self.script.cam_config[
                "PRECISETIME_UNCERTAINTY_POLV"]
        self.script.beam_config[
            "PRECISETIME_UNCERTAINTY_POLH"] = self.script.cam_config[
                "PRECISETIME_UNCERTAINTY_POLH"]
        self.script.beam_config["TFR_KTT_GNSS"] = self.script.cam_config[
            "TFR_KTT_GNSS"]

        self.script.beam_config["ITRF"] = self.script.cam_config["ITRF"]
        self.script.beam_config["OBSERVER"] = self.script.cam_config[
            "OBSERVER"]
        self.script.beam_config["ANTENNAE"] = self.script.cam_config[
            "ANTENNAE"]
        self.script.beam_config["SCHEDULE_BLOCK_ID"] = self.script.cam_config[
            "SCHEDULE_BLOCK_ID"]
        self.script.beam_config["PROPOSAL_ID"] = self.script.cam_config[
            "PROPOSAL_ID"]
        self.script.beam_config["EXPERIMENT_ID"] = self.script.cam_config[
            "EXPERIMENT_ID"]
        self.script.beam_config["DESCRIPTION"] = self.script.cam_config[
            "DESCRIPTION"]
        self.script.beam_config["lock"].release()

        # check the pulsar specified is listed in the catalog
        (result, message) = self.test_pulsar_valid(target_name)
        if result != "ok":
            return (result, message)

        # check the ADC_SYNC_TIME is valid for this beam
        if self.script.beam_config["ADC_SYNC_TIME"] == "0":
            return ("fail", "ADC Synchronisation Time was not valid")

        # change the state
        (result, message) = self.change_state("target_start")
        if result != "ok":
            self.script.log(-1,
                            "target_start: change_state failed: " + message)
            return (result, message)

        # set the pulsar name, this should include a check if the pulsar is in the catalog
        self.script.beam_config["lock"].acquire()
        self.script.beam_config["SOURCE"] = target_name
        self.script.beam_config["lock"].release()

        host = self.script.tcs_host
        port = self.script.tcs_port

        self.script.log(
            2, "request_target_start: opening socket to " + host + ":" +
            str(port))
        sock = sockets.openSocket(DL, host, int(port), 1)
        if sock:
            xml = self.script.get_xml_config()
            self.script.log(2,
                            "request_target_start: get_xml_config=" + str(xml))
            sock.send(xml + "\r\n")
            reply = sock.recv(65536)
            self.script.log(2, "request_target_start: reply=" + str(reply))

            xml = self.script.get_xml_start_cmd()
            self.script.log(
                2, "request_target_start: get_xml_start_cmd=" + str(xml))
            sock.send(xml + "\r\n")
            reply = sock.recv(65536)
            self.script.log(2, "request_target_start: reply=" + str(reply))

            sock.close()
            return ("ok", "")
        else:
            return ("fail", "could not connect to TCS")

    @request()
    @return_reply(Str())
    def request_target_stop(self, req):
        """Cease data processing with target_name."""
        self.script.log(1, "request_target_stop()")
        return self.target_stop()

    def target_stop(self):

        if self._data_product["id"] == "None":
            return ("fail", "data product was not configured")

        # change the state
        (result, message) = self.change_state("target_stop")
        if result != "ok":
            self.script.log(-1, "target_stop: change_state failed: " + message)
            return (result, message)

        self.script.reset_beam_config()

        host = self.script.tcs_host
        port = self.script.tcs_port
        sock = sockets.openSocket(DL, host, int(port), 1)
        if sock:
            xml = self.script.get_xml_stop_cmd()
            sock.send(xml + "\r\n")
            reply = sock.recv(65536)
            sock.close()
            return ("ok", "")
        else:
            return ("fail", "could not connect to tcs[beam]")

    @request()
    @return_reply(Str())
    def request_capture_init(self, req):
        """Prepare the ingest process for data capture."""
        self.script.log(1, "request_capture_init()")

        # change the state
        (result, message) = self.change_state("capture_init")
        if result != "ok":
            self.script.log(-1,
                            "capture_init: change_state failed: " + message)
            return (result, message)

        return ("ok", "")

    @request()
    @return_reply(Str())
    def request_capture_done(self, req):
        """Terminte the ingest process."""
        self.script.log(1, "request_capture_done()")
        return self.capture_done()

    def capture_done(self):

        # in case the observing was terminated early
        if self._data_product["state"] == "recording":
            (result, message) = self.target_stop()

        # change the state
        (result, message) = self.change_state("capture_done")
        if result != "ok":
            self.script.log(-1,
                            "capture_done: change_state failed: " + message)
            return (result, message)

        return ("ok", "")

    @return_reply(Str())
    def request_configure(self, req, msg):
        """Prepare and configure for the reception of the data_product_id."""
        self.script.log(
            1, "request_configure: nargs= " + str(len(msg.arguments)) +
            " msg=" + str(msg))
        if len(msg.arguments) == 0:
            self.script.log(-1, "request_configure: no arguments provided")
            return ("ok", "configured data products: TBD")

        # the sub-array identifier
        data_product_id = msg.arguments[0]

        if len(msg.arguments) == 1:
            self.script.log(
                1, "request_configure: request for configuration of " +
                str(data_product_id))
            if data_product_id == self._data_product["id"]:
                configuration = str(data_product_id) + " " + \
                                str(self._data_product['antennas']) + " " + \
                                str(self._data_product['n_channels']) + " " + \
                                str(self._data_product['cbf_source']) + " " + \
                                str(self._data_product['proxy_name'])
                self.script.log(
                    1, "request_configure: configuration of " +
                    str(data_product_id) + "=" + configuration)
                return ("ok", configuration)
            else:
                self.script.log(
                    -1, "request_configure: no configuration existed for " +
                    str(data_product_id))
                return ("fail",
                        "no configuration existed for " + str(data_product_id))

        if len(msg.arguments) == 5:
            # if the configuration for the specified data product matches extactly the
            # previous specification for that data product, then no action is required
            self.script.log(1,
                            "configure: configuring " + str(data_product_id))

            if data_product_id == self._data_product["id"] and \
                self._data_product['antennas'] == msg.arguments[1] and \
                self._data_product['n_channels'] == msg.arguments[2] and \
                self._data_product['cbf_source'] == str(msg.arguments[3]) and \
                self._data_product['proxy_name'] == str(msg.arguments[4]):
                response = "configuration for " + str(
                    data_product_id) + " matched previous"
                self.script.log(1, "configure: " + response)
                return ("ok", response)

            # the data product requires configuration
            else:
                self.script.log(
                    1, "configure: new data product " + data_product_id)

                # TODO decide what to do regarding preconfigured params (e.g. FREQ, BW) vs CAM supplied values

                # determine which sub-array we are matched against
                the_sub_array = -1
                for i in range(4):
                    self.script.log(
                        1, "configure: testing self.data_product_res[" +
                        str(i) + "].match(" + data_product_id + ")")
                    if self.data_product_res[i].match(data_product_id):
                        the_sub_array = i + 1

                if the_sub_array == -1:
                    self.script.log(
                        1, "configure: could not match subarray from " +
                        data_product_id)
                    return ("fail", "could not data product to sub array")

                antennas = msg.arguments[1]
                n_channels = msg.arguments[2]
                cbf_source = str(msg.arguments[3])
                streams = json.loads(msg.arguments[3])
                proxy_name = str(msg.arguments[4])

                self.script.log(2, "configure: streams=" + str(streams))

                # check if the number of existing + new beams > available
                # (cfreq, bwd, nchan1) = self.script.cfg["SUBBAND_CONFIG_0"].split(":")
                # (cfreq, bwd, nchan2) = self.script.cfg["SUBBAND_CONFIG_1"].split(":")
                # nchan = int(nchan1) + int(nchan2)
                #if nchan != int(n_channels):
                #  self._data_product.pop(data_product_id, None)
                #  response = "PTUSE configured for " + str(nchan) + " channels"
                #  self.script.log (-1, "configure: " + response)
                #  return ("fail", response)

                self._data_product['id'] = data_product_id
                self._data_product['antennas'] = antennas
                self._data_product['n_channels'] = n_channels
                self._data_product['cbf_source'] = cbf_source
                self._data_product['streams'] = str(streams)
                self._data_product['proxy_name'] = proxy_name
                self._data_product['state'] = "unconfigured"

                # change the state
                (result, message) = self.change_state("configure")
                if result != "ok":
                    self.script.log(
                        -1, "configure: change_state failed: " + message)
                    return (result, message)

                # determine the CAM metadata server and update pubsub
                cam_server = "None"
                fengine_stream = "None"
                polh_stream = "None"
                polv_stream = "None"

                self.script.log(
                    2, "configure: streams.keys()=" + str(streams.keys()))
                self.script.log(
                    2, "configure: streams['cam.http'].keys()=" +
                    str(streams['cam.http'].keys()))

                if 'cam.http' in streams.keys(
                ) and 'camdata' in streams['cam.http'].keys():
                    cam_server = streams['cam.http']['camdata']
                    self.script.log(2,
                                    "configure: cam_server=" + str(cam_server))

                if 'cbf.antenna_channelised_voltage' in streams.keys():
                    stream_name = streams[
                        'cbf.antenna_channelised_voltage'].keys()[0]
                    fengine_stream = stream_name.split(".")[0]
                    self.script.log(
                        2, "configure: fengine_stream=" + str(fengine_stream))

                if 'cbf.tied_array_channelised_voltage' in streams.keys():
                    for s in streams[
                            'cbf.tied_array_channelised_voltage'].keys():
                        if s.endswith('y'):
                            polv_stream = s
                        if s.endswith('x'):
                            polh_stream = s
                    self.script.log(
                        2, "configure: polh_stream=" + str(polh_stream) +
                        " polv_stream=" + str(polv_stream))

                if cam_server != "None" and fengine_stream != "None" and polh_stream != "None":
                    self.script.pubsub.update_cam(cam_server, fengine_stream,
                                                  polh_stream, polv_stream,
                                                  antennas)
                else:
                    response = "Could not extract streams[cam.http][camdata]"
                    self.script.log(1, "configure: cam_server=" + cam_server)
                    self.script.log(
                        1, "configure: fengine_stream=" + fengine_stream)
                    self.script.log(1, "configure: polh_stream=" + polh_stream)
                    self.script.log(-1, "configure: " + response)
                    return ("fail", response)

                # restart the pubsub service
                self.script.log(
                    1, "configure: restarting pubsub for new meta-data")
                self.script.pubsub.restart()

                # determine the X and Y tied array channelised voltage streams
                mcasts = {}
                ports = {}
                key = 'cbf.tied_array_channelised_voltage'
                if key in streams.keys():
                    stream = 'i0.tied-array-channelised-voltage.0x'
                    if stream in streams[key].keys():
                        (mcast,
                         port) = self.parseStreamAddress(streams[key][stream])
                        mcasts['x'] = mcast
                        ports['x'] = int(port)
                    else:
                        response = "Could not extract streams[" + key + "][" + stream + "]"
                        self.script.log(-1, "configure: " + response)
                        return ("fail", response)

                    stream = 'i0.tied-array-channelised-voltage.0y'
                    if stream in streams[key].keys():
                        (mcast,
                         port) = self.parseStreamAddress(streams[key][stream])
                        mcasts['y'] = mcast
                        ports['y'] = int(port)
                    else:
                        response = "Could not extract streams[" + key + "][" + stream + "]"
                        self.script.log(-1, "configure: " + response)
                        return ("fail", response)

                # if the backend nchan is < CAM nchan
                self.script.log(1, "configure: n_channels=" + str(n_channels))
                if int(n_channels) == 1024 and False:
                    nchan = 992
                    self.script.log(
                        1, "configure: reconfiguring MCAST groups from  " +
                        mcasts['x'] + ", " + mcasts['y'])
                    (mcast_base_x, mcast_ngroups_x) = mcasts['x'].split("+")
                    (mcast_base_y, mcast_ngroups_y) = mcasts['y'].split("+")
                    nchan_per_group = int(n_channels) / int(mcast_ngroups_x)
                    new_ngroups = nchan / nchan_per_group
                    offset = (int(mcast_ngroups_x) - new_ngroups) / 2
                    self.script.log(
                        1, "configure: nchan_per_group=" +
                        str(nchan_per_group) + " new_ngroups=" +
                        str(new_ngroups) + " offset=" + str(offset))
                    parts_x = mcast_base_x.split(".")
                    parts_y = mcast_base_y.split(".")
                    parts_x[3] = str(int(parts_x[3]) + offset)
                    parts_y[3] = str(int(parts_y[3]) + offset)
                    mcasts['x'] = ".".join(parts_x) + "+" + str(new_ngroups)
                    mcasts['y'] = ".".join(parts_y) + "+" + str(new_ngroups)
                    self.script.log(
                        1, "configure: reconfigured MCAST groups to " +
                        mcasts['x'] + ", " + mcasts['y'])

                self.script.log(
                    1,
                    "configure: connecting to RECV instance to update configuration"
                )

                for istream in range(int(self.script.cfg["NUM_STREAM"])):
                    (host, beam_idx,
                     subband) = self.script.cfg["STREAM_" +
                                                str(istream)].split(":")
                    beam = self.script.cfg["BEAM_" + beam_idx]
                    self.script.log(
                        1, "configure: istream=" + str(istream) + " beam=" +
                        beam + " script.beam_name=" + self.script.beam_name)
                    if beam == self.script.beam_name:

                        # reset ADC_SYNC_TIME on the beam
                        self.script.beam_config["lock"].acquire()
                        self.script.beam_config["ADC_SYNC_TIME"] = "0"
                        self.script.beam_config["lock"].release()

                        port = int(
                            self.script.cfg["STREAM_RECV_PORT"]) + istream
                        self.script.log(
                            1, "configure: connecting to " + host + ":" +
                            str(port))
                        sock = sockets.openSocket(DL, host, port, 1)
                        if sock:
                            req = "<?req version='1.0' encoding='ISO-8859-1'?>"
                            req += "<recv_cmd>"
                            req += "<command>configure</command>"
                            req += "<params>"

                            req += "<param key='DATA_MCAST_0'>" + mcasts[
                                'x'] + "</param>"
                            req += "<param key='DATA_PORT_0'>" + str(
                                ports['x']) + "</param>"
                            req += "<param key='META_MCAST_0'>" + mcasts[
                                'x'] + "</param>"
                            req += "<param key='META_PORT_0'>" + str(
                                ports['x']) + "</param>"
                            req += "<param key='DATA_MCAST_1'>" + mcasts[
                                'y'] + "</param>"
                            req += "<param key='DATA_PORT_1'>" + str(
                                ports['y']) + "</param>"
                            req += "<param key='META_MCAST_1'>" + mcasts[
                                'y'] + "</param>"
                            req += "<param key='META_PORT_1'>" + str(
                                ports['y']) + "</param>"

                            req += "</params>"
                            req += "</recv_cmd>"

                            self.script.log(
                                1, "configure: sending XML req [" + req + "]")
                            sock.send(req)
                            self.script.log(
                                1, "configure: send XML, receiving reply")
                            recv_reply = sock.recv(65536)
                            self.script.log(
                                1, "configure: received " + recv_reply)
                            sock.close()
                        else:
                            response = "configure: could not connect to stream " + str(
                                istream) + " at " + host + ":" + str(port)
                            self.script.log(-1, "configure: " + response)
                            return ("fail", response)

            return ("ok",
                    "data product " + str(data_product_id) + " configured")

        else:
            response = "expected 0, 1 or 5 arguments, received " + str(
                len(msg.arguments))
            self.script.log(-1, "configure: " + response)
            return ("fail", response)

    # parse address of from spead://AAA.BBB.CCC.DDD+NN:PORT into
    def parseStreamAddress(self, stream):

        self.script.log(2, "parseStreamAddress: parsing " + stream)
        (prefix, spead_address) = stream.split("//")
        (mcast, port) = spead_address.split(":")
        self.script.log(2, "parseStreamAddress: parsed " + mcast + ":" + port)

        return (mcast, port)

    @return_reply(Str())
    def request_deconfigure(self, req, msg):
        """Deconfigure for the data_product."""
        self.script.log(1, "request_deconfigure()")

        # in case the observing was terminated early
        if self._data_product["state"] == "recording":
            (result, message) = self.target_stop()

        if self._data_product["state"] == "ready":
            (result, message) = self.capture_done()

        data_product_id = self._data_product["id"]

        # check if the data product was previously configured
        if not data_product_id == self._data_product["id"]:
            response = str(
                data_product_id
            ) + " did not match configured data product [" + self._data_product[
                "id"] + "]"
            self.script.log(-1, "configure: " + response)
            return ("fail", response)

        # change the state
        (result, message) = self.change_state("deconfigure")
        if result != "ok":
            self.script.log(-1, "deconfigure: change_state failed: " + message)
            return (result, message)

        for istream in range(int(self.script.cfg["NUM_STREAM"])):
            (host, beam_idx,
             subband) = self.script.cfg["STREAM_" + str(istream)].split(":")
            if self.script.beam_name == self.script.cfg["BEAM_" + beam_idx]:

                # reset ADC_SYNC_TIME on the beam
                self.script.beam_config["lock"].acquire()
                self.script.beam_config["ADC_SYNC_TIME"] = "0"
                self.script.beam_config["lock"].release()

                port = int(self.script.cfg["STREAM_RECV_PORT"]) + istream
                self.script.log(
                    3, "configure: connecting to " + host + ":" + str(port))
                sock = sockets.openSocket(DL, host, port, 1)
                if sock:

                    req = "<?req version='1.0' encoding='ISO-8859-1'?>"
                    req += "<recv_cmd>"
                    req += "<command>deconfigure</command>"
                    req += "</recv_cmd>"

                    sock.send(req)
                    recv_reply = sock.recv(65536)
                    sock.close()

            # remove the data product
            self._data_product["id"] = "None"

        response = "data product " + str(data_product_id) + " deconfigured"
        self.script.log(1, "configure: " + response)
        return ("ok", response)

    @request(Int())
    @return_reply(Str())
    def request_output_channels(self, req, nchannels):
        """Set the number of output channels."""
        self.script.log(1,
                        "request_output_channels: nchannels=" + str(nchannels))
        if not self.test_power_of_two(nchannels):
            self.script.log(
                -1, "request_output_channels: " + str(nchannels) +
                " not a power of two")
            return ("fail", "number of channels not a power of two")
        if nchannels < 64 or nchannels > 4096:
            self.script.log(
                -1, "request_output_channels: " + str(nchannels) +
                " not within range 64 - 4096")
            return ("fail", "number of channels not within range 64 - 4096")
        self.script.beam_config["lock"].acquire()
        self.script.beam_config["OUTNCHAN"] = str(nchannels)
        self.script.beam_config["lock"].release()
        return ("ok", "")

    @request(Int())
    @return_reply(Str())
    def request_output_bins(self, req, nbin):
        """Set the number of output phase bins."""
        self.script.log(1, "request_output_bins: nbin=" + str(nbin))
        if not self.test_power_of_two(nbin):
            self.script.log(
                -1,
                "request_output_bins: " + str(nbin) + " not a power of two")
            return ("fail", "nbin not a power of two")
        if nbin < 64 or nbin > 2048:
            self.script.log(
                -1, "request_output_bins: " + str(nbin) +
                " not within range 64 - 2048")
            return ("fail", "nbin not within range 64 - 2048")
        self.script.beam_config["lock"].acquire()
        self.script.beam_config["OUTNBIN"] = str(nbin)
        self.script.beam_config["lock"].release()
        return ("ok", "")

    @request(Int())
    @return_reply(Str())
    def request_output_tsubint(self, req, tsubint):
        """Set the length of output sub-integrations."""
        self.script.log(1, "request_output_tsubint: tsubint=" + str(tsubint))
        if tsubint < 10 or tsubint > 60:
            self.script.log(
                -1, "request_output_tsubint: " + str(tsubint) +
                " not within range 10 - 60")
            return (
                "fail",
                "length of output subints must be between 10 and 60 seconds")
        self.script.beam_config["lock"].acquire()
        self.script.beam_config["OUTTSUBINT"] = str(tsubint)
        self.script.beam_config["lock"].release()
        return ("ok", "")

    @request(Float())
    @return_reply(Str())
    def request_dispersion_measure(self, req, dm):
        """Set the value of dispersion measure to be removed"""
        self.script.log(1, "request_dispersion_measure: dm=" + str(dm))
        if dm > 2000:
            self.script.log(
                -1, "request_dispersion_measure: " + str(dm) + " > 2000")
            return ("fail", "dm greater than limit of 2000")
        self.script.beam_config["lock"].acquire()
        self.script.beam_config["DM"] = str(dm)
        self.script.beam_config["lock"].release()
        return ("ok", "")

    @request(Float())
    @return_reply(Str())
    def request_calibration_freq(self, req, cal_freq):
        """Set the value of noise diode firing frequecny in Hz."""
        self.script.log(1,
                        "request_calibration_freq: cal_freq=" + str(cal_freq))
        if cal_freq < 0 or cal_freq > 1000:
            return ("fail", "CAL freq not within range 0 - 1000")
        self.script.beam_config["lock"].acquire()
        self.script.beam_config["CALFREQ"] = str(cal_freq)
        if cal_freq == 0:
            self.script.beam_config["MODE"] = "PSR"
        else:
            self.script.beam_config["MODE"] = "CAL"
        self.script.beam_config["lock"].release()
        return ("ok", "")

    @request(Int())
    @return_reply(Str())
    def request_output_npol(self, req, outnpol):
        """Set the number of output pol parameters."""
        self.script.log(1, "request_output_npol: outnpol=" + str(outnpol))
        if outnpol != 1 and outnpol != 2 and outnpol != 3 and outnpol != 4:
            self.script.log(
                -1, "request_output_npol: " + str(outnpol) + " not 1, 2 or 4")
            return ("fail", "output npol must be between 1, 2 or 4")
        self.script.beam_config["lock"].acquire()
        self.script.beam_config["OUTNPOL"] = str(outnpol)
        self.script.beam_config["lock"].release()
        return ("ok", "")

    @request(Int())
    @return_reply(Str())
    def request_output_nbit(self, req, outnbit):
        """Set the number of bits per output sample."""
        self.script.log(1, "request_output_nbit: outnbit=" + str(outnbit))
        if outnbit != 1 and outnbit != 2 and outnbit != 4 and outnbit != 8:
            self.script.log(
                -1,
                "request_output_nbit: " + str(outnbit) + " not 1, 2, 4 or 8")
            return ("fail", "output nbit must be between 1, 2, 4 or 8")
        self.script.beam_config["lock"].acquire()
        self.script.beam_config["OUTNBIT"] = str(outnbit)
        self.script.beam_config["lock"].release()
        return ("ok", "")

    @request(Int())
    @return_reply(Str())
    def request_output_tdec(self, req, outtdec):
        """Set the number of input samples integrated into 1 output sample."""
        self.script.log(1, "request_output_tdec: outtdec=" + str(outtdec))
        if outtdec < 16 or outtdec > 131072:
            self.script.log(
                -1, "request_output_tdec: " + str(outtdec) +
                " not in range [16..131072]")
            return ("fail", "output tdec must be between 16 and 131072")
        self.script.beam_config["lock"].acquire()
        self.script.beam_config["OUTTDEC"] = str(outtdec)
        self.script.beam_config["lock"].release()
        return ("ok", "")

    @request()
    @return_reply(Str())
    def request_fold_mode(self, req):
        """Set the processing mode to produce folded archives."""
        self.script.beam_config["lock"].acquire()
        self.script.beam_config["PERFORM_FOLD"] = "1"
        self.script.beam_config["PERFORM_SEARCH"] = "0"
        self.script.log(1, "request_search_mode: PERFORM_FOLD=1")
        self.script.beam_config["lock"].release()
        return ("ok", "")

    @request()
    @return_reply(Str())
    def request_search_mode(self, req):
        """Set the processing mode to produce filterbank data."""
        self.script.beam_config["lock"].acquire()
        self.script.beam_config["PERFORM_FOLD"] = "0"
        self.script.beam_config["PERFORM_SEARCH"] = "1"
        self.script.log(1, "request_search_mode: PERFORM_SEARCH=1")
        self.script.beam_config["lock"].release()
        return ("ok", "")

    @request()
    @return_reply(Str())
    def request_disable_zeroed_buffers(self, req):
        """Disable zeroing of ring buffers, enabling stats mode."""
        self.script.beam_config["lock"].acquire()
        self.script.log(1, "request_disable_zeroed_buffers: ZERO_COPY=0")
        self.script.beam_config["ZERO_COPY"] = "0"
        self.script.beam_config["lock"].release()
        return ("ok", "")

    @request()
    @return_reply(Str())
    def request_enable_zeroed_buffers(self, req):
        """Enable zeroing of ring buffers, disabling stats mode."""
        self.script.beam_config["lock"].acquire()
        self.script.log(1, "request_enable_zeroed_buffers: ZERO_COPY=1")
        self.script.beam_config["ZERO_COPY"] = "1"
        self.script.beam_config["lock"].release()
        return ("ok", "")

    # test if a number is a power of two
    def test_power_of_two(self, num):
        return num > 0 and not (num & (num - 1))

    # test whether the specified target exists in the pulsar catalog
    def test_pulsar_valid(self, target):

        self.script.log(2, "test_pulsar_valid: target='[" + target + "]")

        # remove the _R suffix
        if target.endswith('_R'):
            target = target[:-2]

        # check if the target matches the fluxcal.on file
        cmd = "grep " + target + " " + self.script.cfg[
            "CONFIG_DIR"] + "/fluxcal.on | wc -l"
        rval, lines = self.script.system(cmd, 3)
        if rval == 0 and len(lines) == 1 and int(lines[0]) > 0:
            return ("ok", "")

        # check if the target matches the fluxcal.off file
        cmd = "grep " + target + " " + self.script.cfg[
            "CONFIG_DIR"] + "/fluxcal.off | wc -l"
        rval, lines = self.script.system(cmd, 3)
        if rval == 0 and len(lines) == 1 and int(lines[0]) > 0:
            return ("ok", "")

        self.script.log(
            2, "test_pulsar_valid: get_psrcat_param (" + target + ", jname)")
        (reply, message) = self.get_psrcat_param(target, "jname")
        if reply != "ok":
            return (reply, message)

        self.script.log(
            2, "test_pulsar_valid: get_psrcat_param () reply=" + reply +
            " message=" + message)
        if message == target:
            return ("ok", "")
        else:
            return ("fail", "pulsar " + target + " did not exist in catalog")

    def get_psrcat_param(self, target, param):

        # remove the _R suffix
        if target.endswith('_R'):
            target = target[:-2]

        cmd = "psrcat -all " + target + " -c " + param + " -nohead -o short"
        rval, lines = self.script.system(cmd, 3)
        if rval != 0 or len(lines) <= 0:
            return ("fail", "could not use psrcat")

        if lines[0].startswith("WARNING"):
            return ("fail",
                    "pulsar " + target_name + " did not exist in catalog")

        parts = lines[0].split()
        if len(parts) == 2 and parts[0] == "1":
            return ("ok", parts[1])