From 76fefdba974661c8e001f350487dd04bdf57539b Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Wed, 24 Jan 2024 15:21:50 -0800 Subject: [PATCH 01/55] Copying over first file from dl2 dragonfly branch. --- dripline/extensions/daq/r2daq.py | 1446 ++++++++++++++++++++++++++++++ 1 file changed, 1446 insertions(+) create mode 100644 dripline/extensions/daq/r2daq.py diff --git a/dripline/extensions/daq/r2daq.py b/dripline/extensions/daq/r2daq.py new file mode 100644 index 00000000..42e4ec19 --- /dev/null +++ b/dripline/extensions/daq/r2daq.py @@ -0,0 +1,1446 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +""" + +from __future__ import division + + +try: + import adc5g + from numpy import ( + abs, + array, + ceil, + complex64, + concatenate, + float32, + floor, + int8, + ones, + pi, + uint32, + uint64, + uint8, + zeros, + ) + from scipy.signal import firwin2, freqz + from corr.katcp_wrapper import FpgaClient +except ImportError: + pass +from copy import deepcopy +from datetime import datetime +import logging +import re +from socket import inet_aton, socket, AF_INET, SOCK_DGRAM +from struct import unpack +from time import sleep, time + +logger = logging.getLogger(__name__) + +class Packet(): + """ + Encapsulate an R2DAQ packet + + """ + + BYTES_IN_PAYLOAD = 8192 + BYTES_IN_HEADER = 32 + BYTES_IN_PACKET = BYTES_IN_PAYLOAD + BYTES_IN_HEADER + + @property + def unix_time(self): + return self._unix_time + + @property + def pkt_in_batch(self): + return self._pkt_in_batch + + @property + def digital_id(self): + return self._digital_id + + @property + def if_id(self): + return self._if_id + + @property + def user_data_1(self): + return self._user_data_1 + + @property + def user_data_0(self): + return self._user_data_0 + + @property + def reserved_0(self): + return self._reserved_0 + + @property + def reserverd_1(self): + return self._reserved_1 + + @property + def freq_not_time(self): + return self._freq_not_time + + @property + def data(self): + return self._data + + def __init__(self,ut=0,pktnum=0,did=0,ifid=0,ud0=0,ud1=0,res0=0,res1=0,fnt=False,data=None): + """ + Initialize Packet with the given attributes + """ + # assign attributes + self._unix_time = ut + self._pkt_in_batch = pktnum + self._digital_id = did + self._if_id = ifid + self._user_data_0 = ud0 + self._user_data_1 = ud1 + self._reserved_0 = res0 + self._reserved_1 = res1 + self._freq_not_time = fnt + self._data = data + + def interpret_data(self): + """ + Interprets the raw 8bit data as complex-valued Fix_8_7. + + Returns + ------- + x : ndarray + Complex-valued array represented by the data. + """ + x = array(self.data[1::2] + 1j*self.data[0::2],dtype=complex64) + return x + + @classmethod + def FromByteString(cls,bytestr): + """ + Initialize Packet from the given byte string + """ + # check correct size packet + len_bytes = len(bytestr) + if not len_bytes == cls.BYTES_IN_PACKET: + raise ValueError("Packet should comprise {0} bytes, but has {1} bytes".format(len_bytes,cls.BYTES_IN_PACKET)) + # unpack header + hdr = unpack(">{0}Q".format(cls.BYTES_IN_HEADER//8),bytestr[:cls.BYTES_IN_HEADER]) + ut = uint32(hdr[0] & 0xFFFFFFFF) + pktnum = uint32((hdr[0]>>uint32(32)) & 0xFFFFF) + did = uint8(hdr[0]>>uint32(52) & 0x3F) + ifid = uint8(hdr[0]>>uint32(58) & 0x3F) + ud1 = uint32(hdr[1] & 0xFFFFFFFF) + ud0 = uint32((hdr[1]>>uint32(32)) & 0xFFFFFFFF) + res0 = uint64(hdr[2]) + res1 = uint64(hdr[3]&0x7FFFFFFFFFFFFFFF) + fnt = not (hdr[3]&0x8000000000000000 == 0) + # unpack data in 64bit mode to correct for byte-order + data_64bit = array(unpack(">{0}Q".format(cls.BYTES_IN_PAYLOAD//8),bytestr[cls.BYTES_IN_HEADER:]),dtype=uint64) + data = zeros(cls.BYTES_IN_PAYLOAD,dtype=int8) + for ii in xrange(len(data_64bit)): + for jj in xrange(8): + data[ii*8+jj] = int8((data_64bit[ii]>>uint64(8*jj))&uint64(0xFF)) + return Packet(ut,pktnum,did,ifid,ud0,ud1,res0,res1,fnt,data) + +class ArtooDaq(object): + """ + Encapsulate R2DAQ + """ + + _TIMEOUT = 5 + + _FPGA_CLOCK = 200e6 + _DEMUX = 16 + _ADC_SAMPLE_RATE = _FPGA_CLOCK * _DEMUX + _DIGITAL_CHANNEL_WIDTH = 100e6 + _DIGITAL_CHANNELS = ['a','b','c','d','e','f'] + _FFT_ENGINES = ['ab','cd','ef'] + _PHASE_LOOKUP_DEPTH = 10 + + @property + def FPGA_CLOCK(self): + return self._FPGA_CLOCK + + @property + def DEMUX(self): + return self._DEMUX + + @property + def ADC_SAMPLE_RATE(self): + return self._ADC_SAMPLE_RATE + + @property + def DIGITAL_CHANNEL_WIDTH(self): + return self._DIGITAL_CHANNEL_WIDTH + + @property + def DIGITAL_CHANNELS(self): + return self._DIGITAL_CHANNELS + + @property + def FFT_ENGINES(self): + return self._FFT_ENGINES + + @property + def PHASE_LOOKUP_DEPTH(self): + return self._PHASE_LOOKUP_DEPTH + + @property + def implemented_digital_channels(self): + return self._implemented_digital_channels + + @property + def registers(self): + registers = dict() + for k in self.roach2.listdev(): + registers[k] = self.roach2.read_int(k) + return registers + + @property + def roach2(self): + return self._roach2 + + @property + def version(self): + reg = self.registers + return (reg['rcs_lib'],reg['rcs_app'],reg['rcs_user']) + + def string_version(self,ver=None): + """ + Return human-readable detailed bitcode version information. + + Parameters + ---------- + ver : tuple + The version tuple to interpret, as returned by the + ArtooDaq.version property. If None, then the tuple is first + obtained from the current ArtooDaq instance. Default is None. + + Returns + ------- + str : str + A nice display of version information. + """ + def _app_or_lib_to_str(v): + b31_format = ('revision system','timestamp') + _FORMAT_REVISION = 0 + _FORMAT_TIMESTAMP = 1 + + b30_rtype = ('git','svn') + _RTYPE_GIT = 0 + _RTYPE_SVN = 1 + + b28_dirty = ('all changes in revision control','changes not in revision control') + _DIRTY_SAVED = 0 + _DRITY_UNSAVED = 1 + + str_out = '' + v_format = (v & 0x80000000) >> 31 + str_out = ' Format: {0}'.format(b31_format[v_format]) + if v_format == _FORMAT_REVISION: + v_rtype = (v & 0x40000000) >> 30 + str_out = '\n'.join([str_out,' Type: {0}'.format( + b30_rtype[v_rtype] + )]) + v_dirty = (v & 0x10000000) >> 28 + str_out = '\n'.join([str_out,' Dirty: {0}'.format( + b28_dirty[v_dirty] + )]) + if v_rtype == _RTYPE_GIT: + v_hash = (v & 0x0FFFFFFF) + str_out = '\n'.join([str_out,' Hash: {0:07x}'.format( + v_hash + )]) + else: + v_rev = (v & 0x0FFFFFFF) + str_out = '\n'.join([str_out,' Rev: {0:9d}'.format( + v_rev + )]) + else: + v_timestamp = (v & 0x3FFFFFFF) + str_out = '\n'.join([str_out,' Time: {0}'.format( + datetime.fromtimestamp(float(v_timestamp)).strftime("%F %T") + )]) + return str_out + + if ver is None: + ver = self.version + vl,va,vu = ver + str_out = 'CASPER Library' + str_out = '\n'.join([str_out,'==============']) + str_out = '\n'.join([str_out,_app_or_lib_to_str(vl)]) + str_out = '\n'.join([str_out,'Application']) + str_out = '\n'.join([str_out,'===========']) + str_out = '\n'.join([str_out,_app_or_lib_to_str(va)]) + str_out = '\n'.join([str_out,'User']) + str_out = '\n'.join([str_out,'====']) + str_out = '\n'.join([str_out,' Version: {0:10d}'.format(vu)]) + return str_out + + @classmethod + def make_interface_config_dictionary(cls, + src_ip,src_port, + dest_ip,dest_port, + src_mac=None, + arp=None, + dest_mac=None, + tag='a', + ): + """ + Make interface dictionary to initialize 10GbE cores. + + This method is a temporary workaround to avoid calling + _config_channel_net directly. + + Parameters + ---------- + src_ip : str or int + IP address for the ROACH2 10GbE interface associated with + the given channel in dot-decimal notation (str) or as int. + src_port : int + Port number for the ROACH2 10GbE interface associated with + the given channel. + dest_ip : str or int + IP address for the destination of packets associated with + the given channel in dot-decimal notation (str) or as int. + dest_port : int + Port number for the destination of packets associated with + the given channel. + src_mac : str or int + MAC address for the ROACH2 10GbE interface associated with + the given channel in colon-separated-hexadecimal notation + (str) or as int. If None then the MAC is automatically + determined from src_ip. Default is None. + arp : list + ARP table supplied as a 256-element list of integers. Each + entry contains the MAC address of the device on the /24 + network. If None then the MAC address of the device + associated with dest_ip must be supplied in dest_mac and + the ARP is automatically generated by filling other entries + with the broadcast address. Default is None. + dest_mac : str or int + MAC address for the destination of packets associated with + the given channel in colon-separated-hexadecimal notation + (str) or as int. If supplied then the ARP table entry for + src_ip is replaced with the given MAC address. If None then + the ARP table is used as-is. Default is None. + tag : str + Tag associated with the digital channel, should be one of + {'a','b','c','d','e','f'}. Default is 'a'. + + Return + ------ + ifd : dict + The returned dictionary can be used in a configuration list + when initializing the ArtooDaq object. + + Example + ------- + cfg_a = ArtooDaq.make_interface_config_dictionary( + '192.168.10.100',4000, + '192.168.10.63',4001, + dest_mac='00:60:dd:44:91:e7',tag='a' + ) + cfg_b = ArtooDaq.make_interface_config_dictionary( + '192.168.10.101',4000, + '192.168.10.64',4001, + dest_mac='00:60:dd:44:91:e8',tag='b' + ) + cfg_list = [cfg_a,cfg_b] + r2 = ArtooDaq('led',boffile='latest-build',ifcfg=cfg_list): + """ + + return { + 'src_ip':src_ip, + 'src_port':src_port, + 'dest_ip':dest_ip, + 'dest_port':dest_port, + 'src_mac':src_mac, + 'arp':arp, + 'dest_mac':dest_mac, + 'tag':tag + } + + def __init__(self,hostname,dsoc_desc=None,boffile=None,ifcfg=None, + do_ogp_cal=True,do_adcif_cal=True + ): + """ + Initialize an ArtooDaq object. + + Parameters + ---------- + hostname : string + Address of the roach2 on the 1GbE (control) network. + dsoc_desc : tuple + A tuple with the first element the IP address / hostname and + the second element the port where data is to be received. This + argument, if not None, is passed directly to socket.bind(); see + the documentation of that class for details. In this case a + socket is opened and bound to the given address. If None, then + the data socket is not opened. Default is None. + boffile : string + Program the device with this bitcode if not None. The special + filename 'latest-build' uses the current build of the bit-code. + Default is None. + ifcfg : list + List of dictionaries built with calls to + make_interface_config_dictionary. If this parameter is None + then a default list is created. Default is None. + do_ogp_cal : bool + If True then do ADC core calibration. Default is True. + do_adcif_cal : bool + If True then do ADC interface calibration. Default is True. + """ + # connect to roach and store local copy of FpgaClient + r2 = FpgaClient(hostname) + if not r2.wait_connected(self._TIMEOUT): + raise RuntimeError("Unable to connect to ROACH2 named '{0}'".format(hostname)) + self._roach2 = r2 + ### -------- + ### initialize temporary interface dictionary + self._tmp_iface_dict = {} + if ifcfg is None: + _cfg_a = self.make_interface_config_dictionary( + '192.168.10.100',4000, + '192.168.10.63',4001, + dest_mac='00:60:dd:44:91:e7',tag='a' + ) + _cfg_b = self.make_interface_config_dictionary( + '192.168.10.101',4000, + '192.168.10.64',4001, + dest_mac='00:60:dd:44:91:e8',tag='b' + ) + _cfg_c = self.make_interface_config_dictionary( + '192.168.10.102',4000, + '192.168.10.65',4001, + dest_mac='00:60:dd:44:91:e9',tag='c' + ) + ifcfg = [_cfg_a,_cfg_b,_cfg_c] + for _cfg in ifcfg: + self._tmp_add_iface_dict(**_cfg) + ### -------- + # program bitcode + if not boffile is None: + self._start(boffile,do_ogp_cal=do_ogp_cal, + do_adcif_cal=do_adcif_cal + ) + # initialize some data structures + self._populate_digital_channels() + self._ddc_1st = dict() + for did in self.DIGITAL_CHANNELS: + self._ddc_1st[did] = None + # if requested, open data socket + if not dsoc_desc is None: + self.open_dsoc(dsoc_desc) + + def grab_packets(self,n=1,dsoc_desc=None,close_soc=False): + """ + Grab packets using open data socket. + + Calls to this method should only be made while a data socket is + open, unless a socket descriptor is provided. See open_dsoc() for + details. + + Parameters + ---------- + n : int + Number of packets to grab, default is 1. + dsoc_desc : tuple + Socket descriptor tuple as for open_dsoc() method. If None, + a data socket should already be open. Default is None. + close_soc : boolean + Close socket after grabbing the given number of packets. + """ + if not dsoc_desc is None: + self.open_dsoc(dsoc_desc) + try: + dsoc = self._data_socket + except AttributeError: + raise RuntimeError("No open data socket. Call open_dsoc() first.") + pkts = [] + for ii in xrange(n): + data = dsoc.recv(Packet.BYTES_IN_PACKET) + pkts.append(Packet.FromByteString(data)) + if close_soc: + self.close_dsoc() + return pkts + + def open_dsoc(self,dsoc_desc): + """ + Open socket for data reception and bind. + + Parameters + ---------- + dsoc_desc: tuple + Tuple of IP address / hostname and port as passed to socket.bind(). + """ + self._data_socket = socket(AF_INET,SOCK_DGRAM) + self._data_socket.bind(dsoc_desc) + return self._data_socket + + def close_dsoc(self): + """ + Close socket used for data reception. + """ + self._data_socket.close() + + def _config_channel_net(self, + src_ip=None,src_port=None, + dest_ip=None,dest_port=None, + src_mac=None, + arp=None, + dest_mac=None, + tag='a', + ): + """ + Configure network output for digital channel. + + This method is a work-in-progress and should not be called in + its current form, except for the call in _start. Future bitcode + changes will enable this method to be called at any time to + reconfigure the network setup of a particular 10GbE interface + on the ROACH2. + + Parameters + ---------- + src_ip : str or int + IP address for the ROACH2 10GbE interface associated with + the given channel in dot-decimal notation (str) or as int. + src_port : int + Port number for the ROACH2 10GbE interface associated with + the given channel. + dest_ip : str or int + IP address for the destination of packets associated with + the given channel in dot-decimal notation (str) or as int. + dest_port : int + Port number for the destination of packets associated with + the given channel. + src_mac : str or int + MAC address for the ROACH2 10GbE interface associated with + the given channel in colon-separated-hexadecimal notation + (str) or as int. If None then the MAC is automatically + determined from src_ip. Default is None. + arp : list + ARP table supplied as a 256-element list of integers. Each + entry contains the MAC address of the device on the /24 + network. If None then the MAC address of the device + associated with dest_ip must be supplied in dest_mac and + the ARP is automatically generated by filling other entries + with the broadcast address. Default is None. + dest_mac : str or int + MAC address for the destination of packets associated with + the given channel in colon-separated-hexadecimal notation + (str) or as int. If supplied then the ARP table entry for + src_ip is replaced with the given MAC address. If None then + the ARP table is used as-is. Default is None. + tag : str + Tag associated with the digital channel, should be one of + {'a','b','c','d','e','f'}. Default is 'a'. + + Notes + ----- + Only one of arp and dest_mac is allowed to be None and if both + variables are None a ValueError is raised. If dest_mac is not + None then it determines the ARP table entry associated with + dest_ip, possibly overriding the table supplied in arp. + """ + + # retrieve IP value from parameter + def _get_ip_val(ip): + ip_str = str(ip) + return unpack("!L",inet_aton(ip_str))[0] + + # retrieve MAC value from parameter + def _get_mac_val(mac): + mac_str = str(dest_mac) + if not mac_str.isdigit(): + if re.match("[0-9a-f]{2}(:[0-9a-f]{2}){5}$", mac_str.lower()) is None: + raise ValueError("Invalid MAC address format '{0}'".format(mac)) + mac_val = int(mac_str.replace(':', ''),16) + else: + mac_val = int(mac_str) + return mac_val + + self._check_valid_digital_channel(tag) + # check if valid arp / dest_mac combo given + if arp is None and dest_mac is None: + raise ValueError("One of arp or dest_mac must be supplied, both cannot be None.") + + src_ip_int = _get_ip_val(src_ip) + dest_ip_int = _get_ip_val(dest_ip) + # set 10GbE interface MAC + if src_mac is not None: + src_mac_int = _get_mac_val(src_mac) + else: + src_mac_int = (2<<40) + (2<<32) + src_ip_int + # configure ARP table + if arp is None: + arp = [0xffffffffffff] * 256 + if dest_mac is not None: + dest_mac_int = _get_mac_val(dest_mac) + arp[dest_ip_int & 0x000000FF] = dest_mac_int + + ### ------- + ### This breaks the network interface, will have to change bitcode + #~ # disable data interface for this channel + #~ self._tengbe_reset_hi(tag=tag) + ### ------- + # configure core + self.roach2.config_10gbe_core('tengbe_{0}_core'.format(tag), + src_mac_int,src_ip_int,src_port,arp + ) + # set registers + self.roach2.write_int('tengbe_{0}_ip'.format(tag),dest_ip_int) + self.roach2.write_int('tengbe_{0}_port'.format(tag),dest_port) + ### ------- + ### This breaks the network interface, will have to change bitcode + #~ # and release reset + #~ self._tengbe_reset_lo(tag=tag) + ### ------- + + def tune_ddc_1st_to_freq(self,f_c,tag='a'): + """ + Tune 1st stage DDC to the given center frequency. + + Parameters + ---------- + fc : float + Desired center frequency for the 100 MHz channel. + tag : string + Tag associated with the digital channel, should be one of + {'a','b','c','d','e','f'}. Default is 'a'. + + Returns + ------- + fc_d : float + Actual center frequency of the 100MHz digital channel, + taking into account the finite frequency resolution of the + digitally synthesized LO in the first downconversion stage. + """ + self._check_valid_digital_channel(tag) + # position the digital channel at f_c + if f_c < self.ADC_SAMPLE_RATE/4: + ch = ceil((f_c-self.DIGITAL_CHANNEL_WIDTH)/self.DIGITAL_CHANNEL_WIDTH) + 2 + if not ((ch % 2) == 1): + ch = ch+1 + else: + ch = floor((f_c-self.DIGITAL_CHANNEL_WIDTH)/self.DIGITAL_CHANNEL_WIDTH) - 2 + if not ((ch % 2) == 1): + ch = ch-1 + f_ch = self.DIGITAL_CHANNEL_WIDTH*1.5 + ch*self.DIGITAL_CHANNEL_WIDTH + f_lo = f_ch - f_c; + if f_lo < 0: + f_lo = f_lo + self.ADC_SAMPLE_RATE + # set LO parameters + assign_ddc1 = self._build_synth_assignments(f_lo,tag=tag) + # design anti-aliasing FIR filter to match the channel + B = self._built_in_filter_design(f_ch) + assign_ddc1.update(self._build_filterbank_assignments(B,tag=tag)) + # set registers + self._make_assignment(assign_ddc1) + # update local config + self._ddc_1st[tag] = { + 'analog':{ + 'f_c':f_c, + 'f_ch':f_ch, + 'f_lo':f_lo if f_lo < self.ADC_SAMPLE_RATE/2 else -self.ADC_SAMPLE_RATE+f_lo, + 'B':B + } + } + cfg = self.read_ddc_1st_config(tag=tag) + return cfg['digital']['f_c'] + + def read_ddc_1st_config(self,tag='a'): + """ + Extract configuration of 1st stage DDC. + + Parameters + ---------- + tag : string + Tag associated with the digital channel, should be one of + {'a','b','c','d','e','f'}. Default is 'a'. + + Returns + ------- + ddc1_cfg : dict + Dictionary containing configuration information of 1st stage + DDC. + """ + self._check_valid_digital_channel(tag) + cfg = deepcopy(self._ddc_1st[tag]) + d_lo,d_phi0,d_dphi_demux,d_dphi = self._extract_synth_assignments(tag) + d_B = self._extract_filterbank_assignments(tag) + if cfg is None: + # precise 'analog' (or 'ideal') values are not available, so + # we determine the filter response magnitude maximum to the + # nearest 50MHz (using actual filter coefficients set in the + # roach2 registers), subtract from it the digital LO frequency + # (also using the actual LO setting in the roach2 register) + # and assign that as the center frequency. + w,h = freqz(d_B,[1],1024) + f_ch_est0 = w[abs(h).argmax()]/(2*pi) * self.ADC_SAMPLE_RATE + f_ch_est1 = round((f_ch_est0+50e6)/100e6) * 100e6 - 50e6 + cfg = { + 'analog':{ + 'f_c':-1, + 'f_ch':f_ch_est1, + 'f_lo':-1, + 'B':-1*ones(128) + } + } + cfg['digital'] = { + 'f_c': cfg['analog']['f_ch'] - d_lo, + 'f_ch': cfg['analog']['f_ch'], + 'f_lo': d_lo, + 'B': d_B + } + return cfg + + def set_gain(self,g=1.0,tag='a'): + """ + Set gain on output of 1st stage DDC. + + Parameters + ---------- + g : float + Real-valued gain to apply to output of first downconversion + module. Binary representation is Fix_8_4 and the caller should + ensure that the gain is within the allowable range [-8,7.9375]. + Values outside this range will saturate. Default is 1.0. + tag : string + Tag associated with the digital channel, should be one of + {'a','b','c','d','e','f'}. Default is 'a'. + + Notes + ----- + No gain control register for channels {'e','f'} yet. + """ + self._check_valid_digital_channel(tag) + idx = self.DIGITAL_CHANNELS.index(tag) + if idx > 3: + raise Warning("Gain control registers for channels 'e' and 'f' not implemented yet") + return + g_8bit = int8(g*16) + if g_8bit > 127: + g_8bit = 127 + if g_8bit < -128: + g_8bit = -128 + regname = 'gain_ctrl' + masked_val = self.registers[regname] & uint32(~(0xFF<= otol): + logger.debug(" ...offset: solution not good enough, iterating (tol={0:4.4f},iter={1:d})".format(otol,oiter)) + for ii in xrange(0,oiter): + for ic in xrange(1,5): + if mx[ic-1] > otol: + adc5g.set_spi_offset(self.roach2,zdok,ic,core_offsets[ic-1]-res_offset) + elif mx[ic-1] < -otol: + adc5g.set_spi_offset(self.roach2,zdok,ic,core_offsets[ic-1]+res_offset) + core_offsets[ic-1] = adc5g.get_spi_offset(self.roach2,zdok,ic) + x = self._snap_per_core(zdok=zdok,groups=groups) + sx = x.std(axis=0) + mx = x.mean(axis=0)/sx + logger.debug(" ...offset: solution offsets are [{0}] mV, means are [{1}]".format( + ", ".join(["{0:+6.2f}".format(ico) for ico in core_offsets]), + ", ".join(["{0:+7.4f}".format(imx) for imx in mx]) + )) + if all(abs(mx) < otol): + logger.debug(" ...offset: solution good enough") + break + if ii==oiter-1: + logger.critical(" ...offset: maximum number of iterations reached, aborting") + core_offsets = None + else: + logger.debug(" ...offset: solution good enough") + return core_offsets + + def calibrate_adc_gain(self,zdok=0,giter=10,gtol=0.005): + """ + Attempt to match the core gains within the ADC. + + See ArtooDaq.calibrate_adc_ogp for more details. + """ + # gain controlled by float varying over [-18%,18%] with 0.14% resolution + res_gain = 0.14 + lim_gain = [-18.0,18.0] + groups = 8 + test_step = 10*res_gain + logger.info(" Gain calibration ZDOK{0}:".format(zdok)) + for ic in xrange(1,5): + adc5g.set_spi_gain(self.roach2,zdok,ic,0) + x1 = self._snap_per_core(zdok=zdok,groups=groups) + sx1 = x1.std(axis=0) + s0 = sx1[0] + sx1 = sx1/s0 + logger.debug(" ...gain: with zero-offsets, stds are [{0}]".format( + ", ".join(["{0:+7.4f}".format(isx) for isx in sx1]) + )) + # only adjust gains for last three cores, core1 is the reference + for ic in xrange(2,5): + adc5g.set_spi_gain(self.roach2,zdok,ic,test_step) + x2 = self._snap_per_core(zdok=zdok,groups=groups) + sx2 = x2.std(axis=0) + s0 = sx2[0] + sx2 = sx2/s0 + logger.debug(" ...gain: with {0:+6.2f}% gain, stds are [{1}]".format( + test_step, + ", ".join(["{0:+7.4f}".format(isx) for isx in sx2]) + )) + d_sx = 100*(sx2 - sx1)/test_step + # give differential for core1 a non-zero value, it won't be used anyway + d_sx[0] = 1.0 + # gains are in units percentage + core_gains = 100*(1.0-sx1)/d_sx + # set core1 gain to zero + core_gains[0] = 0 + for ic in xrange(2,5): + adc5g.set_spi_gain(self.roach2,zdok,ic,core_gains[ic-1]) + core_gains[ic-1] = adc5g.get_spi_gain(self.roach2,zdok,ic) + x = self._snap_per_core(zdok=zdok,groups=groups) + sx = x.std(axis=0) + s0 = sx[0] + sx = sx/s0 + logger.debug(" ...gain: solution gains are [{0}]%, stds are [{1}]".format( + ", ".join(["{0:+6.2f}".format(ico) for ico in core_gains]), + ", ".join(["{0:+7.4f}".format(isx) for isx in sx]) + )) + if any(abs(1.0-sx) >= gtol): + logger.debug(" ...gain: solution not good enough, iterating (tol={0:4.4f},iter={1:d})".format(gtol,giter)) + for ii in xrange(0,giter): + for ic in xrange(2,5): + if (1.0-sx[ic-1]) > gtol: + adc5g.set_spi_gain(self.roach2,zdok,ic,core_gains[ic-1]+res_gain) + elif (1.0-sx[ic-1]) < -gtol: + adc5g.set_spi_gain(self.roach2,zdok,ic,core_gains[ic-1]-res_gain) + core_gains[ic-1] = adc5g.get_spi_gain(self.roach2,zdok,ic) + x = self._snap_per_core(zdok=zdok,groups=groups) + sx = x.std(axis=0) + s0 = sx[0] + sx = sx/s0 + logger.debug(" ...gain: solution gains are [{0}]%, stds are [{1}]".format( + ", ".join(["{0:+6.2f}".format(ico) for ico in core_gains]), + ", ".join(["{0:+7.4f}".format(isx) for isx in sx]) + )) + if all(abs(1.0-sx) < gtol): + logger.debug(" ...gain: solution good enough") + break + if ii==giter-1: + logger.critical(" ...gain: maximum number of iterations reached, aborting") + core_gains = None + else: + logger.debug(" ...gain: solution good enough") + return core_gains + + def calibrate_adc_phase(self,zdok=0,piter=0,ptol=1.0): + """ + Attempt to match the core phases within the ADC. + + See ArtooDaq.calibrate_adc_ogp for more details. + """ + # phase controlled by float varying over [-14,14] ps with 0.11 ps resolution + res_phase = 0.11 + lim_phase = [-14.0,14.0] + logger.info(" Phase calibration ZDOK{0}:".format(zdok)) + core_phases = zeros(4) + for ic in xrange(1,5): + core_phases[ic-1] = adc5g.get_spi_phase(self.roach2,zdok,ic) + logger.info(" ...phase: tuning not implemented yet, phase parameters are [{0}]".format( + ", ".join(["{0:+06.2f}".format(icp) for icp in core_phases]) + )) + return core_phases + + def _snap_per_core(self,zdok=0,groups=1): + """ + Get a snapshot of 8-bit data per core from the ADC. + + Parameters + ---------- + zdok : int + ID of the ZDOK slot, either 0 or 1 (default is 0) + groups : int + Each snapshot grabs groups*(2**16) samples per core + (default is 1). + + Returns + ------- + x : ndarray + A (groups*(2**16), 4)-shaped array in which data along the + first dimension contains consecutive samples taken form the + same core. The data is ordered such that the index along + the second dimension matches core-indexing in the spi- + family of functions used to tune the core parameters. + """ + x = zeros((0,4)) + for ig in xrange(groups): + grab = self.roach2.snapshot_get('snap_{0}_snapshot'.format(zdok)) + x_ = array(unpack('%ib' %grab['length'], grab['data'])) + x_ = x_.reshape((x_.size//4,4)) + x = concatenate((x,x_)) + return x[:,[0,2,1,3]] + + def _make_assignment(self,assign_dict): + """ + Assign values to ROACH2 software registers. + + Assignments are made as roach2.write_int(key,val). + + Parameters + ---------- + assign_dict : dict + Each key in assign_dict should correspond to a valid ROACH2 + software register name, and each val should be int compatible. + """ + for key in assign_dict.keys(): + val = assign_dict[key] + self.roach2.write_int(key,val) + + def _built_in_filter_design(self,f_ch): + """ + Design basic shape-matching 127th order FIR filter. + + The filter will attempt to match the following gain envelope: + w = [0, s1, p1, p2, s1, 1] + h = [0, 0, 1, 1, 0, 0] + where + s1 = f_ch - DIGITAL_CHANNEL_WIDTH*0.6 + p1 = f_ch - DIGITAL_CHANNEL_WIDTH*0.4 + p2 = f_ch + DIGITAL_CHANNEL_WIDTH*0.4 + s2 = f_ch + DIGITAL_CHANNEL_WIDTH*0.6, + h is normalized to a maximum of 1, and w is the angular frequency + normalized to pi. + + Parameters + ---------- + f_ch : float + Center frequency of bandpass filter. + + Returns + ------- + B : ndarray + FIR filter coefficients. + """ + # filter channel should be at least more than digital bandwidth from sampled boundaries + f_lower = self.DIGITAL_CHANNEL_WIDTH + f_upper = self.ADC_SAMPLE_RATE/2-self.DIGITAL_CHANNEL_WIDTH + if f_ch <= f_lower or f_ch >= f_upper: + raise RuntimeError("Digital channel center frequency is {0:7.3f}MHz, but should be within ({1:7.3f},{2:7.3f}) MHz".format(f_ch/1e6,f_lower/1e6,f_upper/1e6)) + # construct envelope + f_pass = f_ch + array([-1,1])*self.DIGITAL_CHANNEL_WIDTH*0.4 + f_stop = f_ch + array([-1,1])*self.DIGITAL_CHANNEL_WIDTH*0.6 + w_pass = f_pass/(self.ADC_SAMPLE_RATE/2) + w_stop = f_stop/(self.ADC_SAMPLE_RATE/2) + filt_gain = array([0,0,1,1,0,0]) + filt_freq = concatenate(([0],[w_stop[0]], w_pass, [w_stop[1]], [1.0])) + B = firwin2(128,filt_freq,filt_gain,window='boxcar') + # normalize to absolute maximum of 0.5 + B = 0.5*B/(abs(B).max()) + return B + + def _build_synth_assignments(self,f_lo,phase_offset=0,tag='a'): + """ + Build phase increment words for synthesizer. + + Parameters + ---------- + f_lo : float + LO frequency. + phase_offset : float + LO phase offset in radians. + tag : string + Tag associated with the downconverter, should be one of + {'a','b','c','d','e','f'}. Default is 'a'. + + Returns + ------- + assign_synth : dict + Each (key,val) pair can be used to assign devices as in + roach2.write_int(key,val) + """ + # calculate discretized parameter values + phase_res = 2**self.PHASE_LOOKUP_DEPTH + mask = phase_res-1 + dphi = uint32(f_lo/self.ADC_SAMPLE_RATE*phase_res) + dphi_demux = uint32((dphi*self.DEMUX) % phase_res) + phi0 = uint32(phase_offset*phase_res/(2*pi)) + # build single 30bit value + word = ((dphi_demux&mask)<<(self.PHASE_LOOKUP_DEPTH*2)) | ((dphi&mask)<>(self.PHASE_LOOKUP_DEPTH*2))&mask) / phase_res * 2*pi + dphi = float32((word>>self.PHASE_LOOKUP_DEPTH)&mask) / phase_res * 2*pi + phi0 = float32(word&mask) / phase_res * 2*pi + f_lo = self.ADC_SAMPLE_RATE * dphi/(2*pi) + if f_lo >= self.ADC_SAMPLE_RATE/2: + f_lo = -self.ADC_SAMPLE_RATE + f_lo + return f_lo, phi0, dphi_demux, dphi + + def _build_filterbank_assignments(self,B,tag='a'): + """ + Build FIR filter words for anti-aliasing filter. + + Parameters + ---------- + B : ndarray + Filter coefficients for 127th order filter, or 128 coefficients + in total. These values are discretized as Fix_8_7, or 8-bit + fixed-point with a single integer bit. The range of values that + can be represented is [-1.0,0.9921875] and it is up to the + caller to ensure that the values of all filter coefficients + are properly distributed. + tag : string + Tag associated with the downconverter, should be one of + {'a','b','c','d','e','f'}. Default is 'a'. + + Returns + ------- + assign_filter : dict + Each (key,val) pair can be used to assign devices as in + roach2.write_int(key,val) + """ + s = True #signed + w = 8# word length + f = 7# fraction length + groupsize = 4#number of coefficients per 32bit word + shift_factor = uint32(2**w) + mask = uint32(shift_factor-1) + # scale B to absolute maximum of 128 + B = B[:128]*128 + N = len(B); + if not N ==128: + raise RuntimeError("There should be 128 filter coefficients for 1st stage DDC, but {0} received".format(N)) + N_w = N//groupsize; + words = zeros(N_w,uint32) + for bb in xrange(N): + gg = bb % groupsize + nn = int(floor(bb/groupsize)) + b_uint32 = uint32(B[bb]) + words[nn] = words[nn] | ((b_uint32&mask)<<(w*gg)) + # setup assignment dictionary + assign_filter = {} + base = 'ddc_1st_' + tag + for cb_id in xrange(8): + this_cb = base + '_cb{0}'.format(cb_id) + for g_id in xrange(4): + this_key = this_cb + '_g{0}'.format(g_id) + nn = 4*cb_id + g_id + this_val = words[nn] + assign_filter[this_key] = this_val + return assign_filter + + def _extract_filterbank_assignments(self,tag='a'): + """ + Extract FIR filter coefficients from the contents of the control registers. + + Parameters + ---------- + tag : string + Tag associated with the downconverter, should be one of + {'a','b','c','d','e','f'}. Default is 'a'. + + Returns + ------- + B : ndarray + FIR filter coefficients. + """ + s = True #signed + w = 8# word length + f = 7# fraction length + groupsize = 4#number of coefficients per 32bit word + shift_factor = uint32(2**w) + mask = uint32(shift_factor-1) + N = 128 + N_w = N//groupsize + # read uint32 words + words = zeros(N_w,uint32) + base = 'ddc_1st_' + tag + for cb_id in xrange(8): + this_cb = base + '_cb{0}'.format(cb_id) + for g_id in xrange(4): + this_key = this_cb + '_g{0}'.format(g_id) + nn = 4*cb_id + g_id + words[nn] = self.roach2.read_int(this_key) + # decompose into filter coefficients + B = zeros(N,float32) + for bb in xrange(N): + gg = bb % groupsize + nn = int(floor(bb/groupsize)) + b_int8 = ((words[nn] & (mask<<(w*gg)))>>(w*gg)) + if b_int8 > 127: # fix negative values + b_int8 = b_int8 - (1< 1: + raise Warning("FFT engine 'ef' not yet implemented in bitcode") + + def _master_reset_hi(self): + """ + Pull master reset signal up. + """ + master_ctrl = self.roach2.read_int('master_ctrl') + self.roach2.write_int('master_ctrl',master_ctrl|0x00000001) + + def _master_reset_lo(self): + """ + Pull master reset signal down. + """ + master_ctrl = self.roach2.read_int('master_ctrl') + self.roach2.write_int('master_ctrl',master_ctrl&0xFFFFFFFE) + + def _manual_sync_hi(self): + """ + Pull manual sync signal high. + """ + master_ctrl = self.roach2.read_int('master_ctrl') + self.roach2.write_int('master_ctrl',master_ctrl|0x00000002) + + def _manual_sync_lo(self): + """ + Pull manual sync signal low. + """ + master_ctrl = self.roach2.read_int('master_ctrl') + self.roach2.write_int('master_ctrl',master_ctrl&0xFFFFFFFD) + + def _tengbe_reset_hi(self,tag='a'): + """ + Pull tengbe reset signal high for given tag. + + Parameters + ---------- + tag : string + FFT engine tag. Valid tags are any of {'ab','cd','ef'}. + + """ + self._check_valid_digital_channel(tag) + dev = 'tengbe_{0}_ctrl'.format(tag) + ctrl = self.roach2.read_int(dev) + self.roach2.write_int(dev,ctrl|0x00000001) + + def _tengbe_reset_lo(self,tag='a'): + """ + Pull tengbe reset signal low for given tag. + + Parameters + ---------- + tag : string + FFT engine tag. Valid tags are any of {'ab','cd','ef'}. + + """ + self._check_valid_digital_channel(tag) + dev = 'tengbe_{0}_ctrl'.format(tag) + ctrl = self.roach2.read_int(dev) + self.roach2.write_int(dev,ctrl&0xFFFFFFFE) + + def _load_time(self): + """ + Load current time reference into ROACH2. + """ + # set time, wait until just before a second boundary + while(abs(datetime.utcnow().microsecond-9e5)>1e3): + sleep(0.001) + # when the system starts running it will be the next second + ut0 = int(time())+1 + self.roach2.write_int('unix_time0',ut0) + + def _tmp_add_iface_dict(self, + src_ip=None,src_port=None, + dest_ip=None,dest_port=None, + src_mac=None, + arp=None, + dest_mac=None, + tag='a', + ): + """ + Make network interface dictionary for configuring network at startup. + + Parameters are the same as for _config_channel_net. This method + is a temporary solution to ease construction of the dictionary + that should be passed to _start to configure the network channels. + + Future implementations of config_channel_net will have the same + interface as this method. + """ + + self._tmp_iface_dict[tag] = { + 'src_ip':src_ip, + 'src_port':src_port, + 'dest_ip':dest_ip, + 'dest_port':dest_port, + 'src_mac':src_mac, + 'arp':arp, + 'dest_mac':dest_mac, + 'tag':tag + } + + def _start(self,boffile='latest-build',do_ogp_cal=True,do_adcif_cal=True): + """ + Program bitcode on device. + + Parameters + ---------- + boffile : string + Filename of the bitcode to program. If 'latest-build' then + use the current build. Default is 'latest-build'. + do_ogp_cal : bool + If True then do ADC core calibration. Default is True. + do_adcif_cal : bool + If True then do ADC interface calibration. Default is True. + + Returns + ------- + """ + + if boffile == "latest-build": + boffile = "r2daq_2016_May_18_1148.bof" + + # program bitcode + self.roach2.progdev(boffile) + self.roach2.wait_connected() + logger.info("Bitcode '{0}' programmed successfully".format(boffile)) + + # display clock speed + logger.debug("Board clock is {0} MHz".format(self.roach2.est_brd_clk())) + + # ADC interface calibration + if do_adcif_cal: + logger.info("Performing ADC interface calibration... (only doing ZDOK0)") + adc5g.set_test_mode(self.roach2, 0) + #~ adc5g.set_test_mode(self.roach2, 1) #<<---- ZDOK1 not yet in bitcode + adc5g.sync_adc(self.roach2) + opt0, glitches0 = adc5g.calibrate_mmcm_phase(self.roach2, 0, ['snap_0_snapshot',]) + #~ opt1, glitches1 = adc5g.calibrate_mmcm_phase(self.roach2, 1, ['zdok_1_snap_data',]) #<<---- ZDOK1 not yet in bitcode + adc5g.unset_test_mode(self.roach2, 0) + #~ adc5g.unset_test_mode(self.roach2, 1) #<<---- ZDOK1 not yet in bitcode + logger.info("...ADC interface calibration done.") + logger.debug("if0: opt0 = {0}\nglitches0 = {1}\n".format(opt0,array(glitches0))) + #~ logger.debug("if0: opt0 = ",opt0, ", glitches0 = \n", array(glitches0)) #<<---- ZDOK1 not yet in bitcode + + # ADC core calibration + if do_ogp_cal: + self.calibrate_adc_ogp(zdok=0) + + # keep system in reset + self._master_reset_hi() + self._manual_sync_hi() + # hold master reset signal and arm the manual sync + #~ self.roach2.write_int('master_ctrl',0x00000001 | 0x00000002) + #~ master_ctrl = self.roach2.read_int('master_ctrl') + + + self._load_time() + + # release reset + self._master_reset_lo() + self._manual_sync_lo() + #~ # release master reset signal + #~ master_ctrl = self.roach2.read_int('master_ctrl') + #~ master_ctrl = master_ctrl & 0xFFFFFFFC + #~ self.roach2.write_int('master_ctrl',master_ctrl) + + logger.info("Configuration done, system should be running") From a923c9ee61693f0fb7a4fa502c4629055f2ab896 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Wed, 24 Jan 2024 15:37:32 -0800 Subject: [PATCH 02/55] Copied another file, renamed folder. --- dripline/extensions/{daq => cca_daq}/r2daq.py | 0 .../extensions/cca_daq/roach2_interface.py | 387 ++++++++++++++++++ 2 files changed, 387 insertions(+) rename dripline/extensions/{daq => cca_daq}/r2daq.py (100%) create mode 100644 dripline/extensions/cca_daq/roach2_interface.py diff --git a/dripline/extensions/daq/r2daq.py b/dripline/extensions/cca_daq/r2daq.py similarity index 100% rename from dripline/extensions/daq/r2daq.py rename to dripline/extensions/cca_daq/r2daq.py diff --git a/dripline/extensions/cca_daq/roach2_interface.py b/dripline/extensions/cca_daq/roach2_interface.py new file mode 100644 index 00000000..2a623ce3 --- /dev/null +++ b/dripline/extensions/cca_daq/roach2_interface.py @@ -0,0 +1,387 @@ +# -*- coding: utf-8 -*- +""" +Interface for controlling the roach2 +""" + + +from __future__ import absolute_import + +import logging +import os +try: + import adc5g + import numpy as np +except ImportError: + pass +import json +from dripline import core + + +logger = logging.getLogger(__name__) + +#phasmid import + +try: + from .r2daq import ArtooDaq + logger.info('Imported ArtooDaq') + +except ImportError: + class ArtooDaq(object): + def __init__(self, *args, **kwargs): + raise RuntimeError("Dependency not found but is required for ROACH2 support.") + + +__all__ = [] + + + +__all__.append('Roach2Provider') +class Roach2Provider(ArtooDaq, core.Provider): + ''' + A DAQProvider for interacting with the Roach + ''' + def __init__(self, **kwargs): + + + core.Provider.__init__(self, **kwargs) + + + +__all__.append('Roach2Interface') +class Roach2Interface(Roach2Provider): + def __init__(self, + roach2_hostname = 'led', + channel_a_config = None, + channel_b_config = None, + channel_c_config = None, + + daq_name = None, + do_adc_ogp_calibration = False, + default_frequency = 800e6, + gain = 7.0, + fft_shift = '1101010101010', + **kwargs): + + + Roach2Provider.__init__(self, **kwargs) + + self.roach2_hostname = roach2_hostname + + self.channel_a_config = channel_a_config + self.channel_b_config = channel_b_config + self.channel_c_config = channel_c_config + + self.freq_dict = {'a':None, 'b':None, 'c':None} + self.block_dict = {'a': False, 'b': False, 'c':False} + self.daq_name = daq_name + self.default_frequency = default_frequency + self.gain_dict = {'a':gain, 'b':gain, 'c':gain} + self.fft_shift_vector = {'ab': fft_shift, 'cd': fft_shift} + self.configured=False + self.calibrated=False + + + + def _finish_configure(self, boffile=None, **kwargs): + self.channel_list = [] + self.cfg_list = [] + # make list with interface dictionaries + if self.channel_a_config != None: + cfg_a = self.make_interface_config_dictionary(src_ip=self.channel_a_config['source_ip'], src_port=self.channel_a_config['source_port'], src_mac=self.channel_a_config['source_mac'], + dest_ip=self.channel_a_config['dest_ip'], dest_port=self.channel_a_config['dest_port'], dest_mac=self.channel_a_config['dest_mac'], tag='a') + self.cfg_list.append(cfg_a) + self.channel_list.append('a') + + if self.channel_b_config != None: + cfg_b = self.make_interface_config_dictionary(src_ip=self.channel_b_config['source_ip'], src_port=self.channel_b_config['source_port'], src_mac=self.channel_b_config['source_mac'], + dest_ip=self.channel_b_config['dest_ip'], dest_port=self.channel_b_config['dest_port'], dest_mac=self.channel_b_config['dest_mac'], tag='b') + self.cfg_list.append(cfg_b) + self.channel_list.append('b') + + if self.channel_c_config != None: + cfg_c = self.make_interface_config_dictionary(src_ip=self.channel_c_config['source_ip'], src_port=self.channel_c_config['source_port'], src_mac=self.channel_c_config['source_mac'], + dest_ip=self.channel_c_config['dest_ip'], dest_port=self.channel_c_config['dest_port'], dest_mac=self.channel_c_config['dest_mac'], tag='c') + self.cfg_list.append(cfg_c) + self.channel_list.append('c') + + logger.info('Number of channels: {}'.format(len(self.channel_list))) + + ArtooDaq.__init__(self, self.roach2_hostname, boffile=boffile, do_ogp_cal=False, do_adcif_cal=True, ifcfg=self.cfg_list) + self.configured = True + + for channel in self.channel_list: + self.set_central_frequency(channel, self.default_frequency) + self.set_gain(channel, self.gain_dict[channel]) + + self.set_fft_shift_vector('ab', self.fft_shift_vector['ab']) + self.set_fft_shift_vector('cd', self.fft_shift_vector['cd']) + return self.configured + + @property + def calibration_status(self): + return self.calibrated + + + def do_adc_calibration(self): + logger.info('Calibrating ROACH2, this will take a while.') + logger.info('Doing adc ogp calibration') + adc_cal_values = ArtooDaq.calibrate_adc_ogp(self, oiter=5, giter=5) + logger.info('ADC calibration returned: {}'.format(adc_cal_values)) + for k in adc_cal_values.keys(): + if adc_cal_values[k] is None: + self.calibrated = False + logger.critical('ADC calibration failed') + raise core.exceptions.DriplineGenericDAQError('ADC calibration failed') + self.calibrated = True + + + @property + def is_running(self): + logger.info('Pinging ROACH2') + response = os.system("ping -c 1 " + self.roach2_hostname) + #and then check the response... + if response == 0: + logger.info('ROACH2 is switched on') + else: + self.configured=False + self.calibrated=False + + return self.configured + + + def block_channel(self, channel): + self.block_dict[channel]=True + + + def unblock_channel(self, channel): + self.block_dict[channel]=False + + + @property + def blocked_channels(self): + bc = [i for i in self.block_dict.keys() if self.block_dict[i]==True] + return bc + + + def get_central_frequency(self, channel): + return self.freq_dict[channel] + + + def set_central_frequency(self, channel, cf): + if self.block_dict[channel]==False: + if cf > 1550e6 or cf < 50e6: + logger.error('Frequency out of allowed range: 50e6 - 1550e6 Hz') + raise core.exceptions.DriplineGenericDAQError('Frequency out of allowed range') + else: + logger.info('setting central frequency of channel {} to {}'.format(channel, cf)) + cf = ArtooDaq.tune_ddc_1st_to_freq(self, cf, tag=channel) + self.freq_dict[channel]=cf + return cf + else: + logger.error('Channel {} is blocked'.format(channel)) + raise core.exceptions.DriplineGenericDAQError('Channel {} is blocked'.format(channel)) + + + @property + def all_central_frequencies(self): + return self.freq_dict + + + @property + def gain(self): + return self.gain_dict + + + def set_gain(self, channel, gain): + """ + Method to set the gain of a channel. The gain is applied after the first down conversion step. + """ + if self.block_dict[channel]==False: + if gain>-8 and gain <7.93: + logger.info('setting gain of channel {} to {}'.format(channel, gain)) + ArtooDaq.set_gain(self, gain, tag=channel) + self.gain_dict[channel] = gain + else: + raise core.exceptions.DriplineGenericDAQError('Only gains between -8 and 7.93 are allowed') + else: + raise core.exceptions.DriplineGenericDAQError('Channel {} is blocked'.format(channel)) + + + def get_fft_shift_vector(self, tag): + return self.fft_shift_vector[tag] + + + def set_fft_shift_vector(self, tag, fft_shift): + """ + Method to set the fft_shift. See set_fft_shift in r2daq for more details. + """ + + self.fft_shift_vector[tag] = fft_shift + logger.info('setting fft shift of channel {} to {}'.format(tag, fft_shift)) + ArtooDaq.set_fft_shift(self, str(fft_shift), tag=tag) + + + + @property + def roach2_clock(self): + board_clock = self.roach2.est_brd_clk() + return board_clock + + + def get_packets(self, channel='a', NPackets=1, filename=None): + if channel=='a': + dsoc_desc = (self.channel_a_config['dest_ip'],self.channel_a_config['dest_port']) + elif channel=='b': + dsoc_desc = (self.channel_b_config['dest_ip'],self.channel_b_config['dest_port']) + elif channel=='c': + dsoc_desc = (self.channel_c_config['dest_ip'],self.channel_c_config['dest_port']) + else: + raise ValueError('{} is not a valid channel tag'.format(channel)) + + logger.info('grabbing {} packets from {}'.format(NPackets,dsoc_desc)) + pkts=ArtooDaq.grab_packets(self, NPackets, dsoc_desc, True) + logger.info('pkt in batch: {}, digital id: {}, if id: {}'.format(pkts[0].pkt_in_batch, pkts[0].digital_id, pkts[0].if_id)) + p = {} + + for i in range(NPackets): + if pkts[i].freq_not_time==False: + packet_type = 'time' + else: + packet_type='frequency' + x=np.complex128(pkts[i].interpret_data()) + p[i] = {'real': list(x.real), 'imaginary': list(x.imag), 'type': packet_type, 'pkt_in_batch': int(pkts[i].pkt_in_batch)} + + if filename is not None: + with open(filename, 'w') as outfile: + json.dump(p, outfile) + else: + return p + + def get_T_packets(self, channel='a', NPackets=1, filename=None): + if channel=='a': + dsoc_desc = (self.channel_a_config['dest_ip'],self.channel_a_config['dest_port']) + elif channel=='b': + dsoc_desc = (self.channel_b_config['dest_ip'],self.channel_b_config['dest_port']) + elif channel=='c': + dsoc_desc = (self.channel_c_config['dest_ip'],self.channel_c_config['dest_port']) + else: + raise ValueError('{} is not a valid channel tag'.format(channel)) + + + logger.info('grabbing {} packets from {}'.format(NPackets*2,dsoc_desc)) + pkts=ArtooDaq.grab_packets(self, NPackets*2, dsoc_desc, True) + p = {} + ipacket = 0 + for i in range(NPackets*2): + if pkts[i].freq_not_time==False: + x=np.complex128(pkts[i].interpret_data()) + p[ipacket] = {'real': list(x.real), 'imaginary': list(x.imag)} + ipacket+=1 + + if filename is not None: + with open(filename, 'w') as outfile: + json.dump(p, outfile) + else: + return p + + + + def get_F_packets(self,dsoc_desc=None, channel='a', NPackets=10, filename = None): + if channel=='a': + dsoc_desc = (self.channel_a_config['dest_ip'],self.channel_a_config['dest_port']) + elif channel=='b': + dsoc_desc = (self.channel_b_config['dest_ip'],self.channel_b_config['dest_port']) + elif channel=='c': + dsoc_desc = (self.channel_c_config['dest_ip'],self.channel_c_config['dest_port']) + else: + raise ValueError('{} is not a valid channel tag'.format(channel)) + + + logger.info('grabbing packets from {}'.format(dsoc_desc)) + pkts=ArtooDaq.grab_packets(self, NPackets*2, dsoc_desc, True) + + p = {} + ipacket = 0 + for i in range(NPackets*2): + if pkts[i].freq_not_time==True: + f=np.complex128(pkts[i].interpret_data()) + p[ipacket] = {'real': list(f.real), 'imaginary': list(f.imag)} + ipacket+=1 + + if filename is not None: + with open(filename, 'w') as outfile: + json.dump(p, outfile) + else: + return p + + + def get_raw_adc_data(self, NSnaps = 1, filename = None): + x_all = [] + for i in range(NSnaps): + x = ArtooDaq._snap_per_core(self, zdok=0) + x_all.extend(x.flatten('C')) + logger.info('raw adc samples: {}'.format(len(x_all))) + if filename is not None: + logger.info('Saving raw adc data to {}'.format(filename)) + with open(filename, 'w') as outfile: + json.dump(map(int,x_all), outfile) + else: + return list(x_all) + + + + def calibrate_with_2016_values(self): + """ + Calibrates the ADC cores with values from a working calibration in 2016 + """ + self.calibrate_manually(gain1=0.0, gain2=0.42, gain3=0.42, gain4=1.55, offset1=3.14, offset2=-0.39, offset3=2.75, offset4=-1.18, phase1=None, phase2=None, phase3=None, phase4=None) + + + def calibrate_manually(self, gain1=None, gain2=None, gain3=None, gain4=None, offset1=None, offset2=None, offset3=None, offset4=None, phase1=None, phase2=None, phase3=None, phase4=None): + """ + Calibrate the ADC cores manually + """ + if gain1 is not None: + adc5g.set_spi_gain(self.roach2,0, 1, gain1) + if gain2 is not None: + adc5g.set_spi_gain(self.roach2,0, 2, gain2) + if gain3 is not None: + adc5g.set_spi_gain(self.roach2,0, 3, gain3) + if gain4 is not None: + adc5g.set_spi_gain(self.roach2,0, 4, gain4) + + if offset1 is not None: + adc5g.set_spi_offset(self.roach2,0, 1, offset1) + if offset2 is not None: + adc5g.set_spi_offset(self.roach2,0, 2, offset2) + if offset3 is not None: + adc5g.set_spi_offset(self.roach2,0, 3, offset3) + if offset4 is not None: + adc5g.set_spi_offset(self.roach2,0, 4, offset4) + + if phase1 is not None: + adc5g.set_spi_phase(self.roach2, 0, 1, phase1) + if phase2 is not None: + adc5g.set_spi_phase(self.roach2, 0, 2, phase2) + if phase3 is not None: + adc5g.set_spi_phase(self.roach2, 0, 3, phase3) + if phase4 is not None: + adc5g.set_spi_phase(self.roach2, 0, 4, phase4) + + + @property + def adc_calibration_values(self): + calibration_values = {} + calibration_values['gain1'] = adc5g.get_spi_gain(self.roach2, 0, 1) + calibration_values['gain2'] = adc5g.get_spi_gain(self.roach2, 0, 2) + calibration_values['gain3'] = adc5g.get_spi_gain(self.roach2, 0, 3) + calibration_values['gain4'] = adc5g.get_spi_gain(self.roach2, 0, 4) + calibration_values['offset1'] = adc5g.get_spi_offset(self.roach2, 0, 1) + calibration_values['offset2'] = adc5g.get_spi_offset(self.roach2, 0, 2) + calibration_values['offset3'] = adc5g.get_spi_offset(self.roach2, 0, 3) + calibration_values['offset4'] = adc5g.get_spi_offset(self.roach2, 0, 4) + calibration_values['phase1'] = adc5g.get_spi_phase(self.roach2, 0, 1) + calibration_values['phase2'] = adc5g.get_spi_phase(self.roach2, 0, 2) + calibration_values['phase3'] = adc5g.get_spi_phase(self.roach2, 0, 3) + calibration_values['phase4'] = adc5g.get_spi_phase(self.roach2, 0, 4) + return calibration_values From 929841084bb27439e99acf6354566ab8432bbedb Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Wed, 24 Jan 2024 15:46:36 -0800 Subject: [PATCH 03/55] Renamed folder again. Extensions should contain modules or directories with modules, but go no deeper. --- dripline/extensions/{cca_daq => roach2_daq}/r2daq.py | 0 dripline/extensions/{cca_daq => roach2_daq}/roach2_interface.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename dripline/extensions/{cca_daq => roach2_daq}/r2daq.py (100%) rename dripline/extensions/{cca_daq => roach2_daq}/roach2_interface.py (100%) diff --git a/dripline/extensions/cca_daq/r2daq.py b/dripline/extensions/roach2_daq/r2daq.py similarity index 100% rename from dripline/extensions/cca_daq/r2daq.py rename to dripline/extensions/roach2_daq/r2daq.py diff --git a/dripline/extensions/cca_daq/roach2_interface.py b/dripline/extensions/roach2_daq/roach2_interface.py similarity index 100% rename from dripline/extensions/cca_daq/roach2_interface.py rename to dripline/extensions/roach2_daq/roach2_interface.py From bbe683c66396253735a46311e997ae074af0d014 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Wed, 6 Mar 2024 15:58:28 -0800 Subject: [PATCH 04/55] Fixed jitter_example and started modifying roach2_interface for dl3 --- dripline/extensions/roach2_daq/__init__.py | 23 +++++++++++++++++++ .../extensions/roach2_daq/roach2_interface.py | 22 +++++++++--------- 2 files changed, 34 insertions(+), 11 deletions(-) create mode 100644 dripline/extensions/roach2_daq/__init__.py diff --git a/dripline/extensions/roach2_daq/__init__.py b/dripline/extensions/roach2_daq/__init__.py new file mode 100644 index 00000000..1f359c6c --- /dev/null +++ b/dripline/extensions/roach2_daq/__init__.py @@ -0,0 +1,23 @@ +__all__ = [] + +import pkg_resources + +import scarab +a_ver = '0.0.0' #note that this is updated in the following block +try: + a_ver = pkg_resources.get_distribution('dragonfly').version + print('version is: {}'.format(a_ver)) +except: + print('fail!') + pass +version = scarab.VersionSemantic() +version.parse(a_ver) +version.package = 'project8/dragonfly' +version.commit = '---' +__all__.append("version") + +from .roach2_interface import * +from .roach2_interface import __all__ as __roach2_interface_all +from .r2daq import * +__all__ += __roach2_interface_all + diff --git a/dripline/extensions/roach2_daq/roach2_interface.py b/dripline/extensions/roach2_daq/roach2_interface.py index 2a623ce3..5e360a1d 100644 --- a/dripline/extensions/roach2_daq/roach2_interface.py +++ b/dripline/extensions/roach2_daq/roach2_interface.py @@ -35,20 +35,20 @@ def __init__(self, *args, **kwargs): -__all__.append('Roach2Provider') -class Roach2Provider(ArtooDaq, core.Provider): +__all__.append('Roach2Service') +class Roach2Service(ArtooDaq, core.Service): ''' - A DAQProvider for interacting with the Roach + A DAQService for interacting with the Roach ''' def __init__(self, **kwargs): - core.Provider.__init__(self, **kwargs) + core.Service.__init__(self, **kwargs) __all__.append('Roach2Interface') -class Roach2Interface(Roach2Provider): +class Roach2Interface(Roach2Service): def __init__(self, roach2_hostname = 'led', channel_a_config = None, @@ -63,7 +63,7 @@ def __init__(self, **kwargs): - Roach2Provider.__init__(self, **kwargs) + Roach2Service.__init__(self, **kwargs) self.roach2_hostname = roach2_hostname @@ -131,7 +131,7 @@ def do_adc_calibration(self): if adc_cal_values[k] is None: self.calibrated = False logger.critical('ADC calibration failed') - raise core.exceptions.DriplineGenericDAQError('ADC calibration failed') + raise core.ThrowReply('DriplineGenericDAQError','ADC calibration failed') self.calibrated = True @@ -171,7 +171,7 @@ def set_central_frequency(self, channel, cf): if self.block_dict[channel]==False: if cf > 1550e6 or cf < 50e6: logger.error('Frequency out of allowed range: 50e6 - 1550e6 Hz') - raise core.exceptions.DriplineGenericDAQError('Frequency out of allowed range') + raise core.ThrowReply('DriplineGenericDAQError','Frequency out of allowed range') else: logger.info('setting central frequency of channel {} to {}'.format(channel, cf)) cf = ArtooDaq.tune_ddc_1st_to_freq(self, cf, tag=channel) @@ -179,7 +179,7 @@ def set_central_frequency(self, channel, cf): return cf else: logger.error('Channel {} is blocked'.format(channel)) - raise core.exceptions.DriplineGenericDAQError('Channel {} is blocked'.format(channel)) + raise core.ThrowReply('DriplineGenericDAQError','Channel {} is blocked'.format(channel)) @property @@ -202,9 +202,9 @@ def set_gain(self, channel, gain): ArtooDaq.set_gain(self, gain, tag=channel) self.gain_dict[channel] = gain else: - raise core.exceptions.DriplineGenericDAQError('Only gains between -8 and 7.93 are allowed') + raise core.ThrowReply('DriplineGenericDAQError','Only gains between -8 and 7.93 are allowed') else: - raise core.exceptions.DriplineGenericDAQError('Channel {} is blocked'.format(channel)) + raise core.ThrowReply('DriplineGenericDAQError','Channel {} is blocked'.format(channel)) def get_fft_shift_vector(self, tag): From 41c20b435779dba98f48c891183f4f9f4c72b048 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Wed, 6 Mar 2024 16:07:24 -0800 Subject: [PATCH 05/55] missed some files in last commit --- auths.json | 11 +++++++++++ dripline/extensions/jitter/__init__.py | 6 +++--- jitter_example.yaml | 9 +++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 auths.json create mode 100644 jitter_example.yaml diff --git a/auths.json b/auths.json new file mode 100644 index 00000000..0a394d9e --- /dev/null +++ b/auths.json @@ -0,0 +1,11 @@ +{ + "amqp": { + "broker": "rabbit-broker", + "username": "dripline", + "password": "dripline" + }, + "postgresql": { + "username": "postgres", + "password": "" + } +} \ No newline at end of file diff --git a/dripline/extensions/jitter/__init__.py b/dripline/extensions/jitter/__init__.py index 0def8fb8..ab3554f4 100644 --- a/dripline/extensions/jitter/__init__.py +++ b/dripline/extensions/jitter/__init__.py @@ -16,7 +16,7 @@ version.commit = '---' __all__.append("version") -from .dragonfly import * -from .dragonfly import __all__ as __dragonfly_all -__all__ += __dragonfly_all +from .jitter_endpoint import * +from .jitter_endpoint import __all__ as __jitter_all +__all__ += __jitter_all diff --git a/jitter_example.yaml b/jitter_example.yaml new file mode 100644 index 00000000..5e7caf48 --- /dev/null +++ b/jitter_example.yaml @@ -0,0 +1,9 @@ +runtime-config: + name: my_jitter + module: Service + auth_file: /root/auths.json + endpoints: + - name: peaches + module: JitterEntity + calibration: '2*{}' + initial_value: 0.75 From 6f33b6c93b1a89ca80d78e610bde586a7d3de7f5 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Wed, 3 Apr 2024 16:03:59 -0700 Subject: [PATCH 06/55] Adjustments here as well to attempt getting things working. Luck still at zero. --- .../extensions/roach2_daq/roach2_interface.py | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/dripline/extensions/roach2_daq/roach2_interface.py b/dripline/extensions/roach2_daq/roach2_interface.py index 5e360a1d..1518276c 100644 --- a/dripline/extensions/roach2_daq/roach2_interface.py +++ b/dripline/extensions/roach2_daq/roach2_interface.py @@ -35,20 +35,9 @@ def __init__(self, *args, **kwargs): -__all__.append('Roach2Service') -class Roach2Service(ArtooDaq, core.Service): - ''' - A DAQService for interacting with the Roach - ''' - def __init__(self, **kwargs): - - - core.Service.__init__(self, **kwargs) - - - __all__.append('Roach2Interface') -class Roach2Interface(Roach2Service): +class Roach2Interface(ArtooDaq, core.Service): + def __init__(self, roach2_hostname = 'led', channel_a_config = None, @@ -63,7 +52,7 @@ def __init__(self, **kwargs): - Roach2Service.__init__(self, **kwargs) + core.Service.__init__(self, **kwargs) self.roach2_hostname = roach2_hostname @@ -145,11 +134,12 @@ def is_running(self): else: self.configured=False self.calibrated=False - + print("\nDEBUG: is_running pnt 1 reached. \n") return self.configured def block_channel(self, channel): + print("\nDEBUG: reached block_channel pnt 1.\n") self.block_dict[channel]=True From b52f7867bff2e04f289eee510de7271c1ecf519a Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Mon, 29 Apr 2024 15:52:42 -0700 Subject: [PATCH 07/55] Changed Service back to Endpoint. Turns out service(py) does not inherit from endpoint(py) in dl-py. Noah submitted an issue. Will stay with old formatting of service with endpoints for now. --- dripline/extensions/roach2_daq/roach2_interface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dripline/extensions/roach2_daq/roach2_interface.py b/dripline/extensions/roach2_daq/roach2_interface.py index 1518276c..a1ac8d65 100644 --- a/dripline/extensions/roach2_daq/roach2_interface.py +++ b/dripline/extensions/roach2_daq/roach2_interface.py @@ -36,7 +36,7 @@ def __init__(self, *args, **kwargs): __all__.append('Roach2Interface') -class Roach2Interface(ArtooDaq, core.Service): +class Roach2Interface(ArtooDaq, core.Endpoint): def __init__(self, roach2_hostname = 'led', @@ -52,7 +52,7 @@ def __init__(self, **kwargs): - core.Service.__init__(self, **kwargs) + core.Endpoint.__init__(self, **kwargs) self.roach2_hostname = roach2_hostname @@ -150,7 +150,7 @@ def unblock_channel(self, channel): @property def blocked_channels(self): bc = [i for i in self.block_dict.keys() if self.block_dict[i]==True] - return bc + return ''.join(i for i in bc) def get_central_frequency(self, channel): From f704cfca0885cb5795fd81bc1e7b9533aeb01c1f Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Wed, 1 May 2024 14:22:01 -0700 Subject: [PATCH 08/55] xrange -> range. xrange dprecated in python3 --- dripline/extensions/roach2_daq/r2daq.py | 42 ++++++++++++------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/dripline/extensions/roach2_daq/r2daq.py b/dripline/extensions/roach2_daq/r2daq.py index 42e4ec19..481e40b2 100644 --- a/dripline/extensions/roach2_daq/r2daq.py +++ b/dripline/extensions/roach2_daq/r2daq.py @@ -139,8 +139,8 @@ def FromByteString(cls,bytestr): # unpack data in 64bit mode to correct for byte-order data_64bit = array(unpack(">{0}Q".format(cls.BYTES_IN_PAYLOAD//8),bytestr[cls.BYTES_IN_HEADER:]),dtype=uint64) data = zeros(cls.BYTES_IN_PAYLOAD,dtype=int8) - for ii in xrange(len(data_64bit)): - for jj in xrange(8): + for ii in range(len(data_64bit)): + for jj in range(8): data[ii*8+jj] = int8((data_64bit[ii]>>uint64(8*jj))&uint64(0xFF)) return Packet(ut,pktnum,did,ifid,ud0,ud1,res0,res1,fnt,data) @@ -460,7 +460,7 @@ def grab_packets(self,n=1,dsoc_desc=None,close_soc=False): except AttributeError: raise RuntimeError("No open data socket. Call open_dsoc() first.") pkts = [] - for ii in xrange(n): + for ii in range(n): data = dsoc.recv(Packet.BYTES_IN_PACKET) pkts.append(Packet.FromByteString(data)) if close_soc: @@ -817,7 +817,7 @@ def calibrate_adc_offset(self,zdok=0,oiter=10,otol=0.005): groups = 8 test_step = 10*res_offset logger.info(" Offset calibration ZDOK{0}:".format(zdok)) - for ic in xrange(1,5): + for ic in range(1,5): adc5g.set_spi_offset(self.roach2,zdok,ic,0) x1 = self._snap_per_core(zdok=zdok,groups=groups) sx1 = x1.std(axis=0) @@ -825,7 +825,7 @@ def calibrate_adc_offset(self,zdok=0,oiter=10,otol=0.005): logger.debug(" ...offset: with zero-offsets, means are [{0}]".format( ", ".join(["{0:+7.4f}".format(imx) for imx in mx1]) )) - for ic in xrange(1,5): + for ic in range(1,5): adc5g.set_spi_offset(self.roach2,zdok,ic,test_step) x2 = self._snap_per_core(zdok=zdok,groups=groups) sx2 = x2.std(axis=0) @@ -836,7 +836,7 @@ def calibrate_adc_offset(self,zdok=0,oiter=10,otol=0.005): )) d_mx = (mx2 - mx1)/test_step core_offsets = -mx1/d_mx - for ic in xrange(1,5): + for ic in range(1,5): adc5g.set_spi_offset(self.roach2,zdok,ic,core_offsets[ic-1]) core_offsets[ic-1] = adc5g.get_spi_offset(self.roach2,zdok,ic) x = self._snap_per_core(zdok=zdok,groups=groups) @@ -848,8 +848,8 @@ def calibrate_adc_offset(self,zdok=0,oiter=10,otol=0.005): )) if any(abs(mx) >= otol): logger.debug(" ...offset: solution not good enough, iterating (tol={0:4.4f},iter={1:d})".format(otol,oiter)) - for ii in xrange(0,oiter): - for ic in xrange(1,5): + for ii in range(0,oiter): + for ic in range(1,5): if mx[ic-1] > otol: adc5g.set_spi_offset(self.roach2,zdok,ic,core_offsets[ic-1]-res_offset) elif mx[ic-1] < -otol: @@ -884,7 +884,7 @@ def calibrate_adc_gain(self,zdok=0,giter=10,gtol=0.005): groups = 8 test_step = 10*res_gain logger.info(" Gain calibration ZDOK{0}:".format(zdok)) - for ic in xrange(1,5): + for ic in range(1,5): adc5g.set_spi_gain(self.roach2,zdok,ic,0) x1 = self._snap_per_core(zdok=zdok,groups=groups) sx1 = x1.std(axis=0) @@ -894,7 +894,7 @@ def calibrate_adc_gain(self,zdok=0,giter=10,gtol=0.005): ", ".join(["{0:+7.4f}".format(isx) for isx in sx1]) )) # only adjust gains for last three cores, core1 is the reference - for ic in xrange(2,5): + for ic in range(2,5): adc5g.set_spi_gain(self.roach2,zdok,ic,test_step) x2 = self._snap_per_core(zdok=zdok,groups=groups) sx2 = x2.std(axis=0) @@ -911,7 +911,7 @@ def calibrate_adc_gain(self,zdok=0,giter=10,gtol=0.005): core_gains = 100*(1.0-sx1)/d_sx # set core1 gain to zero core_gains[0] = 0 - for ic in xrange(2,5): + for ic in range(2,5): adc5g.set_spi_gain(self.roach2,zdok,ic,core_gains[ic-1]) core_gains[ic-1] = adc5g.get_spi_gain(self.roach2,zdok,ic) x = self._snap_per_core(zdok=zdok,groups=groups) @@ -924,8 +924,8 @@ def calibrate_adc_gain(self,zdok=0,giter=10,gtol=0.005): )) if any(abs(1.0-sx) >= gtol): logger.debug(" ...gain: solution not good enough, iterating (tol={0:4.4f},iter={1:d})".format(gtol,giter)) - for ii in xrange(0,giter): - for ic in xrange(2,5): + for ii in range(0,giter): + for ic in range(2,5): if (1.0-sx[ic-1]) > gtol: adc5g.set_spi_gain(self.roach2,zdok,ic,core_gains[ic-1]+res_gain) elif (1.0-sx[ic-1]) < -gtol: @@ -960,7 +960,7 @@ def calibrate_adc_phase(self,zdok=0,piter=0,ptol=1.0): lim_phase = [-14.0,14.0] logger.info(" Phase calibration ZDOK{0}:".format(zdok)) core_phases = zeros(4) - for ic in xrange(1,5): + for ic in range(1,5): core_phases[ic-1] = adc5g.get_spi_phase(self.roach2,zdok,ic) logger.info(" ...phase: tuning not implemented yet, phase parameters are [{0}]".format( ", ".join(["{0:+06.2f}".format(icp) for icp in core_phases]) @@ -989,7 +989,7 @@ def _snap_per_core(self,zdok=0,groups=1): family of functions used to tune the core parameters. """ x = zeros((0,4)) - for ig in xrange(groups): + for ig in range(groups): grab = self.roach2.snapshot_get('snap_{0}_snapshot'.format(zdok)) x_ = array(unpack('%ib' %grab['length'], grab['data'])) x_ = x_.reshape((x_.size//4,4)) @@ -1155,7 +1155,7 @@ def _build_filterbank_assignments(self,B,tag='a'): raise RuntimeError("There should be 128 filter coefficients for 1st stage DDC, but {0} received".format(N)) N_w = N//groupsize; words = zeros(N_w,uint32) - for bb in xrange(N): + for bb in range(N): gg = bb % groupsize nn = int(floor(bb/groupsize)) b_uint32 = uint32(B[bb]) @@ -1163,9 +1163,9 @@ def _build_filterbank_assignments(self,B,tag='a'): # setup assignment dictionary assign_filter = {} base = 'ddc_1st_' + tag - for cb_id in xrange(8): + for cb_id in range(8): this_cb = base + '_cb{0}'.format(cb_id) - for g_id in xrange(4): + for g_id in range(4): this_key = this_cb + '_g{0}'.format(g_id) nn = 4*cb_id + g_id this_val = words[nn] @@ -1198,15 +1198,15 @@ def _extract_filterbank_assignments(self,tag='a'): # read uint32 words words = zeros(N_w,uint32) base = 'ddc_1st_' + tag - for cb_id in xrange(8): + for cb_id in range(8): this_cb = base + '_cb{0}'.format(cb_id) - for g_id in xrange(4): + for g_id in range(4): this_key = this_cb + '_g{0}'.format(g_id) nn = 4*cb_id + g_id words[nn] = self.roach2.read_int(this_key) # decompose into filter coefficients B = zeros(N,float32) - for bb in xrange(N): + for bb in range(N): gg = bb % groupsize nn = int(floor(bb/groupsize)) b_int8 = ((words[nn] & (mask<<(w*gg)))>>(w*gg)) From 316f55c2bbdc19c61225b902b944cda435922b80 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 10 Jul 2024 14:53:54 -0700 Subject: [PATCH 09/55] Messing around debugging. --- dripline/extensions/roach2_daq/r2daq.py | 45 ++++++++++--------- .../extensions/roach2_daq/roach2_interface.py | 8 ++-- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/dripline/extensions/roach2_daq/r2daq.py b/dripline/extensions/roach2_daq/r2daq.py index 481e40b2..aa76f556 100644 --- a/dripline/extensions/roach2_daq/r2daq.py +++ b/dripline/extensions/roach2_daq/r2daq.py @@ -6,28 +6,27 @@ from __future__ import division -try: - import adc5g - from numpy import ( - abs, - array, - ceil, - complex64, - concatenate, - float32, - floor, - int8, - ones, - pi, - uint32, - uint64, - uint8, - zeros, - ) - from scipy.signal import firwin2, freqz - from corr.katcp_wrapper import FpgaClient -except ImportError: - pass + +import adc5g +from numpy import ( + abs, + array, + ceil, + complex64, + concatenate, + float32, + floor, + int8, + ones, + pi, + uint32, + uint64, + uint8, + zeros, + ) +from scipy.signal import firwin2, freqz +from corr.katcp_wrapper import FpgaClient + from copy import deepcopy from datetime import datetime import logging @@ -393,6 +392,7 @@ def __init__(self,hostname,dsoc_desc=None,boffile=None,ifcfg=None, do_adcif_cal : bool If True then do ADC interface calibration. Default is True. """ + logger.debug("Beginning roach2 initialization.") # connect to roach and store local copy of FpgaClient r2 = FpgaClient(hostname) if not r2.wait_connected(self._TIMEOUT): @@ -421,6 +421,7 @@ def __init__(self,hostname,dsoc_desc=None,boffile=None,ifcfg=None, for _cfg in ifcfg: self._tmp_add_iface_dict(**_cfg) ### -------- + logger.debug("Middle of roach2 initialization.") # program bitcode if not boffile is None: self._start(boffile,do_ogp_cal=do_ogp_cal, diff --git a/dripline/extensions/roach2_daq/roach2_interface.py b/dripline/extensions/roach2_daq/roach2_interface.py index a1ac8d65..05ac07a8 100644 --- a/dripline/extensions/roach2_daq/roach2_interface.py +++ b/dripline/extensions/roach2_daq/roach2_interface.py @@ -19,13 +19,16 @@ logger = logging.getLogger(__name__) +logger.info('HEEREE 01 HEEEEEEEREEEEEEEEEEEE') #phasmid import try: + logger.info('HEEEEEEEEEEEREEEEEEEEEEEEEEE') from .r2daq import ArtooDaq logger.info('Imported ArtooDaq') except ImportError: + logger.info('Failed to Import ArtooDaq') class ArtooDaq(object): def __init__(self, *args, **kwargs): raise RuntimeError("Dependency not found but is required for ROACH2 support.") @@ -52,6 +55,7 @@ def __init__(self, **kwargs): + logger.debug("Roach2Interface __init__") core.Endpoint.__init__(self, **kwargs) self.roach2_hostname = roach2_hostname @@ -134,12 +138,10 @@ def is_running(self): else: self.configured=False self.calibrated=False - print("\nDEBUG: is_running pnt 1 reached. \n") - return self.configured + return not bool(response) def block_channel(self, channel): - print("\nDEBUG: reached block_channel pnt 1.\n") self.block_dict[channel]=True From 9471658b26904cb17eb024e13f89c4b3b86e84be Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 12 Jul 2024 10:58:32 -0700 Subject: [PATCH 10/55] Removed try except clause so that errors are visible --- dripline/extensions/roach2_daq/roach2_interface.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/dripline/extensions/roach2_daq/roach2_interface.py b/dripline/extensions/roach2_daq/roach2_interface.py index 05ac07a8..c779423c 100644 --- a/dripline/extensions/roach2_daq/roach2_interface.py +++ b/dripline/extensions/roach2_daq/roach2_interface.py @@ -19,19 +19,11 @@ logger = logging.getLogger(__name__) -logger.info('HEEREE 01 HEEEEEEEREEEEEEEEEEEE') #phasmid import -try: - logger.info('HEEEEEEEEEEEREEEEEEEEEEEEEEE') - from .r2daq import ArtooDaq - logger.info('Imported ArtooDaq') +from .r2daq import ArtooDaq +logger.info('Imported ArtooDaq') -except ImportError: - logger.info('Failed to Import ArtooDaq') - class ArtooDaq(object): - def __init__(self, *args, **kwargs): - raise RuntimeError("Dependency not found but is required for ROACH2 support.") __all__ = [] From 10755a7ab5d25282b4111e819e1b381d6831e767 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 21 Nov 2024 11:57:24 -0800 Subject: [PATCH 11/55] adjustments --- dripline/extensions/roach2_daq/r2daq.py | 7 ++++--- .../extensions/roach2_daq/roach2_interface.py | 20 ++++++++----------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/dripline/extensions/roach2_daq/r2daq.py b/dripline/extensions/roach2_daq/r2daq.py index aa76f556..c74a9abe 100644 --- a/dripline/extensions/roach2_daq/r2daq.py +++ b/dripline/extensions/roach2_daq/r2daq.py @@ -728,9 +728,9 @@ def set_gain(self,g=1.0,tag='a'): g_8bit = 127 if g_8bit < -128: g_8bit = -128 - regname = 'gain_ctrl' + regname = b'gain_ctrl' + print("REGISTERS: ", self.registers) masked_val = self.registers[regname] & uint32(~(0xFF< Date: Tue, 14 Jan 2025 09:19:15 -0800 Subject: [PATCH 12/55] Added back line that actualy changed gain. --- dripline/extensions/roach2_daq/r2daq.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dripline/extensions/roach2_daq/r2daq.py b/dripline/extensions/roach2_daq/r2daq.py index c74a9abe..cc666e4d 100644 --- a/dripline/extensions/roach2_daq/r2daq.py +++ b/dripline/extensions/roach2_daq/r2daq.py @@ -731,6 +731,7 @@ def set_gain(self,g=1.0,tag='a'): regname = b'gain_ctrl' print("REGISTERS: ", self.registers) masked_val = self.registers[regname] & uint32(~(0xFF< Date: Tue, 14 Jan 2025 11:40:44 -0800 Subject: [PATCH 13/55] Updated packet returning to give jsons. --- dripline/extensions/roach2_daq/roach2_interface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dripline/extensions/roach2_daq/roach2_interface.py b/dripline/extensions/roach2_daq/roach2_interface.py index 6e7ec4cc..40f1b469 100644 --- a/dripline/extensions/roach2_daq/roach2_interface.py +++ b/dripline/extensions/roach2_daq/roach2_interface.py @@ -262,7 +262,7 @@ def get_T_packets(self, channel='a', NPackets=1, filename=None): with open(filename, 'w') as outfile: json.dump(p, outfile) else: - return p + return json.dumps(p) @@ -292,7 +292,7 @@ def get_F_packets(self,dsoc_desc=None, channel='a', NPackets=10, filename = None with open(filename, 'w') as outfile: json.dump(p, outfile) else: - return p + return json.dumps(p) def get_raw_adc_data(self, NSnaps = 1, filename = None): @@ -304,7 +304,7 @@ def get_raw_adc_data(self, NSnaps = 1, filename = None): if filename is not None: logger.info('Saving raw adc data to {}'.format(filename)) with open(filename, 'w') as outfile: - json.dump(map(int,x_all), outfile) + json.dump(list(map(int,x_all)), outfile) else: return list(x_all) From 25c1a334e8ee7c8dd0cb2371d7dc9e854c87ef8d Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Tue, 14 Jan 2025 15:57:32 -0800 Subject: [PATCH 14/55] Addressed a numpy depreciation issue. Added get_gain. --- dripline/extensions/roach2_daq/r2daq.py | 28 +++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/dripline/extensions/roach2_daq/r2daq.py b/dripline/extensions/roach2_daq/r2daq.py index cc666e4d..75613391 100644 --- a/dripline/extensions/roach2_daq/r2daq.py +++ b/dripline/extensions/roach2_daq/r2daq.py @@ -730,9 +730,33 @@ def set_gain(self,g=1.0,tag='a'): g_8bit = -128 regname = b'gain_ctrl' print("REGISTERS: ", self.registers) - masked_val = self.registers[regname] & uint32(~(0xFF< 3: + raise Warning("Gain control registers for channels 'e' and 'f' not implemented yet") + return + regname = b'gain_ctrl' + masked_val = self.registers[regname] & uint32(0xFF<>(idx*8))/16 + + def set_fft_shift(self,shift_vec='1101010101010',tag='ab'): """ Set shift vector for FFT engine. @@ -752,7 +776,7 @@ def set_fft_shift(self,shift_vec='1101010101010',tag='ab'): idx = self.FFT_ENGINES.index(tag) regname = b'fft_ctrl' s_13bit = int(shift_vec,2) & 0x1FFF - masked_val = self.registers[regname] & uint32(~(0x1FFF< Date: Tue, 14 Jan 2025 16:11:37 -0800 Subject: [PATCH 15/55] Implemented get_gain in roach2_interface. --- dripline/extensions/roach2_daq/roach2_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dripline/extensions/roach2_daq/roach2_interface.py b/dripline/extensions/roach2_daq/roach2_interface.py index 40f1b469..4349c0be 100644 --- a/dripline/extensions/roach2_daq/roach2_interface.py +++ b/dripline/extensions/roach2_daq/roach2_interface.py @@ -169,7 +169,7 @@ def all_central_frequencies(self): @property def gain(self): - return json.dumps(self.gain_dict) + return json.dumps({ch:ArtooDaq.get_gain(ch) for ch in self.gain_dict.keys()}) def set_gain(self, channel, gain): From 24a9f0680d6c92425d785a91c2d03b5354cba2b0 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Tue, 14 Jan 2025 16:16:09 -0800 Subject: [PATCH 16/55] Missing parameter. --- dripline/extensions/roach2_daq/roach2_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dripline/extensions/roach2_daq/roach2_interface.py b/dripline/extensions/roach2_daq/roach2_interface.py index 4349c0be..94a6f76c 100644 --- a/dripline/extensions/roach2_daq/roach2_interface.py +++ b/dripline/extensions/roach2_daq/roach2_interface.py @@ -169,7 +169,7 @@ def all_central_frequencies(self): @property def gain(self): - return json.dumps({ch:ArtooDaq.get_gain(ch) for ch in self.gain_dict.keys()}) + return json.dumps({ch:ArtooDaq.get_gain(self, ch) for ch in self.gain_dict.keys()}) def set_gain(self, channel, gain): From fdddf82b2bedc5a3da9f1c97a610d645bed05ade Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Tue, 14 Jan 2025 17:31:51 -0800 Subject: [PATCH 17/55] Checking output of read_ddc_1st_config. --- dripline/extensions/roach2_daq/roach2_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dripline/extensions/roach2_daq/roach2_interface.py b/dripline/extensions/roach2_daq/roach2_interface.py index 94a6f76c..432219e1 100644 --- a/dripline/extensions/roach2_daq/roach2_interface.py +++ b/dripline/extensions/roach2_daq/roach2_interface.py @@ -144,7 +144,7 @@ def blocked_channels(self): def get_central_frequency(self, channel): - return json.dumps(self.freq_dict[channel]) + return json.dumps(ArtooDaq.read_ddc_1st_config(self, channel)) def set_central_frequency(self, channel, cf): From bea39167ee05f86114ea8567b09f0e4db0bb20b9 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Tue, 14 Jan 2025 17:43:20 -0800 Subject: [PATCH 18/55] ndarray-> list so json works --- dripline/extensions/roach2_daq/roach2_interface.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dripline/extensions/roach2_daq/roach2_interface.py b/dripline/extensions/roach2_daq/roach2_interface.py index 432219e1..2f010115 100644 --- a/dripline/extensions/roach2_daq/roach2_interface.py +++ b/dripline/extensions/roach2_daq/roach2_interface.py @@ -144,7 +144,10 @@ def blocked_channels(self): def get_central_frequency(self, channel): - return json.dumps(ArtooDaq.read_ddc_1st_config(self, channel)) + cfg = ArtooDaq.read_ddc_1st_config(self, channel) + cfg['analog']['B'] = list(cfg['analog']['B']) + cfg['digital']['B'] = list(cfg['digital']['B']) + return json.dumps(cfg) def set_central_frequency(self, channel, cf): From ae2c879c4897413d2443be39da1f1096f0b1812a Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Tue, 14 Jan 2025 18:31:12 -0800 Subject: [PATCH 19/55] Removed exploring read function. Added getting all fft shifts, changed getting shifts and central frequencies to querying the roach registers. --- dripline/extensions/roach2_daq/r2daq.py | 17 ++++++++++++++++- .../extensions/roach2_daq/roach2_interface.py | 12 +++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/dripline/extensions/roach2_daq/r2daq.py b/dripline/extensions/roach2_daq/r2daq.py index 75613391..7c7f0de4 100644 --- a/dripline/extensions/roach2_daq/r2daq.py +++ b/dripline/extensions/roach2_daq/r2daq.py @@ -756,7 +756,6 @@ def get_gain(self,tag='a'): masked_val = self.registers[regname] & uint32(0xFF<>(idx*8))/16 - def set_fft_shift(self,shift_vec='1101010101010',tag='ab'): """ Set shift vector for FFT engine. @@ -780,6 +779,22 @@ def set_fft_shift(self,shift_vec='1101010101010',tag='ab'): self._make_assignment({regname: masked_val | uint32(s_13bit<<(idx*13))}) print(shift_vec) + def get_fft_shift(self,tag='ab'): + """ + Get shift vector for FFT engine. + + Parameters + ---------- + tag : string + Tag selects the FFT engine from which to query shift + vector. Default is 'ab'. + """ + self._check_valid_fft_engine(tag) + idx = self.FFT_ENGINES.index(tag) + regname = b'fft_ctrl' + masked_val = self.registers[regname] & uint32(0x1FFF<>idx*13) + def calibrate_adc_ogp(self,zdok=0, oiter=10,otol=0.005, giter=10,gtol=0.005, diff --git a/dripline/extensions/roach2_daq/roach2_interface.py b/dripline/extensions/roach2_daq/roach2_interface.py index 2f010115..572ab7bc 100644 --- a/dripline/extensions/roach2_daq/roach2_interface.py +++ b/dripline/extensions/roach2_daq/roach2_interface.py @@ -145,9 +145,7 @@ def blocked_channels(self): def get_central_frequency(self, channel): cfg = ArtooDaq.read_ddc_1st_config(self, channel) - cfg['analog']['B'] = list(cfg['analog']['B']) - cfg['digital']['B'] = list(cfg['digital']['B']) - return json.dumps(cfg) + return json.dumps(cfg['digital']['f_c']) def set_central_frequency(self, channel, cf): @@ -167,7 +165,7 @@ def set_central_frequency(self, channel, cf): @property def all_central_frequencies(self): - return json.dumps(self.freq_dict) + return json.dumps({ch:self.get_central_frequency[ch] for ch in self.freq_dict.keys()}) @property @@ -190,8 +188,12 @@ def set_gain(self, channel, gain): raise core.ThrowReply('DriplineGenericDAQError','Channel {} is blocked'.format(channel)) + @property + def all_fft_shift_vectors(self): + return json.dumps({blk:self.get_fft_shift_vector(self,blk) for blk in self.fft_shift_vector.keys()}) + def get_fft_shift_vector(self, tag): - return self.fft_shift_vector[tag] + return ArtooDaq.get_fft_shift(self, tag) def set_fft_shift_vector(self, tag, fft_shift): From 888047d735679a2aaf3141388c9c367ff3fcb2d5 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Tue, 14 Jan 2025 18:37:15 -0800 Subject: [PATCH 20/55] subscript bugfix --- dripline/extensions/roach2_daq/roach2_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dripline/extensions/roach2_daq/roach2_interface.py b/dripline/extensions/roach2_daq/roach2_interface.py index 572ab7bc..a753ab57 100644 --- a/dripline/extensions/roach2_daq/roach2_interface.py +++ b/dripline/extensions/roach2_daq/roach2_interface.py @@ -165,7 +165,7 @@ def set_central_frequency(self, channel, cf): @property def all_central_frequencies(self): - return json.dumps({ch:self.get_central_frequency[ch] for ch in self.freq_dict.keys()}) + return json.dumps({ch:self.get_central_frequency(self,ch) for ch in self.freq_dict.keys()}) @property From cd550fa93a977496a0303b405dfadcf8203939d4 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Wed, 15 Jan 2025 09:24:10 -0800 Subject: [PATCH 21/55] addressing 'self' weirdness in python function args. --- dripline/extensions/roach2_daq/roach2_interface.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dripline/extensions/roach2_daq/roach2_interface.py b/dripline/extensions/roach2_daq/roach2_interface.py index a753ab57..392aba59 100644 --- a/dripline/extensions/roach2_daq/roach2_interface.py +++ b/dripline/extensions/roach2_daq/roach2_interface.py @@ -143,6 +143,11 @@ def blocked_channels(self): return json.dumps(self.block_dict) + @property + def all_central_frequencies(self): + return json.dumps({ch:self.get_central_frequency(ch) for ch in self.freq_dict.keys()}) + + def get_central_frequency(self, channel): cfg = ArtooDaq.read_ddc_1st_config(self, channel) return json.dumps(cfg['digital']['f_c']) @@ -163,11 +168,6 @@ def set_central_frequency(self, channel, cf): raise core.ThrowReply('DriplineGenericDAQError','Channel {} is blocked'.format(channel)) - @property - def all_central_frequencies(self): - return json.dumps({ch:self.get_central_frequency(self,ch) for ch in self.freq_dict.keys()}) - - @property def gain(self): return json.dumps({ch:ArtooDaq.get_gain(self, ch) for ch in self.gain_dict.keys()}) @@ -190,7 +190,7 @@ def set_gain(self, channel, gain): @property def all_fft_shift_vectors(self): - return json.dumps({blk:self.get_fft_shift_vector(self,blk) for blk in self.fft_shift_vector.keys()}) + return json.dumps({blk:self.get_fft_shift_vector(blk) for blk in self.fft_shift_vector.keys()}) def get_fft_shift_vector(self, tag): return ArtooDaq.get_fft_shift(self, tag) From e148dbd1aa7d7c046a0e9886aee55369e1210aa8 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Tue, 4 Feb 2025 12:56:05 -0800 Subject: [PATCH 22/55] Copied over daq files from dl2/master --- .../extensions/roach2_daq/psyllid_provider.py | 529 ++++++++++++++++++ .../roach2_daq/roach_daq_run_interface.py | 523 +++++++++++++++++ 2 files changed, 1052 insertions(+) create mode 100644 dripline/extensions/roach2_daq/psyllid_provider.py create mode 100644 dripline/extensions/roach2_daq/roach_daq_run_interface.py diff --git a/dripline/extensions/roach2_daq/psyllid_provider.py b/dripline/extensions/roach2_daq/psyllid_provider.py new file mode 100644 index 00000000..a9223e32 --- /dev/null +++ b/dripline/extensions/roach2_daq/psyllid_provider.py @@ -0,0 +1,529 @@ +''' +''' + +from __future__ import absolute_import + +# standard imports +import logging +import time + +# internal imports +from dripline import core + + +__all__ = [] + +logger = logging.getLogger(__name__) + + +__all__.append('PsyllidProvider') +class PsyllidProvider(core.Provider): + ''' + Provider for direct communication with up to 3 Psyllid instances with a single stream each + ''' + def __init__(self, + set_condition_list = [], + channel_dict = {'a': 'ch0', 'b': 'ch1', 'c': 'ch2'}, + queue_dict = {'a': 'channel_a_psyllid', 'b': 'channel_b_psyllid', 'c': 'channel_c_psyllid'}, + temp_file = '/tmp/empty_egg_file.egg', + **kwargs): + + core.Provider.__init__(self, **kwargs) + self._set_condition_list = set_condition_list + self.queue_dict = queue_dict + self.channel_dict = channel_dict + self.freq_dict = {x: None for x in channel_dict.keys()} + self.mode_dict = {x: None for x in channel_dict.keys()} + self.status_dict = {x: None for x in channel_dict.keys()} + self.status_value_dict = {x: None for x in channel_dict.keys()} + self.temp_file = temp_file + + + def check_all_psyllid_instances(self): + ''' + Populates all dictionaries by checking the configuarions of all psyllid instances + ''' + for channel in self.channel_dict.keys(): + try: + self.request_status(channel) + self.get_acquisition_mode(channel) + if self.freq_dict[channel] == None: + self.freq_dict[channel] = 50.0e6 + self.set_central_frequency(channel, self.freq_dict[channel]) + except core.exceptions.DriplineError: + self.status_dict[channel] = None + self.status_value_dict[channel] = None + self.mode_dict[channel] = None + self.freq_dict[channel] = None + + # Summary + logger.info('Status of channels: {}'.format(self.status_value_dict)) + logger.info('Set central frequencies: {}'.format(self.freq_dict)) + logger.info('Streaming or triggering mode: {}'.format(self.mode_dict)) + + + @property + def all_acquisition_modes(self): + ''' + Returns mode_dict containing the information which psyllid instance is in triggering or streaming mode + Stopped psyllid instances are in acquisition mode None + ''' + for channel in self.channel_dict.keys(): + self.get_acquisition_mode(channel) + return self.mode_dict + + + @all_acquisition_modes.setter + def all_acquisition_modes(self, x): + raise core.exceptions.DriplineGenericDAQError('acquisition_modes cannot be set') + + + @property + def active_channels(self): + ''' + Returns the number of psyllid instances that are activated + ''' + for channel in self.channel_dict.keys(): + self.request_status(channel) + active_channels = [i for i in self.status_value_dict.keys() if self.status_value_dict[i]==4] + return active_channels + + + @active_channels.setter + def active_channels(self, x): + raise core.exceptions.DriplineGenericDAQError('active_channels cannot be set') + + + def get_active_config(self, channel, key): + target = '{}.active-config.{}.{}'.format(self.queue_dict[channel], + str(self.channel_dict[channel]), + key) + return self.provider.get(target) + + + def get_acquisition_mode(self, channel): + ''' + Tests whether psyllid is in streaming or triggering mode + ''' + request = '{}.node-list.{}'.format(self.queue_dict[channel], self.channel_dict[channel]) + node_list = self.provider.get(request)['nodes'] + + if 'trw' in node_list: + self.mode_dict[channel] = 'triggering' + elif 'strw' in node_list: + self.mode_dict[channel] = 'streaming' + else: + self.mode_dict[channel] = None + return {'mode': self.mode_dict[channel]} + + + def request_status(self, channel): + ''' + Asks the psyllid instance what state it is in and returns that state + ''' + logger.info('Checking Psyllid status of channel {}'.format(channel)) + result = self.provider.get(self.queue_dict[channel]+'.daq-status', timeout=5) + self.status_dict[channel] = result['server']['status'] + self.status_value_dict[channel] = result['server']['status-value'] + logger.info('Psyllid is running. Status is {}'.format(self.status_dict[channel])) + logger.info('Status in numbers: {}'.format(self.status_value_dict[channel])) + return self.status_value_dict[channel] + + + def activate(self, channel): + ''' + Tells psyllid to activate and checks whether activation was successful + ''' + self.request_status(channel) + if self.status_value_dict[channel] != 4: + logger.info('Activating Psyllid instance for channel {}'.format(channel)) + self.provider.cmd(self.queue_dict[channel], 'activate-daq') + time.sleep(1) + self.request_status(channel) + if self.status_value_dict[channel]!=4: + logger.error('Activating failed') + raise core.exceptions.DriplineGenericDAQError('Activating psyllid failed') + + else: + logger.info('Psyllid instance of channel {} is already activated'.format(channel)) + + + def deactivate(self, channel): + ''' + Tells psyllid to deactivate and checks whether deactivation was successful + ''' + self.request_status(channel) + if self.status_value_dict[channel] != 0: + logger.info('Deactivating Psyllid instance of channel {}'.format(channel)) + self.provider.cmd(self.queue_dict[channel],'deactivate-daq') + time.sleep(1) + self.request_status(channel) + if self.status_value_dict[channel]!=0: + logger.error('Deactivating failed') + raise core.exceptions.DriplineGenericDAQError('Deactivating psyllid failed') + + else: + logger.info('Psyllid instance of channel {} is already deactivated'.format(channel)) + + + def reactivate(self, channel): + ''' + Tells psyllid to reactivate and checks whether reactivation was successful + ''' + self.request_status(channel) + if self.status_value_dict[channel] == 4: + logger.info('Reactivating Psyllid instance of channel {}'.format(channel)) + self.provider.cmd(self.queue_dict[channel], 'reactivate-daq') + time.sleep(2) + self.request_status(channel) + if self.status_value_dict[channel]!=4: + logger.error('Reactivating failed') + raise core.exceptions.DriplineGenericDAQError('Reactivating psyllid failed') + + elif self.status_value_dict[channel] == 0: + logger.warning('Psyllid is deactivated. Trying to activate instead of re-activate') + self.activate(channel) + else: + logger.error('Cannot reactivate Psyllid instance of channel {}'.format(channel)) + raise core.exceptions.DriplineGenericDAQError('Psyllid is not activated and can therefore not be reactivated') + + + def save_reactivate(self, channel): + ''' + Reactivate results in the loss of all active-node configurations + This method stores settings and re-sets them after reactivation + ''' + self.get_acquisition_mode(channel) + if self.mode_dict[channel] == 'triggering': + # store trigger configuration + result = self.get_trigger_configuration(channel) + # reactivate psyllid (all trigger and frequency settings are lost) + self.get_central_frequency(channel) + self.reactivate(channel) + # re-set central frequency + self.set_central_frequency(channel, self.freq_dict[channel]) + if self.mode_dict[channel] == 'triggering': + # re-set trigger configuration + self.set_threshold_type(channel=channel, snr_or_sigma=result['threshold_type']) + self.set_trigger_configuration(channel=channel, threshold=result['threshold'], threshold_high=result['threshold_high'], n_triggers=result['n_triggers']) + + + def quit_psyllid(self, channel): + ''' + Tells psyllid to quit + ''' + self.provider.cmd(self.queue_dict[channel], 'quit-psyllid') + logger.info('psyllid quit!') + + + @property + def all_central_frequencies(self): + ''' + Returns a dictionary with all central frequencies + ''' + for channel in self.channel_dict.keys(): + self.get_central_frequency(channel) + return self.freq_dict + + + @all_central_frequencies.setter + def all_central_frequencies(self, x): + raise core.exceptions.DriplineGenericDAQError('all_central_frequencies cannot be set') + + + def get_central_frequency(self, channel): + ''' + Gets central frequency from psyllid and returns it + ''' + if self.mode_dict[channel] == None: + logger.error('Acquisition mode is None. Cannot get central frequency from psyllid') + raise core.exceptions.DriplineGenericDAQError('Acquisition mode is None. Update mode by using get_acquisition_mode command') + routing_key_map = { + 'streaming':'strw', + 'triggering':'trw' + } + request = 'active-config.{}.{}'.format(self.channel_dict[channel], routing_key_map[self.mode_dict[channel]]) + result = self.provider.get(self.queue_dict[channel]+'.'+request) + logger.info('Psyllid says cf is {}'.format(result['center-freq'])) + self.freq_dict[channel]=result['center-freq'] + return self.freq_dict[channel] + + + def set_central_frequency(self, channel, cf): + ''' + Sets central frequency in psyllid + ''' + if self.mode_dict[channel] == None: + logger.error('Acquisition mode is None. Cannot set central frequency from psyllid') + raise core.exceptions.DriplineGenericDAQError('Acquisition mode is None. Update mode by using get_acquisition_mode command') + routing_key_map = { + 'streaming':'strw', + 'triggering':'trw' + } + request = '.active-config.{}.{}.center-freq'.format(self.channel_dict[channel], routing_key_map[self.mode_dict[channel]]) + self.provider.set(self.queue_dict[channel]+request, cf) + logger.info('Set central frequency of {} writer for channel {} to {} Hz'.format(self.mode_dict[channel], channel, cf)) + self.get_central_frequency(channel) + + + def is_psyllid_using_monarch(self, channel): + ''' + Check psyllid is using monarch + If it isn't psyllid cannot write files and runs will fail + ''' + result = self.provider.get(self.queue_dict[channel]+'.use-monarch')['values'][0] + logger.info('Psyllid channel {} is using monarch: {}'.format(channel, result)) + return result + + + def start_run(self, channel, duration, filename): + ''' + Tells psyllid to start a run + Payload is run duration and egg-filename + ''' + payload = {'duration':duration, 'filename':filename} + self.provider.cmd(self.queue_dict[channel], 'start-run', payload=payload) + + + def stop_run(self, channel): + ''' + Tells psyllid to stop a run + This method is for interrupting runs + Runs stop automatically after the set duration and normally don't need to be stopped manually + ''' + self.provider.cmd(self.queue_dict[channel], 'stop-run') + + + ################### + # trigger control # + ################### + + def set_trigger_configuration(self, channel='a', threshold=18, threshold_high=0, n_triggers=1,): + ''' + Set all trigger parameters at once + ''' + logger.info('threshold type {}'.format(self.get_threshold_type(channel))) + if self.get_threshold_type( channel ) == 'snr': + self.set_fmt_snr_threshold( threshold, channel ) + self.set_fmt_snr_high_threshold( threshold_high, channel ) + else: + self.set_fmt_sigma_threshold( threshold, channel ) + self.set_fmt_sigma_high_threshold( threshold_high, channel ) + self.set_n_triggers( n_triggers, channel ) + self.set_trigger_mode( channel ) + + + def get_trigger_configuration(self, channel='a'): + ''' + Gets and returns all trigger parameters + ''' + threshold_type = self.get_threshold_type( channel ) + if threshold_type == 'snr': + threshold = self.get_fmt_snr_threshold( channel ) + threshold_high = self.get_fmt_snr_high_threshold( channel ) + else: + threshold = self.get_fmt_sigma_threshold( channel ) + threshold_high = self.get_fmt_sigma_high_threshold ( channel ) + n_triggers = self.get_n_triggers( channel ) + trigger_mode = self.get_trigger_mode( channel ) + + return {'threshold_type': threshold_type, + 'threshold': threshold, 'threshold_high' : threshold_high, + 'n_triggers' : n_triggers, 'trigger_mode' : trigger_mode} + + + def set_time_window(self, channel='a', pretrigger_time=2e-3, skip_tolerance=5e-3): + ''' + Does all time window settings at once + ''' + # apply settings + self.set_pretrigger_time( pretrigger_time, channel ) + self.set_skip_tolerance( skip_tolerance, channel ) + # reactivate without loosing active-node settings + self.save_reactivate( channel ) + + + def get_time_window(self, channel='a'): + ''' + Gets and returns all time window settings + ''' + pretrigger_time = self.get_pretrigger_time( channel ) + skip_tolerance = self.get_skip_tolerance( channel ) + + return {'pretrigger_time': pretrigger_time, 'skip_tolerance': skip_tolerance} + + + ############################################################## + # individual time window and trigger parameter sets and gets # + ############################################################## + + def set_threshold_type(self, snr_or_sigma, channel='a'): + request = '.active-config.{}.fmt.threshold-type'.format(str(self.channel_dict[channel])) + threshold_type = self.provider.set(self.queue_dict[channel]+request, snr_or_sigma) + return threshold_type + + + def get_threshold_type(self, channel='a'): + ''' + Returns string: 'snr' or 'sigma' + ''' + request = '.active-config.{}.fmt.threshold-type'.format(str(self.channel_dict[channel])) + return self.provider.get(self.queue_dict[channel]+request)['threshold-type'] + + + def set_pretrigger_time(self, pretrigger_time, channel='a'): + n_pretrigger_packets = int(round(pretrigger_time/4.096e-5)) + logger.info('Setting psyllid pretrigger to {} packets'.format(n_pretrigger_packets)) + request = '.node-config.{}.eb.pretrigger'.format(str(self.channel_dict[channel])) + self.provider.set(self.queue_dict[channel]+request, n_pretrigger_packets) + + + def get_pretrigger_time(self, channel='a'): + request = '.active-config.{}.eb.pretrigger'.format(str(self.channel_dict[channel])) + n_pretrigger_packets = self.provider.get(self.queue_dict[channel]+request)['pretrigger'] + return float(n_pretrigger_packets) * 4.096e-5 + + + def set_skip_tolerance(self, skip_tolerance, channel='a'): + n_skipped_packets = int(round(skip_tolerance/4.096e-5)) + logger.info('Setting psyllid skip tolerance to {} packets'.format(n_skipped_packets)) + request = '.node-config.{}.eb.skip-tolerance'.format(str(self.channel_dict[channel])) + self.provider.set(self.queue_dict[channel]+request, n_skipped_packets) + + + def get_skip_tolerance(self, channel='a'): + request = '.active-config.{}.eb.skip-tolerance'.format(str(self.channel_dict[channel])) + n_skipped_packets = self.provider.get(self.queue_dict[channel]+request)['skip-tolerance'] + return float(n_skipped_packets) * 4.096e-5 + + + def set_fmt_snr_threshold(self, threshold, channel='a'): + request = '.active-config.{}.fmt.threshold-power-snr'.format(str(self.channel_dict[channel])) + self.provider.set(self.queue_dict[channel]+request, threshold) + logger.info('Setting psyllid power snr threshold to {}'.format(threshold)) + + + def get_fmt_snr_threshold(self, channel='a'): + request = '.active-config.{}.fmt.threshold-power-snr'.format(str(self.channel_dict[channel])) + threshold = self.provider.get(self.queue_dict[channel]+request)['threshold-power-snr'] + return float(threshold) + + + def set_fmt_snr_high_threshold(self, threshold, channel='a'): + request = '.active-config.{}.fmt.threshold-power-snr-high'.format(str(self.channel_dict[channel])) + self.provider.set(self.queue_dict[channel]+request, threshold) + logger.info('Setting psyllid power snr threshold to {}'.format(threshold)) + + + def get_fmt_snr_high_threshold(self, channel='a'): + request = '.active-config.{}.fmt.threshold-power-snr-high'.format(str(self.channel_dict[channel])) + threshold = self.provider.get(self.queue_dict[channel]+request)['threshold-power-snr-high'] + return float(threshold) + + def set_fmt_sigma_threshold(self, threshold, channel='a'): + request = '.active-config.{}.fmt.threshold-power-sigma'.format(str(self.channel_dict[channel])) + self.provider.set(self.queue_dict[channel]+request, threshold) + logger.info('Setting psyllid power sigma threshold to {}'.format(threshold)) + + + def get_fmt_sigma_threshold(self, channel='a'): + request = '.active-config.{}.fmt.threshold-power-sigma'.format(str(self.channel_dict[channel])) + threshold = self.provider.get(self.queue_dict[channel]+request)['threshold-power-sigma'] + return float(threshold) + + + def set_fmt_sigma_high_threshold(self, threshold, channel='a'): + request = '.active-config.{}.fmt.threshold-power-sigma-high'.format(str(self.channel_dict[channel])) + self.provider.set(self.queue_dict[channel]+request, threshold) + logger.info('Setting psyllid power sigma threshold to {}'.format(threshold)) + + + def get_fmt_sigma_high_threshold(self, channel='a'): + request = '.active-config.{}.fmt.threshold-power-sigma-high'.format(str(self.channel_dict[channel])) + threshold = self.provider.get(self.queue_dict[channel]+request)['threshold-power-sigma-high'] + return float(threshold) + + def _set_trigger_mode(self, mode_id, channel='a'): + request = '.active-config.{}.fmt.trigger-mode'.format(str(self.channel_dict[channel])) + self.provider.set(self.queue_dict[channel]+request, mode_id) + logger.info('Setting psyllid trigger mode to {}'.format(mode_id)) + + + def set_trigger_mode(self, channel='a'): + if self.get_threshold_type( channel ) == 'snr': + if self.get_fmt_snr_high_threshold(channel) > self.get_fmt_snr_threshold(channel): + self._set_trigger_mode('two-level', channel) + else: + self._set_trigger_mode('single-level', channel) + else: + if self.get_fmt_sigma_high_threshold(channel) > self.get_fmt_sigma_threshold(channel): + self._set_trigger_mode('two-level', channel) + else: + self._set_trigger_mode('single-level', channel) + + + + def get_trigger_mode(self, channel='a'): + request = '.active-config.{}.fmt.trigger-mode'.format(str(self.channel_dict[channel])) + trigger_mode = self.provider.get(self.queue_dict[channel]+request)['trigger-mode'] + return trigger_mode + + + def set_n_triggers(self, n_triggers, channel='a'): + request = '.active-config.{}.eb.n-triggers'.format(str(self.channel_dict[channel])) + self.provider.set(self.queue_dict[channel]+request, n_triggers) + logger.info('Setting psyllid n-trigger/skip-tolerance to {}'.format(n_triggers)) + + + def get_n_triggers(self, channel='a'): + request = '.active-config.{}.eb.n-triggers'.format(str(self.channel_dict[channel])) + n_triggers = self.provider.get(self.queue_dict[channel]+request)['n-triggers'] + return n_triggers + + + def make_trigger_mask(self, channel='a', filename='~/fmt_mask.json'): + ''' + Tells psyllid to record a frequency mask, write it to a json file and prepare for triggering run + ''' + if self.mode_dict[channel] != 'triggering': + logger.error('Psyllid instance is not in triggering mode') + raise core.exceptions.DriplineGenericDAQError('Psyllid instance is not in triggering mode') + + logger.info('Switch tf_roach_receiver to freq-only') + request = 'run-daq-cmd.{}.tfrr.freq-only'.format(str(self.channel_dict[channel])) + self.provider.cmd(self.queue_dict[channel],request) + + logger.info('Switch frequency_mask_trigger to update-mask') + request = 'run-daq-cmd.{}.fmt.update-mask'.format(str(self.channel_dict[channel])) + self.provider.cmd(self.queue_dict[channel],request) + time.sleep(1) + + logger.info('Telling psyllid to not use monarch when starting next run') + self.provider.set(self.queue_dict[channel]+'.use-monarch', False) + + logger.info('Start short run to record mask') + self.start_run(channel ,1000, self.temp_file) + time.sleep(1) + + # self._write_trigger_mask(channel, filename) + + logger.info('Telling psyllid to use monarch again for next run') + self.provider.set(self.queue_dict[channel]+'.use-monarch', True) + + logger.info('Switch tf_roach_receiver back to time and freq') + request = 'run-daq-cmd.{}.tfrr.time-and-freq'.format(str(self.channel_dict[channel])) + self.provider.cmd(self.queue_dict[channel],request) + + logger.info('Switch frequency mask trigger to apply-trigger') + request = 'run-daq-cmd.{}.fmt.apply-trigger'.format(str(self.channel_dict[channel])) + self.provider.cmd(self.queue_dict[channel],request) + + + def _write_trigger_mask(self, channel, filename): + ''' + Tells psyllid to write a frequency mask to a json file + ''' + logger.info('Write mask to file') + request = 'run-daq-cmd.{}.fmt.write-mask'.format(str(self.channel_dict[channel])) + payload = {'filename': filename} + self.provider.cmd(self.queue_dict[channel], request, payload=payload) diff --git a/dripline/extensions/roach2_daq/roach_daq_run_interface.py b/dripline/extensions/roach2_daq/roach_daq_run_interface.py new file mode 100644 index 00000000..d9df4b12 --- /dev/null +++ b/dripline/extensions/roach2_daq/roach_daq_run_interface.py @@ -0,0 +1,523 @@ +''' +''' + +from __future__ import absolute_import + + +# standard imports +import logging +import time +import os +# internal imports +from dripline import core +from .daq_run_interface import DAQProvider + +__all__ = [] + +logger = logging.getLogger(__name__) + +__all__.append('ROACH1ChAcquisitionInterface') +class ROACH1ChAcquisitionInterface(DAQProvider): + ''' + A DAQProvider for interacting with ROACH-Psyllid DAQ + ''' + def __init__(self, + channel = None, + psyllid_interface='psyllid_interface', + daq_target = 'roach2_interface', + hf_lo_freq = None, + mask_target_path = None, + default_trigger_dict = None, + **kwargs + ): + + DAQProvider.__init__(self, **kwargs) + + self.psyllid_interface = psyllid_interface + self.daq_target = daq_target + self.status_value = None + self.channel_id = channel + self.freq_dict = {self.channel_id: None} + self._run_time = 1 + self._run_name = "test" + self.mask_target_path = mask_target_path + self.payload_channel = {'channel':self.channel_id} + self.default_trigger_dict = default_trigger_dict + + if hf_lo_freq is None: + raise core.exceptions.DriplineValueError('The roach daq run interface interface requires a "hf_lo_freq" in its config file') + self._hf_lo_freq = hf_lo_freq + + if mask_target_path is None: + logger.warning('No mask target path set. Triggered data taking not possible.') + + def prepare_daq_system(self): + ''' + Checks psyllid and roach + Checks acquisition mode + Gets roach frequency and sets psyllid frequency + ''' + logger.info('Doing setup checks...') + self._check_psyllid_instance() + + acquisition_mode = self.acquisition_mode + logger.info('Psyllid instance for this channel is in acquisition mode: {}'.format(acquisition_mode)) + + if self._check_roach2_is_ready() == False: + logger.warning('ROACH2 check indicates ADC is not calibrated.') + + logger.info('Setting Psyllid central frequency identical to ROACH2 central frequency') + freqs = self._get_roach_central_freqs() + self.central_frequency = freqs[self.channel_id] + + + def _check_roach2_is_ready(self): + ''' + Asks roach2_interface whether roach is running and adc is calibrated + ''' + logger.info('Checking ROACH2 status') + result = self.provider.get(self.daq_target+ '.is_running')['values'][0] + if result==True: + result = self.provider.get(self.daq_target+'.calibration_status')['values'][0] + if result == True: + logger.info('ROACH2 is running and ADCs are calibrated') + return True + else: + logger.info('ROACH2 is running but ADC has not been calibrated') + # For now this does not prevent data taking, because the roach2_interface cannot now whether the calibration was successful or not + # the calibration status is therefore only semi meaningful + return True + else: + logger.error('ROACH2 is not ready') + raise core.exceptions.DriplineGenericDAQError('ROACH2 is not ready') + + + def _check_psyllid_instance(self): + ''' + Checks psyllid instance is running and matches channel settings + Activates psyllid if deactivated + Checks channel and stream labels are matching + ''' + logger.info('Checking Psyllid service & instance') + + self.status_value = self.provider.cmd(self.psyllid_interface, 'request_status', payload = self.payload_channel)['values'][0] + + if self.status_value == 0: + self.provider.cmd(self.psyllid_interface, 'activate', payload = self.payload_channel) + self.status_value = self.provider.cmd(self.psyllid_interface, 'request_status', payload = self.payload_channel)['values'][0] + + + @property + def is_running(self): + ''' + Requests status of psyllid + Returns True if status is 5 (currently taking data) + ''' + result = self.provider.cmd(self.psyllid_interface, 'request_status', payload = self.payload_channel, timeout=10) + self.status_value = result['values'][0] + logger.info('psyllid status is {}'.format(self.status_value)) + if self.status_value==5: + return True + else: + return False + + def _do_checks(self): + ''' + Checks everything that could prevent a successful run (in theory) + ''' + if self._run_time ==0: + raise core.exceptions.DriplineValueError('run time is zero') + + #checking that no run is in progress + if self.is_running == True: + raise core.exceptions.DriplineGenericDAQError('Psyllid is already running') + + self._check_psyllid_instance() + + if self.status_value!=4: + raise core.exceptions.DriplineGenericDAQError('Psyllid DAQ is not activated') + + # check psyllid is ready to write a file + result = self.provider.cmd(self.psyllid_interface, 'is_psyllid_using_monarch', payload = self.payload_channel)['values'][0] + if result != True: + raise core.exceptions.DriplineGenericDAQError('Psyllid is not using monarch and therefore not ready to write a file') + + # checking roach is ready + if self._check_roach2_is_ready() != True: + raise core.exceptions.DriplineGenericDAQError('ROACH2 is not ready. ADC not calibrated.') + + # check channel is unblocked + blocked_channels = self.provider.get(self.daq_target+ '.blocked_channels') + if self.channel_id in blocked_channels: + raise core.exceptions.DriplineGenericDAQError('Channel is blocked') + + # check frequency matches + roach_freqs = self._get_roach_central_freqs() + psyllid_freq = self._get_psyllid_central_freq() + if abs(roach_freqs[self.channel_id]-psyllid_freq)>1: + logger.error('Frequency mismatch: roach cf is {}Hz, psyllid cf is {}Hz'.format(roach_freqs[self.channel_id], psyllid_freq)) + raise core.exceptions.DriplineGenericDAQError('Frequency mismatch') + + + return "checks successful" + + + def determine_RF_ROI(self): + ''' + Sets frequency information in _run_metadata + ''' + logger.info('trying to determine roi') + + rf_input = self.provider.get(self._hf_lo_freq['endpoint_name'])[self._hf_lo_freq['payload_field']] + logger.debug('{} returned {}'.format(self._hf_lo_freq['endpoint_name'],rf_input)) + hf_lo_freq = float(self._hf_lo_freq['calibration'][rf_input]) + self._run_meta['RF_HF_MIXING'] = hf_lo_freq + logger.debug('RF High stage mixing: {}'.format(hf_lo_freq)) + + logger.info('Getting central frequency from ROACH2') + cfs = self._get_roach_central_freqs() + cf = cfs[self.channel_id] + logger.info('Central frequency is: {}'.format(cf)) + + self._run_meta['RF_ROI_MIN'] = float(cf-50e6) + hf_lo_freq + logger.debug('RF Min: {}'.format(self._run_meta['RF_ROI_MIN'])) + + self._run_meta['RF_ROI_MAX'] = float(cf+50e6) + hf_lo_freq + logger.debug('RF Max: {}'.format(self._run_meta['RF_ROI_MAX'])) + + + def _start_data_taking(self, directory, filename): + ''' + Blocks roach channel + Converts seconds to miliseconds + Creates directory for data files + Tells psyllid_provider to tell psyllid to start the run + Unblocks roach channels if that fails + ''' + + setup = { 'roach' : self.provider.get('{}.registers'.format(self.daq_target)) } + psyllid_config_kwargs = { + 'target' : self.psyllid_interface, + 'method_name' : 'get_active_config', + 'payload' : { 'channel' : self.channel_id, + 'key' : 'prf' } + } + setup.update( { 'psyllid' : + { 'prf' : self.provider.cmd(**psyllid_config_kwargs) } } ) + + if self.acquisition_mode == 'triggering': + payload = {'channel':self.channel_id, 'filename':'{}/{}_mask.yaml'.format(directory,filename)} + self.provider.cmd(self.psyllid_interface, '_write_trigger_mask', payload=payload) + for key in ('fmt', 'tfrr', 'eb'): + psyllid_config_kwargs['payload'].update( { 'key' : key } ) + setup['psyllid'].update( { key : self.provider.cmd(**psyllid_config_kwargs) } ) + self._send_metadata( type='setup', data=setup) + + logger.info('block roach channel') + self.provider.cmd(self.daq_target, 'block_channel', payload = self.payload_channel) + + # switching from seconds to milisecons + duration = self._run_time*1000.0 + logger.info('run duration in ms: {}'.format(duration)) + + psyllid_filename = filename+'.egg' + + logger.info('Going to tell psyllid to start the run') + payload = {'channel':self.channel_id, 'filename': os.path.join(directory, psyllid_filename), 'duration':duration} + try: + self.provider.cmd(self.psyllid_interface, 'start_run', payload = payload) + except core.exceptions.DriplineError as e: + logger.critical('Error from psyllid provider or psyllid. Starting psyllid run failed.') + payload = {'channel': self.channel_id} + try: + self.provider.cmd(self.daq_target, 'unblock_channel', payload = payload) + logger.info('Unblocked channel') + finally: + raise e + except Exception as e: + logger.critical('Something else went wrong.') + payload = {'channel': self.channel_id} + try: + self.provider.cmd(self.daq_target, 'unblock_channel', payload = payload) + logger.info('Unblocked channel') + finally: + raise e + + + def _stop_data_taking(self): + ''' + Checks whether psyllid run has stopped + If it hasn't, stops run + Unblocks roach channel no matter what + ''' + try: + if self.is_running: + logger.info('Psyllid still running. Telling it to stop the run') + self.provider.cmd(self.psyllid_interface, 'stop_run', payload = self.payload_channel) + except core.exceptions.DriplineError as e: + logger.critical('Getting Psyllid status or stopping run failed') + logger.info('Unblock channel') + payload = {'channel': self.channel_id} + try: + self.provider.cmd(self.daq_target, 'unblock_channel', payload = payload) + finally: + raise e + except Exception as e: + logger.critical('Something else went wrong') + payload = {'channel': self.channel_id} + try: + self.provider.cmd(self.daq_target, 'unblock_channel', payload = payload) + finally: + raise e + else: + logger.info('Unblock channel') + payload = {'channel': self.channel_id} + self.provider.cmd(self.daq_target, 'unblock_channel', payload = payload) + + + def stop_psyllid(self): + ''' + Makes psyllid exit and unblocks roach channel + ''' + self.provider.cmd(self.psyllid_interface, 'quit_psyllid', payload = self.payload_channel) + self.provider.cmd(self.daq_target, 'unblock_channel', payload = self.payload_channel) + + + ########################### + # frequency sets and gets # + ########################### + + def _get_roach_central_freqs(self): + result = self.provider.get(self.daq_target + '.all_central_frequencies') + logger.info('ROACH central freqs {}'.format(result)) + return result + + + def _get_psyllid_central_freq(self): + result = self.provider.cmd(self.psyllid_interface, 'get_central_frequency', payload = self.payload_channel) + logger.info('Psyllid central freqs {}'.format(result['values'][0])) + return result['values'][0] + + + @property + def central_frequency(self): + return self.freq_dict[self.channel_id] + + + @central_frequency.setter + def central_frequency(self, cf): + ''' + Sets central frequency in roach channel + roach2_interface returns true frequency (can deviate from requested cf) + Sets same frequency in psyllid instance + ''' + payload = {'cf':float(cf), 'channel':self.channel_id} + result = self.provider.cmd(self.daq_target, 'set_central_frequency', payload = payload) + logger.info('The roach central frequency is now {}Hz'.format(result['values'][0])) + + self.freq_dict[self.channel_id]=round(result['values'][0]) + payload = {'cf':self.freq_dict[self.channel_id], 'channel':self.channel_id} + self.provider.cmd(self.psyllid_interface, 'set_central_frequency', payload = payload) + + + ################### + # trigger control # + ################### + + @property + def acquisition_mode(self): + ''' + The Cmd returns a dictionary like {"mode": "someMode"}; we want just the mode value. + ''' + result = self.provider.cmd(self.psyllid_interface, 'get_acquisition_mode', payload = self.payload_channel)['mode'] + return result + + @acquisition_mode.setter + def acquisition_mode(self, x): + raise core.exceptions.DriplineGenericDAQError('acquisition mode cannot be set via dragonfly') + + + @property + def threshold_type(self): + result = self.provider.cmd(self.psyllid_interface, 'get_threshold_type', payload = self.payload_channel)['values'][0] + return result + + @threshold_type.setter + def threshold_type(self, snr_or_sigma): + self.provider.cmd(self.psyllid_interface, 'set_threshold_type', payload = {'channel': self.channel_id, 'snr_or_sigma': snr_or_sigma}) + + + @property + def threshold(self): + if self.threshold_type == 'snr': + return self.provider.cmd(self.psyllid_interface, 'get_fmt_snr_threshold', payload = self.payload_channel)['values'][0] + else: + return self.provider.cmd(self.psyllid_interface, 'get_fmt_sigma_threshold', payload = self.payload_channel)['values'][0] + + @threshold.setter + def threshold(self, threshold): + if self.threshold_type == 'snr': + self.provider.cmd(self.psyllid_interface, 'set_fmt_snr_threshold', payload = {'channel': self.channel_id, 'threshold': threshold}) + else: + self.provider.cmd(self.psyllid_interface, 'set_fmt_sigma_threshold', payload = {'channel': self.channel_id, 'threshold': threshold}) + self.provider.cmd(self.psyllid_interface, 'set_trigger_mode', payload = self.payload_channel) + + + @property + def high_threshold(self): + if self.threshold_type == 'snr': + return self.provider.cmd(self.psyllid_interface, 'get_fmt_snr_high_threshold', payload = self.payload_channel)['values'][0] + else: + return self.provider.cmd(self.psyllid_interface, 'get_fmt_sigma_high_threshold', payload = self.payload_channel)['values'][0] + + @high_threshold.setter + def high_threshold(self, threshold): + if self.threshold_type == 'snr': + self.provider.cmd(self.psyllid_interface, 'set_fmt_snr_high_threshold', payload = {'channel': self.channel_id, 'threshold': threshold}) + else: + self.provider.cmd(self.psyllid_interface, 'set_fmt_sigma_high_threshold', payload = {'channel': self.channel_id, 'threshold': threshold}) + self.provider.cmd(self.psyllid_interface, 'set_trigger_mode', payload = self.payload_channel) + + + @property + def n_triggers(self): + result = self.provider.cmd(self.psyllid_interface, 'get_n_triggers', payload = self.payload_channel)['values'][0] + return result + + @n_triggers.setter + def n_triggers(self, n_triggers): + self.provider.cmd(self.psyllid_interface, 'set_n_triggers', payload = {'channel': self.channel_id, 'n_triggers': n_triggers}) + + + @property + def pretrigger_time(self): + result = self.provider.cmd(self.psyllid_interface, 'get_pretrigger_time', payload = self.payload_channel)['values'][0] + return result + + @pretrigger_time.setter + def pretrigger_time(self, pretrigger_time): + ''' + Psyllid only adopts change of pretrigger time after reactivation + ''' + self.provider.cmd(self.psyllid_interface, 'set_pretrigger_time', payload = {'channel': self.channel_id, 'pretrigger_time': pretrigger_time}) + self.provider.cmd(self.psyllid_interface, 'save_reactivate', payload = self.payload_channel) + + + @property + def skip_tolerance(self): + result = self.provider.cmd(self.psyllid_interface, 'get_skip_tolerance', payload = self.payload_channel)['values'][0] + return result + + @skip_tolerance.setter + def skip_tolerance(self, skip_tolerance): + ''' + Psyllid only adopts change of skip tolerance after reactivation + ''' + self.provider.cmd(self.psyllid_interface, 'set_skip_tolerance', payload = {'channel': self.channel_id, 'skip_tolerance': skip_tolerance}) + self.provider.cmd(self.psyllid_interface, 'save_reactivate', payload = self.payload_channel) + + + @property + def trigger_type(self): + ''' + Returns the kind of trigger that is currently set + ''' + n_triggers = self.provider.cmd(self.psyllid_interface, 'get_n_triggers', payload = self.payload_channel)['values'][0] + trigger_mode = self.provider.cmd(self.psyllid_interface, 'get_trigger_mode', payload = self.payload_channel)['values'][0] + + if n_triggers > 1: + trigger_type = 'multi-trigger' + else: + trigger_type = trigger_mode + + return trigger_type + + @trigger_type.setter + def trigger_type(self, x): + raise core.exceptions.DriplineGenericDAQError('Trigger type is result of trigger settings and cannot be set directly') + + + @property + def trigger_settings(self): + ''' + Returns all trigger settings + ''' + result = self.provider.cmd(self.psyllid_interface, 'get_trigger_configuration', payload = self.payload_channel) + return result + + + @trigger_settings.setter + def trigger_settings(self, x): + raise core.exceptions.DriplineGenericDAQError('Use configure_trigger command to set all trigger parameters at once') + + + def configure_trigger(self, threshold, high_threshold, n_triggers): + ''' + Set all trigger parameters with one command + ''' + payload = {'threshold' : threshold, 'threshold_high' : high_threshold, 'n_triggers' : n_triggers, 'channel' : self.channel_id} + self.provider.cmd(self.psyllid_interface, 'set_trigger_configuration', payload = payload) + + + @property + def time_window_settings(self): + ''' + Returns pretrigger time and skip tolerance + ''' + result = self.provider.cmd(self.psyllid_interface, 'get_time_window', payload = self.payload_channel) + return result + + + @time_window_settings.setter + def time_window_settings(self, x): + raise core.exceptions.DriplineGenericDAQError('Use configure_time_window command to set skip_tolerance and pretrigger_time') + + + def configure_time_window(self, pretrigger_time, skip_tolerance): + ''' + Set pretrigger time and skip tolerance with one command + ''' + payload = {'skip_tolerance': skip_tolerance, 'pretrigger_time': pretrigger_time, 'channel' : self.channel_id} + self.provider.cmd(self.psyllid_interface, 'set_time_window', payload = payload) + + + @property + def default_trigger_settings(self): + ''' + Returns dictionary containing default trigger settings + ''' + if self.default_trigger_dict == None: + raise core.exceptions.DriplineGenericDAQError('No default trigger settings present') + else: + return self.default_trigger_dict + + @default_trigger_settings.setter + def default_trigger_settings(self, x): + raise core.exceptions.DriplineGenericDAQError('Default settings must be specified in config file. Use cmd set_default_trigger to apply default settings') + + + def set_default_trigger(self): + ''' + Sets trigger parameters to values specified in config + ''' + if self.default_trigger_dict == None: + raise core.exceptions.DriplineGenericDAQError('No default trigger settings present') + else: + self.threshold_type = self.default_trigger_dict['threshold_type'] + self.configure_trigger(threshold=self.default_trigger_dict['threshold'], high_threshold=self.default_trigger_dict['high_threshold'], n_triggers=self.default_trigger_dict['n_triggers']) + self.configure_time_window(pretrigger_time=float(self.default_trigger_dict['pretrigger_time']), skip_tolerance=float(self.default_trigger_dict['skip_tolerance'])) + + + def make_trigger_mask(self): + ''' + Acquire and save new mask + Raises exception if no mask_target_path was not set in config file + ''' + if self.mask_target_path == None: + raise core.exceptions.DriplineGenericDAQError('No target path set for trigger mask') + + timestr = time.strftime("%Y%m%d_%H%M%S") + filename = '{}_frequency_mask_channel_{}_cf_{}.yaml'.format(timestr, self.channel_id, self.freq_dict[self.channel_id]) + path = os.path.join(self.mask_target_path, filename) + payload = {'channel':self.channel_id, 'filename':path} + self.provider.cmd(self.psyllid_interface, 'make_trigger_mask', payload = payload) From f9255d7c5538fda9190072b2aeea717572759844 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Thu, 6 Feb 2025 14:48:32 -0800 Subject: [PATCH 23/55] Changing from dl2 provider to dl3 interface class. Commented out exceptions. --- .../extensions/roach2_daq/psyllid_provider.py | 101 +++++++++--------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/dripline/extensions/roach2_daq/psyllid_provider.py b/dripline/extensions/roach2_daq/psyllid_provider.py index a9223e32..1f343132 100644 --- a/dripline/extensions/roach2_daq/psyllid_provider.py +++ b/dripline/extensions/roach2_daq/psyllid_provider.py @@ -17,7 +17,7 @@ __all__.append('PsyllidProvider') -class PsyllidProvider(core.Provider): +class PsyllidProvider(core.Interface): ''' Provider for direct communication with up to 3 Psyllid instances with a single stream each ''' @@ -28,7 +28,7 @@ def __init__(self, temp_file = '/tmp/empty_egg_file.egg', **kwargs): - core.Provider.__init__(self, **kwargs) + core.Interface.__init__(self, **kwargs) self._set_condition_list = set_condition_list self.queue_dict = queue_dict self.channel_dict = channel_dict @@ -75,7 +75,8 @@ def all_acquisition_modes(self): @all_acquisition_modes.setter def all_acquisition_modes(self, x): - raise core.exceptions.DriplineGenericDAQError('acquisition_modes cannot be set') + # raise core.exceptions.DriplineGenericDAQError('acquisition_modes cannot be set') + return @property @@ -91,14 +92,15 @@ def active_channels(self): @active_channels.setter def active_channels(self, x): - raise core.exceptions.DriplineGenericDAQError('active_channels cannot be set') + # raise core.exceptions.DriplineGenericDAQError('active_channels cannot be set') + return def get_active_config(self, channel, key): target = '{}.active-config.{}.{}'.format(self.queue_dict[channel], str(self.channel_dict[channel]), key) - return self.provider.get(target) + return self.get(target) def get_acquisition_mode(self, channel): @@ -106,7 +108,7 @@ def get_acquisition_mode(self, channel): Tests whether psyllid is in streaming or triggering mode ''' request = '{}.node-list.{}'.format(self.queue_dict[channel], self.channel_dict[channel]) - node_list = self.provider.get(request)['nodes'] + node_list = self.get(request)['nodes'] if 'trw' in node_list: self.mode_dict[channel] = 'triggering' @@ -122,7 +124,7 @@ def request_status(self, channel): Asks the psyllid instance what state it is in and returns that state ''' logger.info('Checking Psyllid status of channel {}'.format(channel)) - result = self.provider.get(self.queue_dict[channel]+'.daq-status', timeout=5) + result = self.get(self.queue_dict[channel]+'.daq-status', timeout=5) self.status_dict[channel] = result['server']['status'] self.status_value_dict[channel] = result['server']['status-value'] logger.info('Psyllid is running. Status is {}'.format(self.status_dict[channel])) @@ -137,12 +139,12 @@ def activate(self, channel): self.request_status(channel) if self.status_value_dict[channel] != 4: logger.info('Activating Psyllid instance for channel {}'.format(channel)) - self.provider.cmd(self.queue_dict[channel], 'activate-daq') + self.cmd(self.queue_dict[channel], 'activate-daq') time.sleep(1) self.request_status(channel) if self.status_value_dict[channel]!=4: logger.error('Activating failed') - raise core.exceptions.DriplineGenericDAQError('Activating psyllid failed') + # raise core.exceptions.DriplineGenericDAQError('Activating psyllid failed') else: logger.info('Psyllid instance of channel {} is already activated'.format(channel)) @@ -155,12 +157,12 @@ def deactivate(self, channel): self.request_status(channel) if self.status_value_dict[channel] != 0: logger.info('Deactivating Psyllid instance of channel {}'.format(channel)) - self.provider.cmd(self.queue_dict[channel],'deactivate-daq') + self.cmd(self.queue_dict[channel],'deactivate-daq') time.sleep(1) self.request_status(channel) if self.status_value_dict[channel]!=0: logger.error('Deactivating failed') - raise core.exceptions.DriplineGenericDAQError('Deactivating psyllid failed') + # raise core.exceptions.DriplineGenericDAQError('Deactivating psyllid failed') else: logger.info('Psyllid instance of channel {} is already deactivated'.format(channel)) @@ -173,19 +175,19 @@ def reactivate(self, channel): self.request_status(channel) if self.status_value_dict[channel] == 4: logger.info('Reactivating Psyllid instance of channel {}'.format(channel)) - self.provider.cmd(self.queue_dict[channel], 'reactivate-daq') + self.cmd(self.queue_dict[channel], 'reactivate-daq') time.sleep(2) self.request_status(channel) if self.status_value_dict[channel]!=4: logger.error('Reactivating failed') - raise core.exceptions.DriplineGenericDAQError('Reactivating psyllid failed') + # raise core.exceptions.DriplineGenericDAQError('Reactivating psyllid failed') elif self.status_value_dict[channel] == 0: logger.warning('Psyllid is deactivated. Trying to activate instead of re-activate') self.activate(channel) else: logger.error('Cannot reactivate Psyllid instance of channel {}'.format(channel)) - raise core.exceptions.DriplineGenericDAQError('Psyllid is not activated and can therefore not be reactivated') + # raise core.exceptions.DriplineGenericDAQError('Psyllid is not activated and can therefore not be reactivated') def save_reactivate(self, channel): @@ -212,7 +214,7 @@ def quit_psyllid(self, channel): ''' Tells psyllid to quit ''' - self.provider.cmd(self.queue_dict[channel], 'quit-psyllid') + self.cmd(self.queue_dict[channel], 'quit-psyllid') logger.info('psyllid quit!') @@ -228,7 +230,8 @@ def all_central_frequencies(self): @all_central_frequencies.setter def all_central_frequencies(self, x): - raise core.exceptions.DriplineGenericDAQError('all_central_frequencies cannot be set') + # raise core.exceptions.DriplineGenericDAQError('all_central_frequencies cannot be set') + return def get_central_frequency(self, channel): @@ -237,13 +240,13 @@ def get_central_frequency(self, channel): ''' if self.mode_dict[channel] == None: logger.error('Acquisition mode is None. Cannot get central frequency from psyllid') - raise core.exceptions.DriplineGenericDAQError('Acquisition mode is None. Update mode by using get_acquisition_mode command') + # raise core.exceptions.DriplineGenericDAQError('Acquisition mode is None. Update mode by using get_acquisition_mode command') routing_key_map = { 'streaming':'strw', 'triggering':'trw' } request = 'active-config.{}.{}'.format(self.channel_dict[channel], routing_key_map[self.mode_dict[channel]]) - result = self.provider.get(self.queue_dict[channel]+'.'+request) + result = self.get(self.queue_dict[channel]+'.'+request) logger.info('Psyllid says cf is {}'.format(result['center-freq'])) self.freq_dict[channel]=result['center-freq'] return self.freq_dict[channel] @@ -255,13 +258,13 @@ def set_central_frequency(self, channel, cf): ''' if self.mode_dict[channel] == None: logger.error('Acquisition mode is None. Cannot set central frequency from psyllid') - raise core.exceptions.DriplineGenericDAQError('Acquisition mode is None. Update mode by using get_acquisition_mode command') + # raise core.exceptions.DriplineGenericDAQError('Acquisition mode is None. Update mode by using get_acquisition_mode command') routing_key_map = { 'streaming':'strw', 'triggering':'trw' } request = '.active-config.{}.{}.center-freq'.format(self.channel_dict[channel], routing_key_map[self.mode_dict[channel]]) - self.provider.set(self.queue_dict[channel]+request, cf) + self.set(self.queue_dict[channel]+request, cf) logger.info('Set central frequency of {} writer for channel {} to {} Hz'.format(self.mode_dict[channel], channel, cf)) self.get_central_frequency(channel) @@ -271,7 +274,7 @@ def is_psyllid_using_monarch(self, channel): Check psyllid is using monarch If it isn't psyllid cannot write files and runs will fail ''' - result = self.provider.get(self.queue_dict[channel]+'.use-monarch')['values'][0] + result = self.get(self.queue_dict[channel]+'.use-monarch')['values'][0] logger.info('Psyllid channel {} is using monarch: {}'.format(channel, result)) return result @@ -282,7 +285,7 @@ def start_run(self, channel, duration, filename): Payload is run duration and egg-filename ''' payload = {'duration':duration, 'filename':filename} - self.provider.cmd(self.queue_dict[channel], 'start-run', payload=payload) + self.cmd(self.queue_dict[channel], 'start-run', payload=payload) def stop_run(self, channel): @@ -291,7 +294,7 @@ def stop_run(self, channel): This method is for interrupting runs Runs stop automatically after the set duration and normally don't need to be stopped manually ''' - self.provider.cmd(self.queue_dict[channel], 'stop-run') + self.cmd(self.queue_dict[channel], 'stop-run') ################### @@ -359,7 +362,7 @@ def get_time_window(self, channel='a'): def set_threshold_type(self, snr_or_sigma, channel='a'): request = '.active-config.{}.fmt.threshold-type'.format(str(self.channel_dict[channel])) - threshold_type = self.provider.set(self.queue_dict[channel]+request, snr_or_sigma) + threshold_type = self.set(self.queue_dict[channel]+request, snr_or_sigma) return threshold_type @@ -368,19 +371,19 @@ def get_threshold_type(self, channel='a'): Returns string: 'snr' or 'sigma' ''' request = '.active-config.{}.fmt.threshold-type'.format(str(self.channel_dict[channel])) - return self.provider.get(self.queue_dict[channel]+request)['threshold-type'] + return self.get(self.queue_dict[channel]+request)['threshold-type'] def set_pretrigger_time(self, pretrigger_time, channel='a'): n_pretrigger_packets = int(round(pretrigger_time/4.096e-5)) logger.info('Setting psyllid pretrigger to {} packets'.format(n_pretrigger_packets)) request = '.node-config.{}.eb.pretrigger'.format(str(self.channel_dict[channel])) - self.provider.set(self.queue_dict[channel]+request, n_pretrigger_packets) + self.set(self.queue_dict[channel]+request, n_pretrigger_packets) def get_pretrigger_time(self, channel='a'): request = '.active-config.{}.eb.pretrigger'.format(str(self.channel_dict[channel])) - n_pretrigger_packets = self.provider.get(self.queue_dict[channel]+request)['pretrigger'] + n_pretrigger_packets = self.get(self.queue_dict[channel]+request)['pretrigger'] return float(n_pretrigger_packets) * 4.096e-5 @@ -388,64 +391,64 @@ def set_skip_tolerance(self, skip_tolerance, channel='a'): n_skipped_packets = int(round(skip_tolerance/4.096e-5)) logger.info('Setting psyllid skip tolerance to {} packets'.format(n_skipped_packets)) request = '.node-config.{}.eb.skip-tolerance'.format(str(self.channel_dict[channel])) - self.provider.set(self.queue_dict[channel]+request, n_skipped_packets) + self.set(self.queue_dict[channel]+request, n_skipped_packets) def get_skip_tolerance(self, channel='a'): request = '.active-config.{}.eb.skip-tolerance'.format(str(self.channel_dict[channel])) - n_skipped_packets = self.provider.get(self.queue_dict[channel]+request)['skip-tolerance'] + n_skipped_packets = self.get(self.queue_dict[channel]+request)['skip-tolerance'] return float(n_skipped_packets) * 4.096e-5 def set_fmt_snr_threshold(self, threshold, channel='a'): request = '.active-config.{}.fmt.threshold-power-snr'.format(str(self.channel_dict[channel])) - self.provider.set(self.queue_dict[channel]+request, threshold) + self.set(self.queue_dict[channel]+request, threshold) logger.info('Setting psyllid power snr threshold to {}'.format(threshold)) def get_fmt_snr_threshold(self, channel='a'): request = '.active-config.{}.fmt.threshold-power-snr'.format(str(self.channel_dict[channel])) - threshold = self.provider.get(self.queue_dict[channel]+request)['threshold-power-snr'] + threshold = self.get(self.queue_dict[channel]+request)['threshold-power-snr'] return float(threshold) def set_fmt_snr_high_threshold(self, threshold, channel='a'): request = '.active-config.{}.fmt.threshold-power-snr-high'.format(str(self.channel_dict[channel])) - self.provider.set(self.queue_dict[channel]+request, threshold) + self.set(self.queue_dict[channel]+request, threshold) logger.info('Setting psyllid power snr threshold to {}'.format(threshold)) def get_fmt_snr_high_threshold(self, channel='a'): request = '.active-config.{}.fmt.threshold-power-snr-high'.format(str(self.channel_dict[channel])) - threshold = self.provider.get(self.queue_dict[channel]+request)['threshold-power-snr-high'] + threshold = self.get(self.queue_dict[channel]+request)['threshold-power-snr-high'] return float(threshold) def set_fmt_sigma_threshold(self, threshold, channel='a'): request = '.active-config.{}.fmt.threshold-power-sigma'.format(str(self.channel_dict[channel])) - self.provider.set(self.queue_dict[channel]+request, threshold) + self.set(self.queue_dict[channel]+request, threshold) logger.info('Setting psyllid power sigma threshold to {}'.format(threshold)) def get_fmt_sigma_threshold(self, channel='a'): request = '.active-config.{}.fmt.threshold-power-sigma'.format(str(self.channel_dict[channel])) - threshold = self.provider.get(self.queue_dict[channel]+request)['threshold-power-sigma'] + threshold = self.get(self.queue_dict[channel]+request)['threshold-power-sigma'] return float(threshold) def set_fmt_sigma_high_threshold(self, threshold, channel='a'): request = '.active-config.{}.fmt.threshold-power-sigma-high'.format(str(self.channel_dict[channel])) - self.provider.set(self.queue_dict[channel]+request, threshold) + self.set(self.queue_dict[channel]+request, threshold) logger.info('Setting psyllid power sigma threshold to {}'.format(threshold)) def get_fmt_sigma_high_threshold(self, channel='a'): request = '.active-config.{}.fmt.threshold-power-sigma-high'.format(str(self.channel_dict[channel])) - threshold = self.provider.get(self.queue_dict[channel]+request)['threshold-power-sigma-high'] + threshold = self.get(self.queue_dict[channel]+request)['threshold-power-sigma-high'] return float(threshold) def _set_trigger_mode(self, mode_id, channel='a'): request = '.active-config.{}.fmt.trigger-mode'.format(str(self.channel_dict[channel])) - self.provider.set(self.queue_dict[channel]+request, mode_id) + self.set(self.queue_dict[channel]+request, mode_id) logger.info('Setting psyllid trigger mode to {}'.format(mode_id)) @@ -465,19 +468,19 @@ def set_trigger_mode(self, channel='a'): def get_trigger_mode(self, channel='a'): request = '.active-config.{}.fmt.trigger-mode'.format(str(self.channel_dict[channel])) - trigger_mode = self.provider.get(self.queue_dict[channel]+request)['trigger-mode'] + trigger_mode = self.get(self.queue_dict[channel]+request)['trigger-mode'] return trigger_mode def set_n_triggers(self, n_triggers, channel='a'): request = '.active-config.{}.eb.n-triggers'.format(str(self.channel_dict[channel])) - self.provider.set(self.queue_dict[channel]+request, n_triggers) + self.set(self.queue_dict[channel]+request, n_triggers) logger.info('Setting psyllid n-trigger/skip-tolerance to {}'.format(n_triggers)) def get_n_triggers(self, channel='a'): request = '.active-config.{}.eb.n-triggers'.format(str(self.channel_dict[channel])) - n_triggers = self.provider.get(self.queue_dict[channel]+request)['n-triggers'] + n_triggers = self.get(self.queue_dict[channel]+request)['n-triggers'] return n_triggers @@ -487,19 +490,19 @@ def make_trigger_mask(self, channel='a', filename='~/fmt_mask.json'): ''' if self.mode_dict[channel] != 'triggering': logger.error('Psyllid instance is not in triggering mode') - raise core.exceptions.DriplineGenericDAQError('Psyllid instance is not in triggering mode') + # raise core.exceptions.DriplineGenericDAQError('Psyllid instance is not in triggering mode') logger.info('Switch tf_roach_receiver to freq-only') request = 'run-daq-cmd.{}.tfrr.freq-only'.format(str(self.channel_dict[channel])) - self.provider.cmd(self.queue_dict[channel],request) + self.cmd(self.queue_dict[channel],request) logger.info('Switch frequency_mask_trigger to update-mask') request = 'run-daq-cmd.{}.fmt.update-mask'.format(str(self.channel_dict[channel])) - self.provider.cmd(self.queue_dict[channel],request) + self.cmd(self.queue_dict[channel],request) time.sleep(1) logger.info('Telling psyllid to not use monarch when starting next run') - self.provider.set(self.queue_dict[channel]+'.use-monarch', False) + self.set(self.queue_dict[channel]+'.use-monarch', False) logger.info('Start short run to record mask') self.start_run(channel ,1000, self.temp_file) @@ -508,15 +511,15 @@ def make_trigger_mask(self, channel='a', filename='~/fmt_mask.json'): # self._write_trigger_mask(channel, filename) logger.info('Telling psyllid to use monarch again for next run') - self.provider.set(self.queue_dict[channel]+'.use-monarch', True) + self.set(self.queue_dict[channel]+'.use-monarch', True) logger.info('Switch tf_roach_receiver back to time and freq') request = 'run-daq-cmd.{}.tfrr.time-and-freq'.format(str(self.channel_dict[channel])) - self.provider.cmd(self.queue_dict[channel],request) + self.cmd(self.queue_dict[channel],request) logger.info('Switch frequency mask trigger to apply-trigger') request = 'run-daq-cmd.{}.fmt.apply-trigger'.format(str(self.channel_dict[channel])) - self.provider.cmd(self.queue_dict[channel],request) + self.cmd(self.queue_dict[channel],request) def _write_trigger_mask(self, channel, filename): @@ -526,4 +529,4 @@ def _write_trigger_mask(self, channel, filename): logger.info('Write mask to file') request = 'run-daq-cmd.{}.fmt.write-mask'.format(str(self.channel_dict[channel])) payload = {'filename': filename} - self.provider.cmd(self.queue_dict[channel], request, payload=payload) + self.cmd(self.queue_dict[channel], request, payload=payload) From 4e3085d4a0c9348fcb46cbefb608cb5d9cc3dfd8 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Tue, 11 Feb 2025 12:58:53 -0800 Subject: [PATCH 24/55] modified/moved psyllid-provider --- .../extensions/psyllid_provider/__init__.py | 23 +++++++++++++++++++ .../psyllid_provider.py | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 dripline/extensions/psyllid_provider/__init__.py rename dripline/extensions/{roach2_daq => psyllid_provider}/psyllid_provider.py (99%) diff --git a/dripline/extensions/psyllid_provider/__init__.py b/dripline/extensions/psyllid_provider/__init__.py new file mode 100644 index 00000000..1f359c6c --- /dev/null +++ b/dripline/extensions/psyllid_provider/__init__.py @@ -0,0 +1,23 @@ +__all__ = [] + +import pkg_resources + +import scarab +a_ver = '0.0.0' #note that this is updated in the following block +try: + a_ver = pkg_resources.get_distribution('dragonfly').version + print('version is: {}'.format(a_ver)) +except: + print('fail!') + pass +version = scarab.VersionSemantic() +version.parse(a_ver) +version.package = 'project8/dragonfly' +version.commit = '---' +__all__.append("version") + +from .roach2_interface import * +from .roach2_interface import __all__ as __roach2_interface_all +from .r2daq import * +__all__ += __roach2_interface_all + diff --git a/dripline/extensions/roach2_daq/psyllid_provider.py b/dripline/extensions/psyllid_provider/psyllid_provider.py similarity index 99% rename from dripline/extensions/roach2_daq/psyllid_provider.py rename to dripline/extensions/psyllid_provider/psyllid_provider.py index 1f343132..e751fac8 100644 --- a/dripline/extensions/roach2_daq/psyllid_provider.py +++ b/dripline/extensions/psyllid_provider/psyllid_provider.py @@ -17,7 +17,7 @@ __all__.append('PsyllidProvider') -class PsyllidProvider(core.Interface): +class PsyllidProvider(core.Service): ''' Provider for direct communication with up to 3 Psyllid instances with a single stream each ''' @@ -28,7 +28,7 @@ def __init__(self, temp_file = '/tmp/empty_egg_file.egg', **kwargs): - core.Interface.__init__(self, **kwargs) + core.Service.__init__(self, **kwargs) self._set_condition_list = set_condition_list self.queue_dict = queue_dict self.channel_dict = channel_dict From 6e4177a9e4ee30061a486fcfc4dfd0a4ef4601c1 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Tue, 11 Feb 2025 13:12:35 -0800 Subject: [PATCH 25/55] Revert "modified/moved psyllid-provider" This reverts commit 4e3085d4a0c9348fcb46cbefb608cb5d9cc3dfd8. --- .../extensions/psyllid_provider/__init__.py | 23 ------------------- .../psyllid_provider.py | 4 ++-- 2 files changed, 2 insertions(+), 25 deletions(-) delete mode 100644 dripline/extensions/psyllid_provider/__init__.py rename dripline/extensions/{psyllid_provider => roach2_daq}/psyllid_provider.py (99%) diff --git a/dripline/extensions/psyllid_provider/__init__.py b/dripline/extensions/psyllid_provider/__init__.py deleted file mode 100644 index 1f359c6c..00000000 --- a/dripline/extensions/psyllid_provider/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -__all__ = [] - -import pkg_resources - -import scarab -a_ver = '0.0.0' #note that this is updated in the following block -try: - a_ver = pkg_resources.get_distribution('dragonfly').version - print('version is: {}'.format(a_ver)) -except: - print('fail!') - pass -version = scarab.VersionSemantic() -version.parse(a_ver) -version.package = 'project8/dragonfly' -version.commit = '---' -__all__.append("version") - -from .roach2_interface import * -from .roach2_interface import __all__ as __roach2_interface_all -from .r2daq import * -__all__ += __roach2_interface_all - diff --git a/dripline/extensions/psyllid_provider/psyllid_provider.py b/dripline/extensions/roach2_daq/psyllid_provider.py similarity index 99% rename from dripline/extensions/psyllid_provider/psyllid_provider.py rename to dripline/extensions/roach2_daq/psyllid_provider.py index e751fac8..1f343132 100644 --- a/dripline/extensions/psyllid_provider/psyllid_provider.py +++ b/dripline/extensions/roach2_daq/psyllid_provider.py @@ -17,7 +17,7 @@ __all__.append('PsyllidProvider') -class PsyllidProvider(core.Service): +class PsyllidProvider(core.Interface): ''' Provider for direct communication with up to 3 Psyllid instances with a single stream each ''' @@ -28,7 +28,7 @@ def __init__(self, temp_file = '/tmp/empty_egg_file.egg', **kwargs): - core.Service.__init__(self, **kwargs) + core.Interface.__init__(self, **kwargs) self._set_condition_list = set_condition_list self.queue_dict = queue_dict self.channel_dict = channel_dict From 2a9b664c56c8ba3352a27a16002dd223b45c582d Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Tue, 11 Feb 2025 13:13:58 -0800 Subject: [PATCH 26/55] Revert "Merge branch 'develop' into feature/cca_daq" This reverts commit e8563411ebfb595bf0e8c0a1212cedac504660c5, reversing changes made to 4e3085d4a0c9348fcb46cbefb608cb5d9cc3dfd8. --- .github/workflows/build.yaml | 29 ++++++++---------- Dockerfile | 8 ++--- docker-compose.yaml | 42 -------------------------- dragonfly/__init__.py | 17 ----------- dripline/extensions/__init__.py | 8 ----- dripline/extensions/add_auth_spec.py | 37 ----------------------- dripline/extensions/jitter/__init__.py | 19 ++++++++++++ dripline_mesh.yaml | 1 - examples/jitter_example.yaml | 7 ----- examples/key-value-store.yaml | 21 ------------- jitter_example.yml | 9 ++++++ setup.py | 4 +-- 12 files changed, 47 insertions(+), 155 deletions(-) delete mode 100644 docker-compose.yaml delete mode 100644 dragonfly/__init__.py delete mode 100644 dripline/extensions/add_auth_spec.py delete mode 100644 dripline_mesh.yaml delete mode 100644 examples/jitter_example.yaml delete mode 100644 examples/key-value-store.yaml create mode 100644 jitter_example.yml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 86dbaa33..16d06bd2 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -10,7 +10,7 @@ on: env: BASE_IMAGE_USER: driplineorg BASE_IMAGE_REPO: dripline-python - BASE_IMAGE_VER: 'develop' + BASE_IMAGE_VER: 'v4.5.8' REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} @@ -24,20 +24,20 @@ jobs: steps: - name: Checkout the repo - uses: actions/checkout@v4 + uses: actions/checkout@v2 with: submodules: recursive - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx id: setup_buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v2 - name: Build id: build - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v3 env: BASE_IMAGE_TAG: ${{ env.BASE_IMAGE_VER }}-dev with: @@ -81,13 +81,13 @@ jobs: steps: - name: Checkout the repo - uses: actions/checkout@v4 + uses: actions/checkout@v2 with: submodules: recursive - name: Docker meta id: docker_meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v3 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} flavor: | @@ -99,16 +99,16 @@ jobs: type=ref,event=pr - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx id: setup_buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v2 with: buildkitd-flags: --debug - name: Log in to the Container registry - uses: docker/login-action@v3 + uses: docker/login-action@v2 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -116,7 +116,7 @@ jobs: - name: Build and push id: build_push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v3 env: BASE_IMAGE_TAG: ${{ env.BASE_IMAGE_VER }}${{ matrix.tag-suffix }} with: @@ -127,8 +127,5 @@ jobs: img_repo=${{ env.BASE_IMAGE_REPO }} img_tag=${{ env.BASE_IMAGE_TAG }} tags: ${{ steps.docker_meta.outputs.tags }} - platforms: linux/amd64,linux/arm64,linux/arm/v7 - - - name: Release - uses: softprops/action-gh-release@v2 - if: ${{ github.event_name == 'push' && contains(github.ref, 'refs/tags/') }} + platforms: linux/amd64 +# platforms: linux/amd64,linux/arm/v7,linux/arm64 diff --git a/Dockerfile b/Dockerfile index 2287f91b..2137bc7f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,12 @@ -ARG img_user=ghcr.io/driplineorg +ARG img_user=driplineorg ARG img_repo=dripline-python -ARG img_tag=develop-dev +ARG img_tag=v4.5.8 FROM ${img_user}/${img_repo}:${img_tag} -COPY . /usr/local/src_dragonfly +COPY . /usr/local/src/dragonfly -WORKDIR /usr/local/src_dragonfly +WORKDIR /usr/local/src/dragonfly RUN pip install . WORKDIR / diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 9ff66ead..00000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,42 +0,0 @@ -services: - - # The broker for the mesh - rabbit-broker: - image: rabbitmq:3-management - ports: - - "15672:15672" - environment: - - RABBITMQ_DEFAULT_USER=dripline - - RABBITMQ_DEFAULT_PASS=dripline - healthcheck: - test: ["CMD-SHELL", "curl -u dripline:dripline http://rabbit-broker:15672/api/overview &> /dev/null || exit 1"] - - # The classic key-value store, a configuration based on the base Service class - key-value-store: - image: ghcr.io/project8/dragonfly:${DGFLY_IMG_TAG:-latest-dev} - depends_on: - rabbit-broker: - condition: service_healthy - volumes: - - ./examples/key-value-store.yaml:/root/key-value-store.yaml - - ./dripline_mesh.yaml:/root/.dripline_mesh.yaml - environment: - - DRIPLINE_USER=dripline - - DRIPLINE_PASSWORD=dripline - command: > - bash -c "dl-serve -vv -c /root/key-value-store.yaml" - - # The classic key-value-store service with a jitter endpoint - jitter: - image: ghcr.io/project8/dragonfly:${DGFLY_IMG_TAG:-latest-dev} - depends_on: - rabbit-broker: - condition: service_healthy - volumes: - - ./examples/jitter_example.yaml:/root/jitter_example.yaml - - ./dripline_mesh.yaml:/root/.dripline_mesh.yaml - environment: - - DRIPLINE_USER=dripline - - DRIPLINE_PASSWORD=dripline - command: > - bash -c "dl-serve -vv -c /root/jitter_example.yaml" diff --git a/dragonfly/__init__.py b/dragonfly/__init__.py deleted file mode 100644 index ae9daaf1..00000000 --- a/dragonfly/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -import logging -logger = logging.getLogger(__name__) - -def __get_version(): - import scarab - import dragonfly - import pkg_resources - #TODO: this all needs to be populated from setup.py and gita - version = scarab.VersionSemantic() - logger.info('version should be: {}'.format(pkg_resources.get_distribution('dragonfly').version)) - version.parse(pkg_resources.get_distribution('dragonfly').version) - version.package = 'project8/dragonfly' - version.commit = 'na' - dragonfly.core.add_version('dragonfly', version) - return version -version = __get_version() -__version__ = version.version diff --git a/dripline/extensions/__init__.py b/dripline/extensions/__init__.py index b62df1ed..69e3be50 100644 --- a/dripline/extensions/__init__.py +++ b/dripline/extensions/__init__.py @@ -1,9 +1 @@ -__all__ = [] - __path__ = __import__('pkgutil').extend_path(__path__, __name__) - -# Subdirectories -from . import jitter - -# Modules in this directory -from .add_auth_spec import * diff --git a/dripline/extensions/add_auth_spec.py b/dripline/extensions/add_auth_spec.py deleted file mode 100644 index 18af20ac..00000000 --- a/dripline/extensions/add_auth_spec.py +++ /dev/null @@ -1,37 +0,0 @@ -''' -Contains the AddAuthSpec class, for adding authentication specifications -''' - -import dripline.implementations -import scarab - -import logging - -logger = logging.getLogger(__name__) - -__all__ = [] - -__all__.append('AddAuthSpec') -class AddAuthSpec(dripline.implementations.BaseAddAuthSpec): - ''' - - ''' - - def __init__(self, app): - ''' - ''' - dripline.implementations.BaseAddAuthSpec.__init__(self, app) - self.add_slack_auth_spec(app) - - def add_slack_auth_spec(self, app): - ''' - Adds the Slack authenticaiton specification to a scarab::main_app object - ''' - auth_spec = { - 'dripline': { - 'default': 'default-token', - 'env': 'DRIPLINE_SLACK_TOKEN', - }, - } - app.add_default_auth_spec_group( 'slack', scarab.to_param(auth_spec).as_node() ) - logger.debug('Added slack auth spec') diff --git a/dripline/extensions/jitter/__init__.py b/dripline/extensions/jitter/__init__.py index 82eeaf13..ab3554f4 100644 --- a/dripline/extensions/jitter/__init__.py +++ b/dripline/extensions/jitter/__init__.py @@ -1,3 +1,22 @@ __all__ = [] +import pkg_resources + +import scarab +a_ver = '0.0.0' #note that this is updated in the following block +try: + a_ver = pkg_resources.get_distribution('dragonfly').version + print('version is: {}'.format(a_ver)) +except: + print('fail!') + pass +version = scarab.VersionSemantic() +version.parse(a_ver) +version.package = 'project8/dragonfly' +version.commit = '---' +__all__.append("version") + from .jitter_endpoint import * +from .jitter_endpoint import __all__ as __jitter_all +__all__ += __jitter_all + diff --git a/dripline_mesh.yaml b/dripline_mesh.yaml deleted file mode 100644 index ce34d787..00000000 --- a/dripline_mesh.yaml +++ /dev/null @@ -1 +0,0 @@ -broker: rabbit-broker diff --git a/examples/jitter_example.yaml b/examples/jitter_example.yaml deleted file mode 100644 index 378e1a28..00000000 --- a/examples/jitter_example.yaml +++ /dev/null @@ -1,7 +0,0 @@ -name: jitter-store -module: Service -endpoints: - - name: jitter-peaches - module: JitterEntity - calibration: '2*{}' - initial_value: 0.75 diff --git a/examples/key-value-store.yaml b/examples/key-value-store.yaml deleted file mode 100644 index 8b265304..00000000 --- a/examples/key-value-store.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: my_store -module: Service -endpoints: - - name: peaches - module: KeyValueStore - calibration: '2*{}' - initial_value: 0.75 - log_interval: 10 - get_on_set: True - log_on_set: True - - name: chips - module: KeyValueStore - calibration: 'times3({})' - initial_value: 1.75 - - name: waffles - module: KeyValueStore - #log_interval: 30 - #log_on_set: True - calibration: '1.*{}' - initial_value: 4.00 - diff --git a/jitter_example.yml b/jitter_example.yml new file mode 100644 index 00000000..c9bc9335 --- /dev/null +++ b/jitter_example.yml @@ -0,0 +1,9 @@ +runtime-config: + name: my_store + module: Service + auth_file: /root/auths.json + endpoints: + - name: peaches + module: JitterEntity + calibration: '2*{}' + initial_value: 0.75 diff --git a/setup.py b/setup.py index adbc55de..3ef1d71c 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,10 @@ from setuptools import setup, find_namespace_packages -packages = find_namespace_packages('.', include=['dragonfly', 'dripline.extensions', 'dripline.extensions.*']) +packages = find_namespace_packages('.', include=['dripline.extensions.*']) print('packages are: {}'.format(packages)) setup( name="dragonfly", - version='v2.0.1', # TODO: should get version from git + version='v2.0.0', packages=packages, ) From 3faa9b447d99511f42496331a18c35c8f5b7b02e Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 28 Feb 2025 10:11:13 -0800 Subject: [PATCH 27/55] reorganized files. Updated psyllid_provider to use the mixin. --- .../extensions/psyllid_provider/__init__.py | 22 +++ .../psyllid_provider.py | 125 +++++++++--------- .../__init__.py | 0 .../{roach2_daq => roach2_interface}/r2daq.py | 0 .../roach2_interface.py | 4 +- .../roach_daq_run_interface.py | 0 6 files changed, 87 insertions(+), 64 deletions(-) create mode 100644 dripline/extensions/psyllid_provider/__init__.py rename dripline/extensions/{roach2_daq => psyllid_provider}/psyllid_provider.py (76%) rename dripline/extensions/{roach2_daq => roach2_interface}/__init__.py (100%) rename dripline/extensions/{roach2_daq => roach2_interface}/r2daq.py (100%) rename dripline/extensions/{roach2_daq => roach2_interface}/roach2_interface.py (99%) rename dripline/extensions/{roach2_daq => roach2_interface}/roach_daq_run_interface.py (100%) diff --git a/dripline/extensions/psyllid_provider/__init__.py b/dripline/extensions/psyllid_provider/__init__.py new file mode 100644 index 00000000..025c6f20 --- /dev/null +++ b/dripline/extensions/psyllid_provider/__init__.py @@ -0,0 +1,22 @@ +__all__ = [] + +import pkg_resources + +import scarab +a_ver = '0.0.0' #note that this is updated in the following block +try: + a_ver = pkg_resources.get_distribution('dragonfly').version + print('version is: {}'.format(a_ver)) +except: + print('fail!') + pass +version = scarab.VersionSemantic() +version.parse(a_ver) +version.package = 'project8/dragonfly' +version.commit = '---' +__all__.append("version") + +from .psyllid_provider import * +from .psyllid_provider import __all__ as __psyllid_provider_all +__all__ += __psyllid_provider_all + diff --git a/dripline/extensions/roach2_daq/psyllid_provider.py b/dripline/extensions/psyllid_provider/psyllid_provider.py similarity index 76% rename from dripline/extensions/roach2_daq/psyllid_provider.py rename to dripline/extensions/psyllid_provider/psyllid_provider.py index 1f343132..84df2192 100644 --- a/dripline/extensions/roach2_daq/psyllid_provider.py +++ b/dripline/extensions/psyllid_provider/psyllid_provider.py @@ -17,7 +17,7 @@ __all__.append('PsyllidProvider') -class PsyllidProvider(core.Interface): +class PsyllidProvider(core.Service): ''' Provider for direct communication with up to 3 Psyllid instances with a single stream each ''' @@ -28,7 +28,7 @@ def __init__(self, temp_file = '/tmp/empty_egg_file.egg', **kwargs): - core.Interface.__init__(self, **kwargs) + core.Service.__init__(self, **kwargs) self._set_condition_list = set_condition_list self.queue_dict = queue_dict self.channel_dict = channel_dict @@ -39,6 +39,7 @@ def __init__(self, self.temp_file = temp_file + def check_all_psyllid_instances(self): ''' Populates all dictionaries by checking the configuarions of all psyllid instances @@ -97,18 +98,18 @@ def active_channels(self, x): def get_active_config(self, channel, key): - target = '{}.active-config.{}.{}'.format(self.queue_dict[channel], - str(self.channel_dict[channel]), - key) - return self.get(target) + target = 'active-config.{}.{}'.format(str(self.channel_dict[channel]), + key) + reply = self.get(endpoint=self.queue_dict[channel], specifier=target) + return reply def get_acquisition_mode(self, channel): ''' Tests whether psyllid is in streaming or triggering mode ''' - request = '{}.node-list.{}'.format(self.queue_dict[channel], self.channel_dict[channel]) - node_list = self.get(request)['nodes'] + request = 'node-list.{}'.format(self.channel_dict[channel]) + node_list = self.get(endpoint=self.queue_dict[channel], specifier=request)['nodes'] if 'trw' in node_list: self.mode_dict[channel] = 'triggering' @@ -124,7 +125,7 @@ def request_status(self, channel): Asks the psyllid instance what state it is in and returns that state ''' logger.info('Checking Psyllid status of channel {}'.format(channel)) - result = self.get(self.queue_dict[channel]+'.daq-status', timeout=5) + result = self.get(endpoint=self.queue_dict[channel], specifier='daq-status', timeout_s=5) self.status_dict[channel] = result['server']['status'] self.status_value_dict[channel] = result['server']['status-value'] logger.info('Psyllid is running. Status is {}'.format(self.status_dict[channel])) @@ -139,7 +140,7 @@ def activate(self, channel): self.request_status(channel) if self.status_value_dict[channel] != 4: logger.info('Activating Psyllid instance for channel {}'.format(channel)) - self.cmd(self.queue_dict[channel], 'activate-daq') + self.cmd(endpoint=self.queue_dict[channel], specifier='activate-daq') time.sleep(1) self.request_status(channel) if self.status_value_dict[channel]!=4: @@ -157,7 +158,7 @@ def deactivate(self, channel): self.request_status(channel) if self.status_value_dict[channel] != 0: logger.info('Deactivating Psyllid instance of channel {}'.format(channel)) - self.cmd(self.queue_dict[channel],'deactivate-daq') + self.cmd(endpoint=self.queue_dict[channel], specifier='deactivate-daq') time.sleep(1) self.request_status(channel) if self.status_value_dict[channel]!=0: @@ -175,7 +176,7 @@ def reactivate(self, channel): self.request_status(channel) if self.status_value_dict[channel] == 4: logger.info('Reactivating Psyllid instance of channel {}'.format(channel)) - self.cmd(self.queue_dict[channel], 'reactivate-daq') + self.cmd(endpoint=self.queue_dict[channel], specifier='reactivate-daq') time.sleep(2) self.request_status(channel) if self.status_value_dict[channel]!=4: @@ -214,7 +215,7 @@ def quit_psyllid(self, channel): ''' Tells psyllid to quit ''' - self.cmd(self.queue_dict[channel], 'quit-psyllid') + self.cmd(endpoint=self.queue_dict[channel], specifier='quit') logger.info('psyllid quit!') @@ -246,7 +247,7 @@ def get_central_frequency(self, channel): 'triggering':'trw' } request = 'active-config.{}.{}'.format(self.channel_dict[channel], routing_key_map[self.mode_dict[channel]]) - result = self.get(self.queue_dict[channel]+'.'+request) + result = self.get(endpoint=self.queue_dict[channel], specifier=request) logger.info('Psyllid says cf is {}'.format(result['center-freq'])) self.freq_dict[channel]=result['center-freq'] return self.freq_dict[channel] @@ -263,8 +264,8 @@ def set_central_frequency(self, channel, cf): 'streaming':'strw', 'triggering':'trw' } - request = '.active-config.{}.{}.center-freq'.format(self.channel_dict[channel], routing_key_map[self.mode_dict[channel]]) - self.set(self.queue_dict[channel]+request, cf) + request = 'active-config.{}.{}.center-freq'.format(self.channel_dict[channel], routing_key_map[self.mode_dict[channel]]) + self.set(endpoint=self.queue_dict[channel], specifier=request, value=cf) logger.info('Set central frequency of {} writer for channel {} to {} Hz'.format(self.mode_dict[channel], channel, cf)) self.get_central_frequency(channel) @@ -274,7 +275,7 @@ def is_psyllid_using_monarch(self, channel): Check psyllid is using monarch If it isn't psyllid cannot write files and runs will fail ''' - result = self.get(self.queue_dict[channel]+'.use-monarch')['values'][0] + result = self.get(endpoint=self.queue_dict[channel], specifier='use-monarch')['values'][0] logger.info('Psyllid channel {} is using monarch: {}'.format(channel, result)) return result @@ -285,7 +286,7 @@ def start_run(self, channel, duration, filename): Payload is run duration and egg-filename ''' payload = {'duration':duration, 'filename':filename} - self.cmd(self.queue_dict[channel], 'start-run', payload=payload) + self.cmd(endpoint=self.queue_dict[channel], specifier='start-run', keyed_args=payload) def stop_run(self, channel): @@ -294,7 +295,7 @@ def stop_run(self, channel): This method is for interrupting runs Runs stop automatically after the set duration and normally don't need to be stopped manually ''' - self.cmd(self.queue_dict[channel], 'stop-run') + self.cmd(endpoint=self.queue_dict[channel], specifier='stop-run') ################### @@ -361,8 +362,8 @@ def get_time_window(self, channel='a'): ############################################################## def set_threshold_type(self, snr_or_sigma, channel='a'): - request = '.active-config.{}.fmt.threshold-type'.format(str(self.channel_dict[channel])) - threshold_type = self.set(self.queue_dict[channel]+request, snr_or_sigma) + request = 'active-config.{}.fmt.threshold-type'.format(str(self.channel_dict[channel])) + threshold_type = self.set(endpoint=self.queue_dict[channel], specifier=request, value=snr_or_sigma) return threshold_type @@ -370,85 +371,85 @@ def get_threshold_type(self, channel='a'): ''' Returns string: 'snr' or 'sigma' ''' - request = '.active-config.{}.fmt.threshold-type'.format(str(self.channel_dict[channel])) - return self.get(self.queue_dict[channel]+request)['threshold-type'] + request = 'active-config.{}.fmt.threshold-type'.format(str(self.channel_dict[channel])) + return self.get(endpoint=self.queue_dict[channel], specifier=request)['threshold-type'] def set_pretrigger_time(self, pretrigger_time, channel='a'): n_pretrigger_packets = int(round(pretrigger_time/4.096e-5)) logger.info('Setting psyllid pretrigger to {} packets'.format(n_pretrigger_packets)) - request = '.node-config.{}.eb.pretrigger'.format(str(self.channel_dict[channel])) - self.set(self.queue_dict[channel]+request, n_pretrigger_packets) + request = 'node-config.{}.eb.pretrigger'.format(str(self.channel_dict[channel])) + self.set(endpoint=self.queue_dict[channel], specifier=request, value=n_pretrigger_packets) def get_pretrigger_time(self, channel='a'): - request = '.active-config.{}.eb.pretrigger'.format(str(self.channel_dict[channel])) - n_pretrigger_packets = self.get(self.queue_dict[channel]+request)['pretrigger'] + request = 'active-config.{}.eb.pretrigger'.format(str(self.channel_dict[channel])) + n_pretrigger_packets = self.get(endpoint=self.queue_dict[channel], specifier=request)['pretrigger'] return float(n_pretrigger_packets) * 4.096e-5 def set_skip_tolerance(self, skip_tolerance, channel='a'): n_skipped_packets = int(round(skip_tolerance/4.096e-5)) logger.info('Setting psyllid skip tolerance to {} packets'.format(n_skipped_packets)) - request = '.node-config.{}.eb.skip-tolerance'.format(str(self.channel_dict[channel])) - self.set(self.queue_dict[channel]+request, n_skipped_packets) + request = 'node-config.{}.eb.skip-tolerance'.format(str(self.channel_dict[channel])) + self.set(endpoint=self.queue_dict[channel], specifier=request, value=n_skipped_packets) def get_skip_tolerance(self, channel='a'): - request = '.active-config.{}.eb.skip-tolerance'.format(str(self.channel_dict[channel])) - n_skipped_packets = self.get(self.queue_dict[channel]+request)['skip-tolerance'] + request = 'active-config.{}.eb.skip-tolerance'.format(str(self.channel_dict[channel])) + n_skipped_packets = self.get(endpoint=self.queue_dict[channel], specifier=request)['skip-tolerance'] return float(n_skipped_packets) * 4.096e-5 def set_fmt_snr_threshold(self, threshold, channel='a'): - request = '.active-config.{}.fmt.threshold-power-snr'.format(str(self.channel_dict[channel])) - self.set(self.queue_dict[channel]+request, threshold) + request = 'active-config.{}.fmt.threshold-power-snr'.format(str(self.channel_dict[channel])) + self.set(endpoint=self.queue_dict[channel], specifier=request, value=threshold) logger.info('Setting psyllid power snr threshold to {}'.format(threshold)) def get_fmt_snr_threshold(self, channel='a'): - request = '.active-config.{}.fmt.threshold-power-snr'.format(str(self.channel_dict[channel])) - threshold = self.get(self.queue_dict[channel]+request)['threshold-power-snr'] + request = 'active-config.{}.fmt.threshold-power-snr'.format(str(self.channel_dict[channel])) + threshold = self.get(endpoint=self.queue_dict[channel], specifier=request)['threshold-power-snr'] return float(threshold) def set_fmt_snr_high_threshold(self, threshold, channel='a'): - request = '.active-config.{}.fmt.threshold-power-snr-high'.format(str(self.channel_dict[channel])) - self.set(self.queue_dict[channel]+request, threshold) + request = 'active-config.{}.fmt.threshold-power-snr-high'.format(str(self.channel_dict[channel])) + self.set(endpoint=self.queue_dict[channel], specifier=request, value=threshold) logger.info('Setting psyllid power snr threshold to {}'.format(threshold)) def get_fmt_snr_high_threshold(self, channel='a'): - request = '.active-config.{}.fmt.threshold-power-snr-high'.format(str(self.channel_dict[channel])) - threshold = self.get(self.queue_dict[channel]+request)['threshold-power-snr-high'] + request = 'active-config.{}.fmt.threshold-power-snr-high'.format(str(self.channel_dict[channel])) + threshold = self.get(endpoint=self.queue_dict[channel], specifier=request)['threshold-power-snr-high'] return float(threshold) def set_fmt_sigma_threshold(self, threshold, channel='a'): - request = '.active-config.{}.fmt.threshold-power-sigma'.format(str(self.channel_dict[channel])) - self.set(self.queue_dict[channel]+request, threshold) + request = 'active-config.{}.fmt.threshold-power-sigma'.format(str(self.channel_dict[channel])) + self.set(endpoint=self.queue_dict[channel], specifier=request, value=threshold) logger.info('Setting psyllid power sigma threshold to {}'.format(threshold)) def get_fmt_sigma_threshold(self, channel='a'): - request = '.active-config.{}.fmt.threshold-power-sigma'.format(str(self.channel_dict[channel])) - threshold = self.get(self.queue_dict[channel]+request)['threshold-power-sigma'] + request = 'active-config.{}.fmt.threshold-power-sigma'.format(str(self.channel_dict[channel])) + threshold = self.get(endpoint=self.queue_dict[channel], specifier=request)['threshold-power-sigma'] return float(threshold) def set_fmt_sigma_high_threshold(self, threshold, channel='a'): - request = '.active-config.{}.fmt.threshold-power-sigma-high'.format(str(self.channel_dict[channel])) - self.set(self.queue_dict[channel]+request, threshold) + request = 'active-config.{}.fmt.threshold-power-sigma-high'.format(str(self.channel_dict[channel])) + self.set(endpoint=self.queue_dict[channel], specifier=request, value=threshold) logger.info('Setting psyllid power sigma threshold to {}'.format(threshold)) def get_fmt_sigma_high_threshold(self, channel='a'): - request = '.active-config.{}.fmt.threshold-power-sigma-high'.format(str(self.channel_dict[channel])) - threshold = self.get(self.queue_dict[channel]+request)['threshold-power-sigma-high'] + request = 'active-config.{}.fmt.threshold-power-sigma-high'.format(str(self.channel_dict[channel])) + threshold = self.get(endpoint=self.queue_dict[channel], specifier=request)['threshold-power-sigma-high'] return float(threshold) def _set_trigger_mode(self, mode_id, channel='a'): - request = '.active-config.{}.fmt.trigger-mode'.format(str(self.channel_dict[channel])) - self.set(self.queue_dict[channel]+request, mode_id) + request = 'active-config.{}.fmt.trigger-mode'.format(str(self.channel_dict[channel])) + self.set(endpoint=self.queue_dict[channel], specifier=request, value=mode_id) logger.info('Setting psyllid trigger mode to {}'.format(mode_id)) @@ -467,20 +468,20 @@ def set_trigger_mode(self, channel='a'): def get_trigger_mode(self, channel='a'): - request = '.active-config.{}.fmt.trigger-mode'.format(str(self.channel_dict[channel])) - trigger_mode = self.get(self.queue_dict[channel]+request)['trigger-mode'] + request = 'active-config.{}.fmt.trigger-mode'.format(str(self.channel_dict[channel])) + trigger_mode = self.get(endpoint=self.queue_dict[channel], specifier=request)['trigger-mode'] return trigger_mode def set_n_triggers(self, n_triggers, channel='a'): - request = '.active-config.{}.eb.n-triggers'.format(str(self.channel_dict[channel])) - self.set(self.queue_dict[channel]+request, n_triggers) + request = 'active-config.{}.eb.n-triggers'.format(str(self.channel_dict[channel])) + self.set(endpoint=self.queue_dict[channel], specifier=request, value=n_triggers) logger.info('Setting psyllid n-trigger/skip-tolerance to {}'.format(n_triggers)) def get_n_triggers(self, channel='a'): - request = '.active-config.{}.eb.n-triggers'.format(str(self.channel_dict[channel])) - n_triggers = self.get(self.queue_dict[channel]+request)['n-triggers'] + request = 'active-config.{}.eb.n-triggers'.format(str(self.channel_dict[channel])) + n_triggers = self.get(endpoint=self.queue_dict[channel], specifier=request)['n-triggers'] return n_triggers @@ -494,15 +495,15 @@ def make_trigger_mask(self, channel='a', filename='~/fmt_mask.json'): logger.info('Switch tf_roach_receiver to freq-only') request = 'run-daq-cmd.{}.tfrr.freq-only'.format(str(self.channel_dict[channel])) - self.cmd(self.queue_dict[channel],request) + self.cmd(endpoint=self.queue_dict[channel], specifier=request) logger.info('Switch frequency_mask_trigger to update-mask') request = 'run-daq-cmd.{}.fmt.update-mask'.format(str(self.channel_dict[channel])) - self.cmd(self.queue_dict[channel],request) + self.cmd(endpoint=self.queue_dict[channel], specifier=request) time.sleep(1) logger.info('Telling psyllid to not use monarch when starting next run') - self.set(self.queue_dict[channel]+'.use-monarch', False) + self.set(endpoint=self.queue_dict[channel], specifier='use-monarch', value=False) logger.info('Start short run to record mask') self.start_run(channel ,1000, self.temp_file) @@ -511,15 +512,15 @@ def make_trigger_mask(self, channel='a', filename='~/fmt_mask.json'): # self._write_trigger_mask(channel, filename) logger.info('Telling psyllid to use monarch again for next run') - self.set(self.queue_dict[channel]+'.use-monarch', True) + self.set(endpoint=self.queue_dict[channel], specifier='use-monarch', value=True) logger.info('Switch tf_roach_receiver back to time and freq') request = 'run-daq-cmd.{}.tfrr.time-and-freq'.format(str(self.channel_dict[channel])) - self.cmd(self.queue_dict[channel],request) + self.cmd(endpoint=self.queue_dict[channel],specifier=request) logger.info('Switch frequency mask trigger to apply-trigger') request = 'run-daq-cmd.{}.fmt.apply-trigger'.format(str(self.channel_dict[channel])) - self.cmd(self.queue_dict[channel],request) + self.cmd(endpoint=self.queue_dict[channel],specifier=request) def _write_trigger_mask(self, channel, filename): @@ -529,4 +530,4 @@ def _write_trigger_mask(self, channel, filename): logger.info('Write mask to file') request = 'run-daq-cmd.{}.fmt.write-mask'.format(str(self.channel_dict[channel])) payload = {'filename': filename} - self.cmd(self.queue_dict[channel], request, payload=payload) + self.cmd(self.queue_dict[channel], request, keyed_args=payload) diff --git a/dripline/extensions/roach2_daq/__init__.py b/dripline/extensions/roach2_interface/__init__.py similarity index 100% rename from dripline/extensions/roach2_daq/__init__.py rename to dripline/extensions/roach2_interface/__init__.py diff --git a/dripline/extensions/roach2_daq/r2daq.py b/dripline/extensions/roach2_interface/r2daq.py similarity index 100% rename from dripline/extensions/roach2_daq/r2daq.py rename to dripline/extensions/roach2_interface/r2daq.py diff --git a/dripline/extensions/roach2_daq/roach2_interface.py b/dripline/extensions/roach2_interface/roach2_interface.py similarity index 99% rename from dripline/extensions/roach2_daq/roach2_interface.py rename to dripline/extensions/roach2_interface/roach2_interface.py index 392aba59..768a1ef6 100644 --- a/dripline/extensions/roach2_daq/roach2_interface.py +++ b/dripline/extensions/roach2_interface/roach2_interface.py @@ -28,7 +28,7 @@ __all__.append('Roach2Interface') -class Roach2Interface(ArtooDaq, core.Endpoint): +class Roach2Interface(ArtooDaq, core.Service): def __init__(self, roach2_hostname = 'led', @@ -45,7 +45,7 @@ def __init__(self, logger.debug("Roach2Interface __init__") - core.Endpoint.__init__(self, **kwargs) + core.Service.__init__(self, **kwargs) self.roach2_hostname = roach2_hostname diff --git a/dripline/extensions/roach2_daq/roach_daq_run_interface.py b/dripline/extensions/roach2_interface/roach_daq_run_interface.py similarity index 100% rename from dripline/extensions/roach2_daq/roach_daq_run_interface.py rename to dripline/extensions/roach2_interface/roach_daq_run_interface.py From f5c7af3bb9958485d852db12b3e248ab3ad074a0 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Sun, 2 Mar 2025 22:47:38 -0500 Subject: [PATCH 28/55] Dockerfile includes dependencies, cloning from git repos. Using dl3 getsetcmd-mixin as base image. Beginning to include and modify daq chain files. --- Dockerfile | 28 +- .../daq_run_interface.py | 259 ++++++++++++++++++ .../roach_daq_run_interface.py | 110 ++++---- 3 files changed, 338 insertions(+), 59 deletions(-) create mode 100644 dripline/extensions/roach2_daq_run_interface/daq_run_interface.py rename dripline/extensions/{roach2_interface => roach2_daq_run_interface}/roach_daq_run_interface.py (70%) diff --git a/Dockerfile b/Dockerfile index 2137bc7f..74b57faa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,28 @@ -ARG img_user=driplineorg -ARG img_repo=dripline-python -ARG img_tag=v4.5.8 +ARG img_user=ghcr.io +ARG img_repo=driplineorg/dripline-python +ARG img_tag=getsetcmd-mixin-test -FROM ${img_user}/${img_repo}:${img_tag} +FROM ${img_user}/${img_repo}:${img_tag} AS base + +FROM base AS deps +# installing dependencies +RUN pip install numpy==1.26.4 &&\ + pip install scipy==1.14.1 &&\ + pip install backports.ssl_match_hostname==3.7.0.1 &&\ + pip install katcp==0.9.3 &&\ + cd /usr/local &&\ + git clone https://github.com/pkolbeck/adc_tests.git &&\ + cd adc_tests &&\ + git checkout master &&\ + pip install . &&\ + cd /tmp &&\ + git clone https://github.com/pkolbeck/corr.git &&\ + cd corr &&\ + git checkout p8/r2daq_only &&\ + cp -r corr /usr/local/corr &&\ + export PYTHONPATH=${PYTHONPATH}:/usr/local/corr + +FROM deps AS build COPY . /usr/local/src/dragonfly diff --git a/dripline/extensions/roach2_daq_run_interface/daq_run_interface.py b/dripline/extensions/roach2_daq_run_interface/daq_run_interface.py new file mode 100644 index 00000000..f27cb94b --- /dev/null +++ b/dripline/extensions/roach2_daq_run_interface/daq_run_interface.py @@ -0,0 +1,259 @@ +''' +A service for uniform interfacing with the DAQ (in general) +''' + +from __future__ import absolute_import + + +# standard imports +import logging +from datetime import datetime + +# internal imports +from dripline import core + +__all__ = [] + +logger = logging.getLogger(__name__) + +__all__.append('DAQProvider') + +@core.fancy_doc +class DAQProvider(core.Service): + ''' + Base class for providing a uniform interface to different DAQ systems + ''' + def __init__(self, + daq_name=None, + run_table_endpoint=None, + directory_path=None, + data_directory_path=None, + meta_data_directory_path=None, + filename_prefix='', + snapshot_state_target='', + metadata_state_target='', + metadata_target='', + set_condition_list = [10], + **kwargs): + ''' + daq_name (str): name of the DAQ (used with the run table and in metadata) + run_table_endpoint (str): name of the endpoint providing an interface to the run table + directory_path (str): absolute path to "hot" storage (as seen from the DAQ software, not a network path) + meta_data_directory_path (str): path where the metadata file should be written + filename_prefix (str): prefix for unique filenames + snapshot_state_target (str): target to request snapshot from + metadata_state_target (str): multiget endpoint to Get() for system state + metadata_target (str): target to send metadata to + ''' + core.Service.__init__(self, **kwargs) + + if daq_name is None: + raise core.exceptions.DriplineValueError('<{}> instance <{}> requires a value for "{}" to initialize'.format(self.__class__.__name__, self.name, 'daq_name')) + else: + self.daq_name = daq_name + if run_table_endpoint is None: + raise core.exceptions.DriplineValueError('<{}> instance <{}> requires a value for "{}" to initialize'.format(self.__class__.__name__, self.name, 'run_table_endpoint')) + else: + self.run_table_endpoint = run_table_endpoint + + # deal with directory structures + if (directory_path is None) and (data_directory_path is None) and (meta_data_directory_path is None): + raise core.exceptions.DriplineValueError('<{}> instance <{}> requires a value for "{}" to initialize'.format(self.__class__.__name__, self.name, '[meta_[data_]]directory_path')) + if (data_directory_path is None) and (directory_path is not None): + data_directory_path = directory_path + if (meta_data_directory_path is None) and (directory_path is not None): + meta_data_directory_path = directory_path + self.data_directory_path = data_directory_path + self.meta_data_directory_path = meta_data_directory_path + + self._metadata_state_target = metadata_state_target + self._metadata_target = metadata_target + self._snapshot_state_target = snapshot_state_target + self.filename_prefix = filename_prefix + + self._stop_handle = None + self._run_name = None + self.run_id = None + self._start_time = None + self._run_meta = None + self._run_snapshot = None + self._run_time = None + + # Set condition and DAQ safe mode init + self._daq_in_safe_mode = False + self._set_condition_list = set_condition_list + + @property + def run_name(self): + return self._run_name + @run_name.setter + def run_name(self, value): + ''' + inserts run name to run table in database and retrieves run id and start timestamp + + value (str): name of acquisition run + ''' + self._run_name = value + try: + result = self.cmd(endpoint=self.run_table_endpoint, specifier='do_insert', keyed_args={'run_name':value}) + self.run_id = result['run_id'] + self._start_time = result['start_timestamp'] + except Exception as err: + if self._stop_handle is not None: # end the run + self.service._connection.remove_timeout(self._stop_handle) + self._stop_handle = None + self._run_name = None + self.run_id = None + raise core.exceptions.DriplineValueError('failed to insert run_name to the db, obtain run_id, and start_timestamp. run "<{}>" not started\nerror:\n{}'.format(value,str(err))) + + def start_run(self, run_name): + ''' + Do the prerun_gets and send the metadata to the recording associated computer + + run_name (str): name of acquisition run + ''' + self.run_name = run_name + self._run_meta = {'DAQ': self.daq_name, + 'run_time': self._run_time, + 'run_id': self.run_id + } + + self._do_prerun_gets() + self._send_metadata(type='meta', data=self._run_meta) + logger.debug('these meta will be {}'.format(self._run_meta)) + logger.info('start_run finished') + + def end_run(self): + ''' + Send command to the DAQ provider to stop data taking, do the post-run snapshot, and announce the end of the run. + ''' + # call _stop_data_taking DAQ-specific method + self._stop_data_taking() + + if self._stop_handle is not None: + logger.info("Removing sec timeout for run <{}> duration".format(self.run_id)) + self.service._connection.remove_timeout(self._stop_handle) + self._stop_handle = None + if self.run_id is None: + raise core.exceptions.DriplineValueError("No run to end: run_id is None.") + self._do_snapshot() + logger.info('run <{}> ended'.format(self.run_id)) + self._run_name = None + self.run_id = None + + def _do_prerun_gets(self): + ''' + Calls pre-run methods to obtain run metadata + ''' + logger.info('doing prerun meta-data get') + meta_result = self.get(endpoint=self._metadata_state_target, timeout=30) + self._run_meta.update(meta_result['value_raw']) + self._run_meta.update({'DAQ_MODE':getattr(self,'acquisition_mode')}) + self.determine_RF_ROI() + + def _do_snapshot(self): + ''' + Calls take_snapshot method of snapshot target for database snapshot + ''' + logger.info('requesting snapshot of database') + filename = '{directory}/{runNyx:03d}yyyxxx/{runNx:06d}xxx/{runN:09d}/{prefix}{runN:09d}_snapshot.json'.format( + directory=self.meta_data_directory_path, + prefix=self.filename_prefix, + runNyx=self.run_id/1000000, + runNx=self.run_id/1000, + runN=self.run_id + ) + time_now = datetime.utcnow().strftime(core.constants.TIME_FORMAT) + self.cmd(endpoint=self._snapshot_state_target, + specifier='take_snapshot', + ordered_args=[self._start_time,time_now,self._metadata_target,filename], + timeout=30) + logger.info('snapshot returned ok') + + def determine_RF_ROI(self): + raise core.exceptions.DriplineMethodNotSupportedError('subclass must implement RF ROI determination') + + def _send_metadata(self, type, data): + ''' + Sends metadata to metadata target (mdreceiver) to be written to file(s) + ''' + logger.info('metadata {} should broadcast'.format(type)) + filename = '{directory}/{runNyx:03d}yyyxxx/{runNx:06d}xxx/{runN:09d}/{prefix}{runN:09d}_{type}.json'.format( + directory=self.meta_data_directory_path, + prefix=self.filename_prefix, + runNyx=self.run_id/1000000, + runNx=self.run_id/1000, + runN=self.run_id, + type=type + ) + this_payload = {'contents': data, + 'filename': filename, + } + self.cmd(endpoint=self._metadata_target, specifier='write_json', keyed_args=this_payload) + logger.debug('metadata sent') + + def start_timed_run(self, run_name, run_time): + ''' + Starts timed acquisition run + run_name (str): name of acquisition run + run_time (int): length of run in seconds + ''' + self._run_time = int(run_time) + if self._daq_in_safe_mode: + logger.info("DAQ in safe mode") + raise core.exceptions.DriplineDAQNotEnabled("{} is not enabled: enable it using ".format(self.daq_name)) + + logger.debug('testing if the DAQ is running') + result = self.is_running + if result == True: + raise core.exceptions.DriplineDAQRunning('DAQ is already running: aborting run') + + # do the last minutes checks: DAQ specific + self._do_checks() + + # get run_id and do pre_run gets + self.start_run(run_name) + + # call start_run method in daq_target + directory = '{base}/{runNyx:03d}yyyxxx/{runNx:06d}xxx/{runN:09d}'.format( + base=self.data_directory_path, + runNyx=self.run_id/1000000, + runNx=self.run_id/1000, + runN=self.run_id + ) + + filename = "{}{:09d}".format(self.filename_prefix, self.run_id) + self._start_data_taking(directory,filename) + logger.info("Adding {} sec timeout for run <{}> duration".format(self._run_time, self.run_id)) + self._stop_handle = self.service._connection.add_timeout(self._run_time, self.end_run) + return self.run_id + + @property + def is_running(self): + raise core.exceptions.DriplineMethodNotSupportedError('subclass must implement is running method') + + def _do_checks(self): + raise core.exceptions.DriplineMethodNotSupportedError('subclass must implement checks methods') + + def _start_data_taking(self,directory,filename): + raise core.exceptions.DriplineMethodNotSupportedError('subclass must implement start data taking method') + + def _stop_data_taking(self): + raise core.exceptions.DriplineMethodNotSupportedError('subclass must implement stop data taking method') + + def _set_condition(self,number): + ''' + Puts/Removes DAQ in safe mode + number (int): 0 (leave safe mode) or e.g 10 for global DAQ stop or 11 for RSA DAQ stop + ''' + logger.debug('receiving a set_condition {} request'.format(number)) + if number in self._set_condition_list: + logger.debug('putting myself in safe_mode') + self._daq_in_safe_mode = True + logger.critical('Condition {} reached! DAQ in safe mode!'.format(number)) + elif number == 0: + logger.debug('getting out of safe_mode') + self._daq_in_safe_mode = False + logger.critical('Condition {} reached! Not in safe mode.'.format(number)) + else: + logger.debug('condition {} is unknown: ignoring!'.format(number)) diff --git a/dripline/extensions/roach2_interface/roach_daq_run_interface.py b/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py similarity index 70% rename from dripline/extensions/roach2_interface/roach_daq_run_interface.py rename to dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py index d9df4b12..19159eba 100644 --- a/dripline/extensions/roach2_interface/roach_daq_run_interface.py +++ b/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py @@ -76,9 +76,9 @@ def _check_roach2_is_ready(self): Asks roach2_interface whether roach is running and adc is calibrated ''' logger.info('Checking ROACH2 status') - result = self.provider.get(self.daq_target+ '.is_running')['values'][0] + result = self.get(endpoint=self.daq_target+ '.is_running')['values'][0] if result==True: - result = self.provider.get(self.daq_target+'.calibration_status')['values'][0] + result = self.get(endpoint=self.daq_target+'.calibration_status')['values'][0] if result == True: logger.info('ROACH2 is running and ADCs are calibrated') return True @@ -100,11 +100,11 @@ def _check_psyllid_instance(self): ''' logger.info('Checking Psyllid service & instance') - self.status_value = self.provider.cmd(self.psyllid_interface, 'request_status', payload = self.payload_channel)['values'][0] + self.status_value = self.cmd(endpoint=self.psyllid_interface, specifier='request_status', keyed_args = self.payload_channel)['values'][0] if self.status_value == 0: - self.provider.cmd(self.psyllid_interface, 'activate', payload = self.payload_channel) - self.status_value = self.provider.cmd(self.psyllid_interface, 'request_status', payload = self.payload_channel)['values'][0] + self.cmd(endpoint=self.psyllid_interface, specifier='activate', keyed_args = self.payload_channel) + self.status_value = self.cmd(endpoint=self.psyllid_interface, specifier='request_status', keyed_args = self.payload_channel)['values'][0] @property @@ -113,7 +113,7 @@ def is_running(self): Requests status of psyllid Returns True if status is 5 (currently taking data) ''' - result = self.provider.cmd(self.psyllid_interface, 'request_status', payload = self.payload_channel, timeout=10) + result = self.cmd(endpoint=self.psyllid_interface, specifier='request_status', keyed_args = self.payload_channel, timeout=10) self.status_value = result['values'][0] logger.info('psyllid status is {}'.format(self.status_value)) if self.status_value==5: @@ -138,7 +138,7 @@ def _do_checks(self): raise core.exceptions.DriplineGenericDAQError('Psyllid DAQ is not activated') # check psyllid is ready to write a file - result = self.provider.cmd(self.psyllid_interface, 'is_psyllid_using_monarch', payload = self.payload_channel)['values'][0] + result = self.cmd(endpoint=self.psyllid_interface, specifier='is_psyllid_using_monarch', keyed_args = self.payload_channel)['values'][0] if result != True: raise core.exceptions.DriplineGenericDAQError('Psyllid is not using monarch and therefore not ready to write a file') @@ -147,7 +147,7 @@ def _do_checks(self): raise core.exceptions.DriplineGenericDAQError('ROACH2 is not ready. ADC not calibrated.') # check channel is unblocked - blocked_channels = self.provider.get(self.daq_target+ '.blocked_channels') + blocked_channels = self.get(endpoint=self.daq_target+ '.blocked_channels') if self.channel_id in blocked_channels: raise core.exceptions.DriplineGenericDAQError('Channel is blocked') @@ -168,7 +168,7 @@ def determine_RF_ROI(self): ''' logger.info('trying to determine roi') - rf_input = self.provider.get(self._hf_lo_freq['endpoint_name'])[self._hf_lo_freq['payload_field']] + rf_input = self.get(endpoint=self._hf_lo_freq['endpoint_name'])[self._hf_lo_freq['payload_field']] logger.debug('{} returned {}'.format(self._hf_lo_freq['endpoint_name'],rf_input)) hf_lo_freq = float(self._hf_lo_freq['calibration'][rf_input]) self._run_meta['RF_HF_MIXING'] = hf_lo_freq @@ -195,7 +195,7 @@ def _start_data_taking(self, directory, filename): Unblocks roach channels if that fails ''' - setup = { 'roach' : self.provider.get('{}.registers'.format(self.daq_target)) } + setup = { 'roach' : self.get(endpoint='{}.registers'.format(self.daq_target)) } psyllid_config_kwargs = { 'target' : self.psyllid_interface, 'method_name' : 'get_active_config', @@ -203,18 +203,18 @@ def _start_data_taking(self, directory, filename): 'key' : 'prf' } } setup.update( { 'psyllid' : - { 'prf' : self.provider.cmd(**psyllid_config_kwargs) } } ) + { 'prf' : self.cmd(endpoint=**psyllid_config_kwargs) } } ) if self.acquisition_mode == 'triggering': payload = {'channel':self.channel_id, 'filename':'{}/{}_mask.yaml'.format(directory,filename)} - self.provider.cmd(self.psyllid_interface, '_write_trigger_mask', payload=payload) + self.cmd(endpoint=self.psyllid_interface, specifier='_write_trigger_mask', payload=payload) for key in ('fmt', 'tfrr', 'eb'): psyllid_config_kwargs['payload'].update( { 'key' : key } ) - setup['psyllid'].update( { key : self.provider.cmd(**psyllid_config_kwargs) } ) + setup['psyllid'].update( { key : self.cmd(endpoint=**psyllid_config_kwargs) } ) self._send_metadata( type='setup', data=setup) logger.info('block roach channel') - self.provider.cmd(self.daq_target, 'block_channel', payload = self.payload_channel) + self.cmd(endpoint=self.daq_target, specifier='block_channel', keyed_args = self.payload_channel) # switching from seconds to milisecons duration = self._run_time*1000.0 @@ -225,12 +225,12 @@ def _start_data_taking(self, directory, filename): logger.info('Going to tell psyllid to start the run') payload = {'channel':self.channel_id, 'filename': os.path.join(directory, psyllid_filename), 'duration':duration} try: - self.provider.cmd(self.psyllid_interface, 'start_run', payload = payload) + self.cmd(endpoint=self.psyllid_interface, specifier='start_run', keyed_args = payload) except core.exceptions.DriplineError as e: logger.critical('Error from psyllid provider or psyllid. Starting psyllid run failed.') payload = {'channel': self.channel_id} try: - self.provider.cmd(self.daq_target, 'unblock_channel', payload = payload) + self.cmd(endpoint=self.daq_target, specifier='unblock_channel', keyed_args = payload) logger.info('Unblocked channel') finally: raise e @@ -238,7 +238,7 @@ def _start_data_taking(self, directory, filename): logger.critical('Something else went wrong.') payload = {'channel': self.channel_id} try: - self.provider.cmd(self.daq_target, 'unblock_channel', payload = payload) + self.cmd(endpoint=self.daq_target, specifier='unblock_channel', keyed_args = payload) logger.info('Unblocked channel') finally: raise e @@ -253,34 +253,34 @@ def _stop_data_taking(self): try: if self.is_running: logger.info('Psyllid still running. Telling it to stop the run') - self.provider.cmd(self.psyllid_interface, 'stop_run', payload = self.payload_channel) + self.cmd(endpoint=self.psyllid_interface, specifier='stop_run', keyed_args = self.payload_channel) except core.exceptions.DriplineError as e: logger.critical('Getting Psyllid status or stopping run failed') logger.info('Unblock channel') payload = {'channel': self.channel_id} try: - self.provider.cmd(self.daq_target, 'unblock_channel', payload = payload) + self.cmd(endpoint=self.daq_target, specifier='unblock_channel', keyed_args = payload) finally: raise e except Exception as e: logger.critical('Something else went wrong') payload = {'channel': self.channel_id} try: - self.provider.cmd(self.daq_target, 'unblock_channel', payload = payload) + self.cmd(endpoint=self.daq_target, specifier='unblock_channel', keyed_args = payload) finally: raise e else: logger.info('Unblock channel') payload = {'channel': self.channel_id} - self.provider.cmd(self.daq_target, 'unblock_channel', payload = payload) + self.cmd(endpoint=self.daq_target, specifier='unblock_channel', keyed_args = payload) def stop_psyllid(self): ''' Makes psyllid exit and unblocks roach channel ''' - self.provider.cmd(self.psyllid_interface, 'quit_psyllid', payload = self.payload_channel) - self.provider.cmd(self.daq_target, 'unblock_channel', payload = self.payload_channel) + self.cmd(endpoint=self.psyllid_interface, specifier='quit_psyllid', keyed_args = self.payload_channel) + self.cmd(endpoint=self.daq_target, specifier='unblock_channel', keyed_args = self.payload_channel) ########################### @@ -288,13 +288,13 @@ def stop_psyllid(self): ########################### def _get_roach_central_freqs(self): - result = self.provider.get(self.daq_target + '.all_central_frequencies') + result = self.get(endpoint=self.daq_target + '.all_central_frequencies') logger.info('ROACH central freqs {}'.format(result)) return result def _get_psyllid_central_freq(self): - result = self.provider.cmd(self.psyllid_interface, 'get_central_frequency', payload = self.payload_channel) + result = self.cmd(endpoint=self.psyllid_interface, specifier='get_central_frequency', keyed_args = self.payload_channel) logger.info('Psyllid central freqs {}'.format(result['values'][0])) return result['values'][0] @@ -312,12 +312,12 @@ def central_frequency(self, cf): Sets same frequency in psyllid instance ''' payload = {'cf':float(cf), 'channel':self.channel_id} - result = self.provider.cmd(self.daq_target, 'set_central_frequency', payload = payload) + result = self.cmd(endpoint=self.daq_target, specifier='set_central_frequency', keyed_args = payload) logger.info('The roach central frequency is now {}Hz'.format(result['values'][0])) self.freq_dict[self.channel_id]=round(result['values'][0]) payload = {'cf':self.freq_dict[self.channel_id], 'channel':self.channel_id} - self.provider.cmd(self.psyllid_interface, 'set_central_frequency', payload = payload) + self.cmd(endpoint=self.psyllid_interface, specifier='set_central_frequency', keyed_args = payload) ################### @@ -329,7 +329,7 @@ def acquisition_mode(self): ''' The Cmd returns a dictionary like {"mode": "someMode"}; we want just the mode value. ''' - result = self.provider.cmd(self.psyllid_interface, 'get_acquisition_mode', payload = self.payload_channel)['mode'] + result = self.cmd(endpoint=self.psyllid_interface, specifier='get_acquisition_mode', keyed_args = self.payload_channel)['mode'] return result @acquisition_mode.setter @@ -339,59 +339,59 @@ def acquisition_mode(self, x): @property def threshold_type(self): - result = self.provider.cmd(self.psyllid_interface, 'get_threshold_type', payload = self.payload_channel)['values'][0] + result = self.cmd(endpoint=self.psyllid_interface, specifier='get_threshold_type', keyed_args = self.payload_channel)['values'][0] return result @threshold_type.setter def threshold_type(self, snr_or_sigma): - self.provider.cmd(self.psyllid_interface, 'set_threshold_type', payload = {'channel': self.channel_id, 'snr_or_sigma': snr_or_sigma}) + self.cmd(endpoint=self.psyllid_interface, specifier='set_threshold_type', keyed_args = {'channel': self.channel_id, 'snr_or_sigma': snr_or_sigma}) @property def threshold(self): if self.threshold_type == 'snr': - return self.provider.cmd(self.psyllid_interface, 'get_fmt_snr_threshold', payload = self.payload_channel)['values'][0] + return self.cmd(endpoint=self.psyllid_interface, specifier='get_fmt_snr_threshold', keyed_args = self.payload_channel)['values'][0] else: - return self.provider.cmd(self.psyllid_interface, 'get_fmt_sigma_threshold', payload = self.payload_channel)['values'][0] + return self.cmd(endpoint=self.psyllid_interface, specifier='get_fmt_sigma_threshold', keyed_args = self.payload_channel)['values'][0] @threshold.setter def threshold(self, threshold): if self.threshold_type == 'snr': - self.provider.cmd(self.psyllid_interface, 'set_fmt_snr_threshold', payload = {'channel': self.channel_id, 'threshold': threshold}) + self.cmd(endpoint=self.psyllid_interface, specifier='set_fmt_snr_threshold', keyed_args = {'channel': self.channel_id, 'threshold': threshold}) else: - self.provider.cmd(self.psyllid_interface, 'set_fmt_sigma_threshold', payload = {'channel': self.channel_id, 'threshold': threshold}) - self.provider.cmd(self.psyllid_interface, 'set_trigger_mode', payload = self.payload_channel) + self.cmd(endpoint=self.psyllid_interface, specifier='set_fmt_sigma_threshold', keyed_args = {'channel': self.channel_id, 'threshold': threshold}) + self.cmd(endpoint=self.psyllid_interface, specifier='set_trigger_mode', keyed_args = self.payload_channel) @property def high_threshold(self): if self.threshold_type == 'snr': - return self.provider.cmd(self.psyllid_interface, 'get_fmt_snr_high_threshold', payload = self.payload_channel)['values'][0] + return self.cmd(endpoint=self.psyllid_interface, specifier='get_fmt_snr_high_threshold', keyed_args = self.payload_channel)['values'][0] else: - return self.provider.cmd(self.psyllid_interface, 'get_fmt_sigma_high_threshold', payload = self.payload_channel)['values'][0] + return self.cmd(endpoint=self.psyllid_interface, specifier='get_fmt_sigma_high_threshold', keyed_args = self.payload_channel)['values'][0] @high_threshold.setter def high_threshold(self, threshold): if self.threshold_type == 'snr': - self.provider.cmd(self.psyllid_interface, 'set_fmt_snr_high_threshold', payload = {'channel': self.channel_id, 'threshold': threshold}) + self.cmd(endpoint=self.psyllid_interface, specifier='set_fmt_snr_high_threshold', keyed_args = {'channel': self.channel_id, 'threshold': threshold}) else: - self.provider.cmd(self.psyllid_interface, 'set_fmt_sigma_high_threshold', payload = {'channel': self.channel_id, 'threshold': threshold}) - self.provider.cmd(self.psyllid_interface, 'set_trigger_mode', payload = self.payload_channel) + self.cmd(endpoint=self.psyllid_interface, specifier='set_fmt_sigma_high_threshold', keyed_args = {'channel': self.channel_id, 'threshold': threshold}) + self.cmd(endpoint=self.psyllid_interface, specifier='set_trigger_mode', keyed_args = self.payload_channel) @property def n_triggers(self): - result = self.provider.cmd(self.psyllid_interface, 'get_n_triggers', payload = self.payload_channel)['values'][0] + result = self.cmd(endpoint=self.psyllid_interface, specifier='get_n_triggers', keyed_args = self.payload_channel)['values'][0] return result @n_triggers.setter def n_triggers(self, n_triggers): - self.provider.cmd(self.psyllid_interface, 'set_n_triggers', payload = {'channel': self.channel_id, 'n_triggers': n_triggers}) + self.cmd(endpoint=self.psyllid_interface, specifier='set_n_triggers', keyed_args = {'channel': self.channel_id, 'n_triggers': n_triggers}) @property def pretrigger_time(self): - result = self.provider.cmd(self.psyllid_interface, 'get_pretrigger_time', payload = self.payload_channel)['values'][0] + result = self.cmd(endpoint=self.psyllid_interface, specifier='get_pretrigger_time', keyed_args = self.payload_channel)['values'][0] return result @pretrigger_time.setter @@ -399,13 +399,13 @@ def pretrigger_time(self, pretrigger_time): ''' Psyllid only adopts change of pretrigger time after reactivation ''' - self.provider.cmd(self.psyllid_interface, 'set_pretrigger_time', payload = {'channel': self.channel_id, 'pretrigger_time': pretrigger_time}) - self.provider.cmd(self.psyllid_interface, 'save_reactivate', payload = self.payload_channel) + self.cmd(endpoint=self.psyllid_interface, specifier='set_pretrigger_time', keyed_args = {'channel': self.channel_id, 'pretrigger_time': pretrigger_time}) + self.cmd(endpoint=self.psyllid_interface, specifier='save_reactivate', keyed_args = self.payload_channel) @property def skip_tolerance(self): - result = self.provider.cmd(self.psyllid_interface, 'get_skip_tolerance', payload = self.payload_channel)['values'][0] + result = self.cmd(endpoint=self.psyllid_interface, specifier='get_skip_tolerance', keyed_args = self.payload_channel)['values'][0] return result @skip_tolerance.setter @@ -413,8 +413,8 @@ def skip_tolerance(self, skip_tolerance): ''' Psyllid only adopts change of skip tolerance after reactivation ''' - self.provider.cmd(self.psyllid_interface, 'set_skip_tolerance', payload = {'channel': self.channel_id, 'skip_tolerance': skip_tolerance}) - self.provider.cmd(self.psyllid_interface, 'save_reactivate', payload = self.payload_channel) + self.cmd(endpoint=self.psyllid_interface, specifier='set_skip_tolerance', keyed_args = {'channel': self.channel_id, 'skip_tolerance': skip_tolerance}) + self.cmd(endpoint=self.psyllid_interface, specifier='save_reactivate', keyed_args = self.payload_channel) @property @@ -422,8 +422,8 @@ def trigger_type(self): ''' Returns the kind of trigger that is currently set ''' - n_triggers = self.provider.cmd(self.psyllid_interface, 'get_n_triggers', payload = self.payload_channel)['values'][0] - trigger_mode = self.provider.cmd(self.psyllid_interface, 'get_trigger_mode', payload = self.payload_channel)['values'][0] + n_triggers = self.cmd(endpoint=self.psyllid_interface, specifier='get_n_triggers', keyed_args = self.payload_channel)['values'][0] + trigger_mode = self.cmd(endpoint=self.psyllid_interface, specifier='get_trigger_mode', keyed_args = self.payload_channel)['values'][0] if n_triggers > 1: trigger_type = 'multi-trigger' @@ -442,7 +442,7 @@ def trigger_settings(self): ''' Returns all trigger settings ''' - result = self.provider.cmd(self.psyllid_interface, 'get_trigger_configuration', payload = self.payload_channel) + result = self.cmd(endpoint=self.psyllid_interface, specifier='get_trigger_configuration', keyed_args = self.payload_channel) return result @@ -456,7 +456,7 @@ def configure_trigger(self, threshold, high_threshold, n_triggers): Set all trigger parameters with one command ''' payload = {'threshold' : threshold, 'threshold_high' : high_threshold, 'n_triggers' : n_triggers, 'channel' : self.channel_id} - self.provider.cmd(self.psyllid_interface, 'set_trigger_configuration', payload = payload) + self.cmd(endpoint=self.psyllid_interface, specifier='set_trigger_configuration', keyed_args = payload) @property @@ -464,7 +464,7 @@ def time_window_settings(self): ''' Returns pretrigger time and skip tolerance ''' - result = self.provider.cmd(self.psyllid_interface, 'get_time_window', payload = self.payload_channel) + result = self.cmd(endpoint=self.psyllid_interface, specifier='get_time_window', keyed_args = self.payload_channel) return result @@ -478,7 +478,7 @@ def configure_time_window(self, pretrigger_time, skip_tolerance): Set pretrigger time and skip tolerance with one command ''' payload = {'skip_tolerance': skip_tolerance, 'pretrigger_time': pretrigger_time, 'channel' : self.channel_id} - self.provider.cmd(self.psyllid_interface, 'set_time_window', payload = payload) + self.cmd(endpoint=self.psyllid_interface, specifier='set_time_window', keyed_args = payload) @property @@ -520,4 +520,4 @@ def make_trigger_mask(self): filename = '{}_frequency_mask_channel_{}_cf_{}.yaml'.format(timestr, self.channel_id, self.freq_dict[self.channel_id]) path = os.path.join(self.mask_target_path, filename) payload = {'channel':self.channel_id, 'filename':path} - self.provider.cmd(self.psyllid_interface, 'make_trigger_mask', payload = payload) + self.cmd(endpoint=self.psyllid_interface, specifier='make_trigger_mask', keyed_args = payload) From 7d09b57153e9b3ac198fea877d23f57456807fa4 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Sun, 2 Mar 2025 23:28:36 -0500 Subject: [PATCH 29/55] Fixed dockerfile. --- Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 74b57faa..23c4b35f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,8 +19,10 @@ RUN pip install numpy==1.26.4 &&\ git clone https://github.com/pkolbeck/corr.git &&\ cd corr &&\ git checkout p8/r2daq_only &&\ - cp -r corr /usr/local/corr &&\ - export PYTHONPATH=${PYTHONPATH}:/usr/local/corr + cp -r corr /usr/local/corr/ &&\ + rm -rf /tmp/corr/ + +ENV PYTHONPATH="/usr/:/usr/local/:/usr/local/corr/" FROM deps AS build From 3015b89ed7dd9baf82b3dd73b3f08c269c7c9c98 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Mon, 3 Mar 2025 19:37:37 -0500 Subject: [PATCH 30/55] Translated daq interface to dl3. --- Dockerfile | 2 +- .../roach2_daq_run_interface/__init__.py | 22 +++++++ .../daq_run_interface.py | 2 +- .../roach_daq_run_interface.py | 60 +++++++++---------- 4 files changed, 54 insertions(+), 32 deletions(-) create mode 100644 dripline/extensions/roach2_daq_run_interface/__init__.py diff --git a/Dockerfile b/Dockerfile index 23c4b35f..7c0f152a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ ARG img_user=ghcr.io ARG img_repo=driplineorg/dripline-python -ARG img_tag=getsetcmd-mixin-test +ARG img_tag=propertydebug-test FROM ${img_user}/${img_repo}:${img_tag} AS base diff --git a/dripline/extensions/roach2_daq_run_interface/__init__.py b/dripline/extensions/roach2_daq_run_interface/__init__.py new file mode 100644 index 00000000..64afa9b1 --- /dev/null +++ b/dripline/extensions/roach2_daq_run_interface/__init__.py @@ -0,0 +1,22 @@ +__all__ = [] + +import pkg_resources + +import scarab +a_ver = '0.0.0' #note that this is updated in the following block +try: + a_ver = pkg_resources.get_distribution('dragonfly').version + print('version is: {}'.format(a_ver)) +except: + print('fail!') + pass +version = scarab.VersionSemantic() +version.parse(a_ver) +version.package = 'project8/dragonfly' +version.commit = '---' +__all__.append("version") + +from .roach_daq_run_interface import * +from .roach_daq_run_interface import __all__ as __roach_daq_run_interface_all +__all__ += __roach_daq_run_interface_all + diff --git a/dripline/extensions/roach2_daq_run_interface/daq_run_interface.py b/dripline/extensions/roach2_daq_run_interface/daq_run_interface.py index f27cb94b..fb1caee2 100644 --- a/dripline/extensions/roach2_daq_run_interface/daq_run_interface.py +++ b/dripline/extensions/roach2_daq_run_interface/daq_run_interface.py @@ -18,7 +18,7 @@ __all__.append('DAQProvider') -@core.fancy_doc + class DAQProvider(core.Service): ''' Base class for providing a uniform interface to different DAQ systems diff --git a/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py b/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py index 19159eba..f0586bfa 100644 --- a/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py +++ b/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py @@ -23,7 +23,7 @@ class ROACH1ChAcquisitionInterface(DAQProvider): ''' def __init__(self, channel = None, - psyllid_interface='psyllid_interface', + psyllid_interface = 'psyllid_interface', daq_target = 'roach2_interface', hf_lo_freq = None, mask_target_path = None, @@ -76,9 +76,9 @@ def _check_roach2_is_ready(self): Asks roach2_interface whether roach is running and adc is calibrated ''' logger.info('Checking ROACH2 status') - result = self.get(endpoint=self.daq_target+ '.is_running')['values'][0] + result = self.get(endpoint=self.daq_target, specifer='is_running') if result==True: - result = self.get(endpoint=self.daq_target+'.calibration_status')['values'][0] + result = self.get(endpoint=self.daq_target, specifier='calibration_status') if result == True: logger.info('ROACH2 is running and ADCs are calibrated') return True @@ -100,11 +100,11 @@ def _check_psyllid_instance(self): ''' logger.info('Checking Psyllid service & instance') - self.status_value = self.cmd(endpoint=self.psyllid_interface, specifier='request_status', keyed_args = self.payload_channel)['values'][0] + self.status_value = self.cmd(endpoint=self.psyllid_interface, specifier='request_status', keyed_args = self.payload_channel) if self.status_value == 0: self.cmd(endpoint=self.psyllid_interface, specifier='activate', keyed_args = self.payload_channel) - self.status_value = self.cmd(endpoint=self.psyllid_interface, specifier='request_status', keyed_args = self.payload_channel)['values'][0] + self.status_value = self.cmd(endpoint=self.psyllid_interface, specifier='request_status', keyed_args = self.payload_channel) @property @@ -113,8 +113,8 @@ def is_running(self): Requests status of psyllid Returns True if status is 5 (currently taking data) ''' - result = self.cmd(endpoint=self.psyllid_interface, specifier='request_status', keyed_args = self.payload_channel, timeout=10) - self.status_value = result['values'][0] + result = self.cmd(endpoint=self.psyllid_interface, specifier='request_status', keyed_args = self.payload_channel, timeout_s=10) + self.status_value = result logger.info('psyllid status is {}'.format(self.status_value)) if self.status_value==5: return True @@ -138,7 +138,7 @@ def _do_checks(self): raise core.exceptions.DriplineGenericDAQError('Psyllid DAQ is not activated') # check psyllid is ready to write a file - result = self.cmd(endpoint=self.psyllid_interface, specifier='is_psyllid_using_monarch', keyed_args = self.payload_channel)['values'][0] + result = self.cmd(endpoint=self.psyllid_interface, specifier='is_psyllid_using_monarch', keyed_args = self.payload_channel) if result != True: raise core.exceptions.DriplineGenericDAQError('Psyllid is not using monarch and therefore not ready to write a file') @@ -147,7 +147,7 @@ def _do_checks(self): raise core.exceptions.DriplineGenericDAQError('ROACH2 is not ready. ADC not calibrated.') # check channel is unblocked - blocked_channels = self.get(endpoint=self.daq_target+ '.blocked_channels') + blocked_channels = self.get(endpoint=self.daq_target, specifier='blocked_channels') if self.channel_id in blocked_channels: raise core.exceptions.DriplineGenericDAQError('Channel is blocked') @@ -195,22 +195,22 @@ def _start_data_taking(self, directory, filename): Unblocks roach channels if that fails ''' - setup = { 'roach' : self.get(endpoint='{}.registers'.format(self.daq_target)) } + setup = { 'roach' : self.get(endpoint=self.daq_target, specifier='registers') } psyllid_config_kwargs = { - 'target' : self.psyllid_interface, - 'method_name' : 'get_active_config', - 'payload' : { 'channel' : self.channel_id, + 'endpoint' : self.psyllid_interface, + 'specifier' : 'get_active_config', + 'keyed_args' : { 'channel' : self.channel_id, 'key' : 'prf' } } setup.update( { 'psyllid' : - { 'prf' : self.cmd(endpoint=**psyllid_config_kwargs) } } ) + { 'prf' : self.cmd(**psyllid_config_kwargs) } } ) if self.acquisition_mode == 'triggering': payload = {'channel':self.channel_id, 'filename':'{}/{}_mask.yaml'.format(directory,filename)} self.cmd(endpoint=self.psyllid_interface, specifier='_write_trigger_mask', payload=payload) for key in ('fmt', 'tfrr', 'eb'): psyllid_config_kwargs['payload'].update( { 'key' : key } ) - setup['psyllid'].update( { key : self.cmd(endpoint=**psyllid_config_kwargs) } ) + setup['psyllid'].update( { key : self.cmd(**psyllid_config_kwargs) } ) self._send_metadata( type='setup', data=setup) logger.info('block roach channel') @@ -288,15 +288,15 @@ def stop_psyllid(self): ########################### def _get_roach_central_freqs(self): - result = self.get(endpoint=self.daq_target + '.all_central_frequencies') + result = self.get(endpoint=self.daq_target, specifier='all_central_frequencies') logger.info('ROACH central freqs {}'.format(result)) return result def _get_psyllid_central_freq(self): result = self.cmd(endpoint=self.psyllid_interface, specifier='get_central_frequency', keyed_args = self.payload_channel) - logger.info('Psyllid central freqs {}'.format(result['values'][0])) - return result['values'][0] + logger.info('Psyllid central freqs {}'.format(result)) + return result @property @@ -313,9 +313,9 @@ def central_frequency(self, cf): ''' payload = {'cf':float(cf), 'channel':self.channel_id} result = self.cmd(endpoint=self.daq_target, specifier='set_central_frequency', keyed_args = payload) - logger.info('The roach central frequency is now {}Hz'.format(result['values'][0])) + logger.info('The roach central frequency is now {}Hz'.format(result)) - self.freq_dict[self.channel_id]=round(result['values'][0]) + self.freq_dict[self.channel_id]=round(result) payload = {'cf':self.freq_dict[self.channel_id], 'channel':self.channel_id} self.cmd(endpoint=self.psyllid_interface, specifier='set_central_frequency', keyed_args = payload) @@ -339,7 +339,7 @@ def acquisition_mode(self, x): @property def threshold_type(self): - result = self.cmd(endpoint=self.psyllid_interface, specifier='get_threshold_type', keyed_args = self.payload_channel)['values'][0] + result = self.cmd(endpoint=self.psyllid_interface, specifier='get_threshold_type', keyed_args = self.payload_channel) return result @threshold_type.setter @@ -350,9 +350,9 @@ def threshold_type(self, snr_or_sigma): @property def threshold(self): if self.threshold_type == 'snr': - return self.cmd(endpoint=self.psyllid_interface, specifier='get_fmt_snr_threshold', keyed_args = self.payload_channel)['values'][0] + return self.cmd(endpoint=self.psyllid_interface, specifier='get_fmt_snr_threshold', keyed_args = self.payload_channel) else: - return self.cmd(endpoint=self.psyllid_interface, specifier='get_fmt_sigma_threshold', keyed_args = self.payload_channel)['values'][0] + return self.cmd(endpoint=self.psyllid_interface, specifier='get_fmt_sigma_threshold', keyed_args = self.payload_channel) @threshold.setter def threshold(self, threshold): @@ -366,9 +366,9 @@ def threshold(self, threshold): @property def high_threshold(self): if self.threshold_type == 'snr': - return self.cmd(endpoint=self.psyllid_interface, specifier='get_fmt_snr_high_threshold', keyed_args = self.payload_channel)['values'][0] + return self.cmd(endpoint=self.psyllid_interface, specifier='get_fmt_snr_high_threshold', keyed_args = self.payload_channel) else: - return self.cmd(endpoint=self.psyllid_interface, specifier='get_fmt_sigma_high_threshold', keyed_args = self.payload_channel)['values'][0] + return self.cmd(endpoint=self.psyllid_interface, specifier='get_fmt_sigma_high_threshold', keyed_args = self.payload_channel) @high_threshold.setter def high_threshold(self, threshold): @@ -381,7 +381,7 @@ def high_threshold(self, threshold): @property def n_triggers(self): - result = self.cmd(endpoint=self.psyllid_interface, specifier='get_n_triggers', keyed_args = self.payload_channel)['values'][0] + result = self.cmd(endpoint=self.psyllid_interface, specifier='get_n_triggers', keyed_args = self.payload_channel) return result @n_triggers.setter @@ -391,7 +391,7 @@ def n_triggers(self, n_triggers): @property def pretrigger_time(self): - result = self.cmd(endpoint=self.psyllid_interface, specifier='get_pretrigger_time', keyed_args = self.payload_channel)['values'][0] + result = self.cmd(endpoint=self.psyllid_interface, specifier='get_pretrigger_time', keyed_args = self.payload_channel) return result @pretrigger_time.setter @@ -405,7 +405,7 @@ def pretrigger_time(self, pretrigger_time): @property def skip_tolerance(self): - result = self.cmd(endpoint=self.psyllid_interface, specifier='get_skip_tolerance', keyed_args = self.payload_channel)['values'][0] + result = self.cmd(endpoint=self.psyllid_interface, specifier='get_skip_tolerance', keyed_args = self.payload_channel) return result @skip_tolerance.setter @@ -422,8 +422,8 @@ def trigger_type(self): ''' Returns the kind of trigger that is currently set ''' - n_triggers = self.cmd(endpoint=self.psyllid_interface, specifier='get_n_triggers', keyed_args = self.payload_channel)['values'][0] - trigger_mode = self.cmd(endpoint=self.psyllid_interface, specifier='get_trigger_mode', keyed_args = self.payload_channel)['values'][0] + n_triggers = self.cmd(endpoint=self.psyllid_interface, specifier='get_n_triggers', keyed_args = self.payload_channel) + trigger_mode = self.cmd(endpoint=self.psyllid_interface, specifier='get_trigger_mode', keyed_args = self.payload_channel) if n_triggers > 1: trigger_type = 'multi-trigger' From ee3b3d6caa1ac4899c1b585a144facd1fce1dc47 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Tue, 3 Jun 2025 14:50:11 -0700 Subject: [PATCH 31/55] removed json.dumps from roach2_interface, should return dictionaries now. --- Dockerfile | 2 +- .../roach2_interface/roach2_interface.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7c0f152a..ce451a75 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ ARG img_user=ghcr.io ARG img_repo=driplineorg/dripline-python -ARG img_tag=propertydebug-test +ARG img_tag=v5.1.0-test FROM ${img_user}/${img_repo}:${img_tag} AS base diff --git a/dripline/extensions/roach2_interface/roach2_interface.py b/dripline/extensions/roach2_interface/roach2_interface.py index 768a1ef6..923bbcce 100644 --- a/dripline/extensions/roach2_interface/roach2_interface.py +++ b/dripline/extensions/roach2_interface/roach2_interface.py @@ -140,17 +140,17 @@ def unblock_channel(self, channel): @property def blocked_channels(self): - return json.dumps(self.block_dict) + return self.block_dict @property def all_central_frequencies(self): - return json.dumps({ch:self.get_central_frequency(ch) for ch in self.freq_dict.keys()}) + return {ch:self.get_central_frequency(ch) for ch in self.freq_dict.keys()} def get_central_frequency(self, channel): cfg = ArtooDaq.read_ddc_1st_config(self, channel) - return json.dumps(cfg['digital']['f_c']) + return cfg['digital']['f_c'] def set_central_frequency(self, channel, cf): @@ -170,7 +170,7 @@ def set_central_frequency(self, channel, cf): @property def gain(self): - return json.dumps({ch:ArtooDaq.get_gain(self, ch) for ch in self.gain_dict.keys()}) + return {ch:ArtooDaq.get_gain(self, ch) for ch in self.gain_dict.keys()} def set_gain(self, channel, gain): @@ -190,7 +190,7 @@ def set_gain(self, channel, gain): @property def all_fft_shift_vectors(self): - return json.dumps({blk:self.get_fft_shift_vector(blk) for blk in self.fft_shift_vector.keys()}) + return {blk:self.get_fft_shift_vector(blk) for blk in self.fft_shift_vector.keys()} def get_fft_shift_vector(self, tag): return ArtooDaq.get_fft_shift(self, tag) @@ -240,7 +240,7 @@ def get_packets(self, channel='a', NPackets=1, filename=None): with open(filename, 'w') as outfile: json.dump(p, outfile) else: - return json.dumps(p) + return p def get_T_packets(self, channel='a', NPackets=1, filename=None): if channel=='a': @@ -267,7 +267,7 @@ def get_T_packets(self, channel='a', NPackets=1, filename=None): with open(filename, 'w') as outfile: json.dump(p, outfile) else: - return json.dumps(p) + return p @@ -297,7 +297,7 @@ def get_F_packets(self,dsoc_desc=None, channel='a', NPackets=10, filename = None with open(filename, 'w') as outfile: json.dump(p, outfile) else: - return json.dumps(p) + return p def get_raw_adc_data(self, NSnaps = 1, filename = None): @@ -369,4 +369,4 @@ def adc_calibration_values(self): calibration_values['phase2'] = adc5g.get_spi_phase(self.roach2, 0, 2) calibration_values['phase3'] = adc5g.get_spi_phase(self.roach2, 0, 3) calibration_values['phase4'] = adc5g.get_spi_phase(self.roach2, 0, 4) - return json.dumps(calibration_values) + return calibration_values From 7f83e0df5fedc9a237e356056f64927542127b89 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Thu, 12 Jun 2025 09:32:57 -0700 Subject: [PATCH 32/55] Switched errors to ThrowReply --- .../psyllid_provider/psyllid_provider.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dripline/extensions/psyllid_provider/psyllid_provider.py b/dripline/extensions/psyllid_provider/psyllid_provider.py index 84df2192..f560fee3 100644 --- a/dripline/extensions/psyllid_provider/psyllid_provider.py +++ b/dripline/extensions/psyllid_provider/psyllid_provider.py @@ -51,7 +51,7 @@ def check_all_psyllid_instances(self): if self.freq_dict[channel] == None: self.freq_dict[channel] = 50.0e6 self.set_central_frequency(channel, self.freq_dict[channel]) - except core.exceptions.DriplineError: + except core.ThrowReply: self.status_dict[channel] = None self.status_value_dict[channel] = None self.mode_dict[channel] = None @@ -76,7 +76,7 @@ def all_acquisition_modes(self): @all_acquisition_modes.setter def all_acquisition_modes(self, x): - # raise core.exceptions.DriplineGenericDAQError('acquisition_modes cannot be set') + raise core.ThrowReply('DriplineGenericDAQError', 'acquisition_modes cannot be set') return @@ -93,7 +93,7 @@ def active_channels(self): @active_channels.setter def active_channels(self, x): - # raise core.exceptions.DriplineGenericDAQError('active_channels cannot be set') + raise core.ThrowReply('DriplineGenericDAQError', 'active_channels cannot be set') return @@ -145,7 +145,7 @@ def activate(self, channel): self.request_status(channel) if self.status_value_dict[channel]!=4: logger.error('Activating failed') - # raise core.exceptions.DriplineGenericDAQError('Activating psyllid failed') + raise core.ThrowReply('DriplineGenericDAQError', 'Activating psyllid failed') else: logger.info('Psyllid instance of channel {} is already activated'.format(channel)) @@ -163,7 +163,7 @@ def deactivate(self, channel): self.request_status(channel) if self.status_value_dict[channel]!=0: logger.error('Deactivating failed') - # raise core.exceptions.DriplineGenericDAQError('Deactivating psyllid failed') + raise core.ThrowReply('DriplineGenericDAQError', 'Deactivating psyllid failed') else: logger.info('Psyllid instance of channel {} is already deactivated'.format(channel)) @@ -181,14 +181,14 @@ def reactivate(self, channel): self.request_status(channel) if self.status_value_dict[channel]!=4: logger.error('Reactivating failed') - # raise core.exceptions.DriplineGenericDAQError('Reactivating psyllid failed') + raise core.ThrowReply('DriplineGenericDAQError', 'Reactivating psyllid failed') elif self.status_value_dict[channel] == 0: logger.warning('Psyllid is deactivated. Trying to activate instead of re-activate') self.activate(channel) else: logger.error('Cannot reactivate Psyllid instance of channel {}'.format(channel)) - # raise core.exceptions.DriplineGenericDAQError('Psyllid is not activated and can therefore not be reactivated') + raise core.ThrowReply('DriplineGenericDAQError', 'Psyllid is not activated and can therefore not be reactivated') def save_reactivate(self, channel): @@ -231,7 +231,7 @@ def all_central_frequencies(self): @all_central_frequencies.setter def all_central_frequencies(self, x): - # raise core.exceptions.DriplineGenericDAQError('all_central_frequencies cannot be set') + raise core.ThrowReply('DriplineGenericDAQError', 'all_central_frequencies cannot be set') return @@ -241,7 +241,7 @@ def get_central_frequency(self, channel): ''' if self.mode_dict[channel] == None: logger.error('Acquisition mode is None. Cannot get central frequency from psyllid') - # raise core.exceptions.DriplineGenericDAQError('Acquisition mode is None. Update mode by using get_acquisition_mode command') + raise core.ThrowReply('DriplineGenericDAQError', 'Acquisition mode is None. Update mode by using get_acquisition_mode command') routing_key_map = { 'streaming':'strw', 'triggering':'trw' @@ -259,7 +259,7 @@ def set_central_frequency(self, channel, cf): ''' if self.mode_dict[channel] == None: logger.error('Acquisition mode is None. Cannot set central frequency from psyllid') - # raise core.exceptions.DriplineGenericDAQError('Acquisition mode is None. Update mode by using get_acquisition_mode command') + raise core.ThrowReply('DriplineGenericDAQError', 'Acquisition mode is None. Update mode by using get_acquisition_mode command') routing_key_map = { 'streaming':'strw', 'triggering':'trw' @@ -491,7 +491,7 @@ def make_trigger_mask(self, channel='a', filename='~/fmt_mask.json'): ''' if self.mode_dict[channel] != 'triggering': logger.error('Psyllid instance is not in triggering mode') - # raise core.exceptions.DriplineGenericDAQError('Psyllid instance is not in triggering mode') + raise core.ThrowReply('DriplineGenericDAQError', 'Psyllid instance is not in triggering mode') logger.info('Switch tf_roach_receiver to freq-only') request = 'run-daq-cmd.{}.tfrr.freq-only'.format(str(self.channel_dict[channel])) From bc3ea909f230af21deba6000822bc5165d9ff19b Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Thu, 12 Jun 2025 09:40:47 -0700 Subject: [PATCH 33/55] Switched to ThrowReply in remainder of daq chain. --- .../daq_run_interface.py | 24 ++++++------ .../roach_daq_run_interface.py | 38 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dripline/extensions/roach2_daq_run_interface/daq_run_interface.py b/dripline/extensions/roach2_daq_run_interface/daq_run_interface.py index fb1caee2..95817b5b 100644 --- a/dripline/extensions/roach2_daq_run_interface/daq_run_interface.py +++ b/dripline/extensions/roach2_daq_run_interface/daq_run_interface.py @@ -48,17 +48,17 @@ def __init__(self, core.Service.__init__(self, **kwargs) if daq_name is None: - raise core.exceptions.DriplineValueError('<{}> instance <{}> requires a value for "{}" to initialize'.format(self.__class__.__name__, self.name, 'daq_name')) + raise core.ThrowReply('DriplineValueError', '<{}> instance <{}> requires a value for "{}" to initialize'.format(self.__class__.__name__, self.name, 'daq_name')) else: self.daq_name = daq_name if run_table_endpoint is None: - raise core.exceptions.DriplineValueError('<{}> instance <{}> requires a value for "{}" to initialize'.format(self.__class__.__name__, self.name, 'run_table_endpoint')) + raise core.ThrowReply('DriplineValueError', '<{}> instance <{}> requires a value for "{}" to initialize'.format(self.__class__.__name__, self.name, 'run_table_endpoint')) else: self.run_table_endpoint = run_table_endpoint # deal with directory structures if (directory_path is None) and (data_directory_path is None) and (meta_data_directory_path is None): - raise core.exceptions.DriplineValueError('<{}> instance <{}> requires a value for "{}" to initialize'.format(self.__class__.__name__, self.name, '[meta_[data_]]directory_path')) + raise core.ThrowReply('DriplineValueError', '<{}> instance <{}> requires a value for "{}" to initialize'.format(self.__class__.__name__, self.name, '[meta_[data_]]directory_path')) if (data_directory_path is None) and (directory_path is not None): data_directory_path = directory_path if (meta_data_directory_path is None) and (directory_path is not None): @@ -104,7 +104,7 @@ def run_name(self, value): self._stop_handle = None self._run_name = None self.run_id = None - raise core.exceptions.DriplineValueError('failed to insert run_name to the db, obtain run_id, and start_timestamp. run "<{}>" not started\nerror:\n{}'.format(value,str(err))) + raise core.ThrowReply('DriplineValueError', 'failed to insert run_name to the db, obtain run_id, and start_timestamp. run "<{}>" not started\nerror:\n{}'.format(value,str(err))) def start_run(self, run_name): ''' @@ -135,7 +135,7 @@ def end_run(self): self.service._connection.remove_timeout(self._stop_handle) self._stop_handle = None if self.run_id is None: - raise core.exceptions.DriplineValueError("No run to end: run_id is None.") + raise core.ThrowReply('DriplineValueError', "No run to end: run_id is None.") self._do_snapshot() logger.info('run <{}> ended'.format(self.run_id)) self._run_name = None @@ -171,7 +171,7 @@ def _do_snapshot(self): logger.info('snapshot returned ok') def determine_RF_ROI(self): - raise core.exceptions.DriplineMethodNotSupportedError('subclass must implement RF ROI determination') + raise core.ThrowReply('DriplineMethodNotSupportedError', 'subclass must implement RF ROI determination') def _send_metadata(self, type, data): ''' @@ -201,12 +201,12 @@ def start_timed_run(self, run_name, run_time): self._run_time = int(run_time) if self._daq_in_safe_mode: logger.info("DAQ in safe mode") - raise core.exceptions.DriplineDAQNotEnabled("{} is not enabled: enable it using ".format(self.daq_name)) + raise core.ThrowReply('DriplineDAQNotEnabled', "{} is not enabled: enable it using ".format(self.daq_name)) logger.debug('testing if the DAQ is running') result = self.is_running if result == True: - raise core.exceptions.DriplineDAQRunning('DAQ is already running: aborting run') + raise core.ThrowReply('DriplineDAQRunning', 'DAQ is already running: aborting run') # do the last minutes checks: DAQ specific self._do_checks() @@ -230,16 +230,16 @@ def start_timed_run(self, run_name, run_time): @property def is_running(self): - raise core.exceptions.DriplineMethodNotSupportedError('subclass must implement is running method') + raise core.ThrowReply('DriplineMethodNotSupportedError', 'subclass must implement is running method') def _do_checks(self): - raise core.exceptions.DriplineMethodNotSupportedError('subclass must implement checks methods') + raise core.ThrowReply('DriplineMethodNotSupportedError', 'subclass must implement checks methods') def _start_data_taking(self,directory,filename): - raise core.exceptions.DriplineMethodNotSupportedError('subclass must implement start data taking method') + raise core.ThrowReply('DriplineMethodNotSupportedError', 'subclass must implement start data taking method') def _stop_data_taking(self): - raise core.exceptions.DriplineMethodNotSupportedError('subclass must implement stop data taking method') + raise core.ThrowReply('DriplineMethodNotSupportedError', 'subclass must implement stop data taking method') def _set_condition(self,number): ''' diff --git a/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py b/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py index f0586bfa..efab455f 100644 --- a/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py +++ b/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py @@ -45,7 +45,7 @@ def __init__(self, self.default_trigger_dict = default_trigger_dict if hf_lo_freq is None: - raise core.exceptions.DriplineValueError('The roach daq run interface interface requires a "hf_lo_freq" in its config file') + raise core.ThrowReply('DriplineValueError', 'The roach daq run interface interface requires a "hf_lo_freq" in its config file') self._hf_lo_freq = hf_lo_freq if mask_target_path is None: @@ -89,7 +89,7 @@ def _check_roach2_is_ready(self): return True else: logger.error('ROACH2 is not ready') - raise core.exceptions.DriplineGenericDAQError('ROACH2 is not ready') + raise core.ThrowReply('DriplineGenericDAQError', 'ROACH2 is not ready') def _check_psyllid_instance(self): @@ -126,37 +126,37 @@ def _do_checks(self): Checks everything that could prevent a successful run (in theory) ''' if self._run_time ==0: - raise core.exceptions.DriplineValueError('run time is zero') + raise core.ThrowReply('DriplineValueError', 'run time is zero') #checking that no run is in progress if self.is_running == True: - raise core.exceptions.DriplineGenericDAQError('Psyllid is already running') + raise core.ThrowReply('DriplineGenericDAQError', 'Psyllid is already running') self._check_psyllid_instance() if self.status_value!=4: - raise core.exceptions.DriplineGenericDAQError('Psyllid DAQ is not activated') + raise core.ThrowReply('DriplineGenericDAQError', 'Psyllid DAQ is not activated') # check psyllid is ready to write a file result = self.cmd(endpoint=self.psyllid_interface, specifier='is_psyllid_using_monarch', keyed_args = self.payload_channel) if result != True: - raise core.exceptions.DriplineGenericDAQError('Psyllid is not using monarch and therefore not ready to write a file') + raise core.ThrowReply('DriplineGenericDAQError', 'Psyllid is not using monarch and therefore not ready to write a file') # checking roach is ready if self._check_roach2_is_ready() != True: - raise core.exceptions.DriplineGenericDAQError('ROACH2 is not ready. ADC not calibrated.') + raise core.ThrowReply('DriplineGenericDAQError', 'ROACH2 is not ready. ADC not calibrated.') # check channel is unblocked blocked_channels = self.get(endpoint=self.daq_target, specifier='blocked_channels') if self.channel_id in blocked_channels: - raise core.exceptions.DriplineGenericDAQError('Channel is blocked') + raise core.ThrowReply('DriplineGenericDAQError', 'Channel is blocked') # check frequency matches roach_freqs = self._get_roach_central_freqs() psyllid_freq = self._get_psyllid_central_freq() if abs(roach_freqs[self.channel_id]-psyllid_freq)>1: logger.error('Frequency mismatch: roach cf is {}Hz, psyllid cf is {}Hz'.format(roach_freqs[self.channel_id], psyllid_freq)) - raise core.exceptions.DriplineGenericDAQError('Frequency mismatch') + raise core.ThrowReply('DriplineGenericDAQError', 'Frequency mismatch') return "checks successful" @@ -226,7 +226,7 @@ def _start_data_taking(self, directory, filename): payload = {'channel':self.channel_id, 'filename': os.path.join(directory, psyllid_filename), 'duration':duration} try: self.cmd(endpoint=self.psyllid_interface, specifier='start_run', keyed_args = payload) - except core.exceptions.DriplineError as e: + except core.ThrowReply as e: logger.critical('Error from psyllid provider or psyllid. Starting psyllid run failed.') payload = {'channel': self.channel_id} try: @@ -254,7 +254,7 @@ def _stop_data_taking(self): if self.is_running: logger.info('Psyllid still running. Telling it to stop the run') self.cmd(endpoint=self.psyllid_interface, specifier='stop_run', keyed_args = self.payload_channel) - except core.exceptions.DriplineError as e: + except core.ThrowReply as e: logger.critical('Getting Psyllid status or stopping run failed') logger.info('Unblock channel') payload = {'channel': self.channel_id} @@ -334,7 +334,7 @@ def acquisition_mode(self): @acquisition_mode.setter def acquisition_mode(self, x): - raise core.exceptions.DriplineGenericDAQError('acquisition mode cannot be set via dragonfly') + raise core.ThrowReply('DriplineGenericDAQError', 'acquisition mode cannot be set via dragonfly') @property @@ -434,7 +434,7 @@ def trigger_type(self): @trigger_type.setter def trigger_type(self, x): - raise core.exceptions.DriplineGenericDAQError('Trigger type is result of trigger settings and cannot be set directly') + raise core.ThrowReply('DriplineGenericDAQError', 'Trigger type is result of trigger settings and cannot be set directly') @property @@ -448,7 +448,7 @@ def trigger_settings(self): @trigger_settings.setter def trigger_settings(self, x): - raise core.exceptions.DriplineGenericDAQError('Use configure_trigger command to set all trigger parameters at once') + raise core.ThrowReply('DriplineGenericDAQError', 'Use configure_trigger command to set all trigger parameters at once') def configure_trigger(self, threshold, high_threshold, n_triggers): @@ -470,7 +470,7 @@ def time_window_settings(self): @time_window_settings.setter def time_window_settings(self, x): - raise core.exceptions.DriplineGenericDAQError('Use configure_time_window command to set skip_tolerance and pretrigger_time') + raise core.ThrowReply('DriplineGenericDAQError', 'Use configure_time_window command to set skip_tolerance and pretrigger_time') def configure_time_window(self, pretrigger_time, skip_tolerance): @@ -487,13 +487,13 @@ def default_trigger_settings(self): Returns dictionary containing default trigger settings ''' if self.default_trigger_dict == None: - raise core.exceptions.DriplineGenericDAQError('No default trigger settings present') + raise core.ThrowReply('DriplineGenericDAQError', 'No default trigger settings present') else: return self.default_trigger_dict @default_trigger_settings.setter def default_trigger_settings(self, x): - raise core.exceptions.DriplineGenericDAQError('Default settings must be specified in config file. Use cmd set_default_trigger to apply default settings') + raise core.ThrowReply('DriplineGenericDAQError', 'Default settings must be specified in config file. Use cmd set_default_trigger to apply default settings') def set_default_trigger(self): @@ -501,7 +501,7 @@ def set_default_trigger(self): Sets trigger parameters to values specified in config ''' if self.default_trigger_dict == None: - raise core.exceptions.DriplineGenericDAQError('No default trigger settings present') + raise core.ThrowReply('DriplineGenericDAQError', 'No default trigger settings present') else: self.threshold_type = self.default_trigger_dict['threshold_type'] self.configure_trigger(threshold=self.default_trigger_dict['threshold'], high_threshold=self.default_trigger_dict['high_threshold'], n_triggers=self.default_trigger_dict['n_triggers']) @@ -514,7 +514,7 @@ def make_trigger_mask(self): Raises exception if no mask_target_path was not set in config file ''' if self.mask_target_path == None: - raise core.exceptions.DriplineGenericDAQError('No target path set for trigger mask') + raise core.ThrowReply('DriplineGenericDAQError', 'No target path set for trigger mask') timestr = time.strftime("%Y%m%d_%H%M%S") filename = '{}_frequency_mask_channel_{}_cf_{}.yaml'.format(timestr, self.channel_id, self.freq_dict[self.channel_id]) From 7c08dd92726d1dc63a0e8c3dbbe084fd54531fc8 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Mon, 16 Jun 2025 11:33:29 -0700 Subject: [PATCH 34/55] uncommented mask writing --- dripline/extensions/psyllid_provider/psyllid_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dripline/extensions/psyllid_provider/psyllid_provider.py b/dripline/extensions/psyllid_provider/psyllid_provider.py index f560fee3..3346222b 100644 --- a/dripline/extensions/psyllid_provider/psyllid_provider.py +++ b/dripline/extensions/psyllid_provider/psyllid_provider.py @@ -509,7 +509,7 @@ def make_trigger_mask(self, channel='a', filename='~/fmt_mask.json'): self.start_run(channel ,1000, self.temp_file) time.sleep(1) - # self._write_trigger_mask(channel, filename) + self._write_trigger_mask(channel, filename) logger.info('Telling psyllid to use monarch again for next run') self.set(endpoint=self.queue_dict[channel], specifier='use-monarch', value=True) From 14122a9945d3f86c3d0d4d01e1dc908ff4325603 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Tue, 17 Jun 2025 16:51:11 -0700 Subject: [PATCH 35/55] debugging --- dripline/extensions/psyllid_provider/psyllid_provider.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dripline/extensions/psyllid_provider/psyllid_provider.py b/dripline/extensions/psyllid_provider/psyllid_provider.py index 3346222b..4babd843 100644 --- a/dripline/extensions/psyllid_provider/psyllid_provider.py +++ b/dripline/extensions/psyllid_provider/psyllid_provider.py @@ -433,6 +433,7 @@ def set_fmt_sigma_threshold(self, threshold, channel='a'): def get_fmt_sigma_threshold(self, channel='a'): request = 'active-config.{}.fmt.threshold-power-sigma'.format(str(self.channel_dict[channel])) threshold = self.get(endpoint=self.queue_dict[channel], specifier=request)['threshold-power-sigma'] + logger.debug("psyllid reply: " + threshold) return float(threshold) From 8cf3ce43d35e36880cc2005f00951ea07f42c076 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Tue, 17 Jun 2025 16:55:59 -0700 Subject: [PATCH 36/55] debug --- dripline/extensions/psyllid_provider/psyllid_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dripline/extensions/psyllid_provider/psyllid_provider.py b/dripline/extensions/psyllid_provider/psyllid_provider.py index 4babd843..db6bfe3f 100644 --- a/dripline/extensions/psyllid_provider/psyllid_provider.py +++ b/dripline/extensions/psyllid_provider/psyllid_provider.py @@ -433,7 +433,7 @@ def set_fmt_sigma_threshold(self, threshold, channel='a'): def get_fmt_sigma_threshold(self, channel='a'): request = 'active-config.{}.fmt.threshold-power-sigma'.format(str(self.channel_dict[channel])) threshold = self.get(endpoint=self.queue_dict[channel], specifier=request)['threshold-power-sigma'] - logger.debug("psyllid reply: " + threshold) + logger.debug("psyllid reply: {}".format(threshold) ) return float(threshold) From 4a926317a161762498be89bf7c1b0730851382f7 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Mon, 23 Jun 2025 16:09:46 -0700 Subject: [PATCH 37/55] typo --- .../roach2_daq_run_interface/roach_daq_run_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py b/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py index efab455f..04b6a243 100644 --- a/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py +++ b/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py @@ -76,7 +76,7 @@ def _check_roach2_is_ready(self): Asks roach2_interface whether roach is running and adc is calibrated ''' logger.info('Checking ROACH2 status') - result = self.get(endpoint=self.daq_target, specifer='is_running') + result = self.get(endpoint=self.daq_target, specifier='is_running') if result==True: result = self.get(endpoint=self.daq_target, specifier='calibration_status') if result == True: From 407304b82311a255fad37688cce2e306ce4633de Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Mon, 23 Jun 2025 18:06:29 -0700 Subject: [PATCH 38/55] Additional logs in check psyllid instance. --- .../roach2_daq_run_interface/roach_daq_run_interface.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py b/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py index 04b6a243..3d09cab7 100644 --- a/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py +++ b/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py @@ -103,8 +103,13 @@ def _check_psyllid_instance(self): self.status_value = self.cmd(endpoint=self.psyllid_interface, specifier='request_status', keyed_args = self.payload_channel) if self.status_value == 0: + logger.info('Psyllid is deactivated. Activating...') self.cmd(endpoint=self.psyllid_interface, specifier='activate', keyed_args = self.payload_channel) self.status_value = self.cmd(endpoint=self.psyllid_interface, specifier='request_status', keyed_args = self.payload_channel) + if self.status_value == 4: + logger.info('Psyllid is activated') + else: + logger.error(f'Psyllid is not activated. Status in numbers is <{self.status_value}> ') @property From 83556e904c26aee39d0921b8523d32ba5f0a5beb Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Wed, 9 Jul 2025 09:37:40 -0700 Subject: [PATCH 39/55] stashing changes --- .../roach2_daq_run_interface/roach_daq_run_interface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py b/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py index 3d09cab7..021c9fcf 100644 --- a/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py +++ b/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py @@ -106,10 +106,10 @@ def _check_psyllid_instance(self): logger.info('Psyllid is deactivated. Activating...') self.cmd(endpoint=self.psyllid_interface, specifier='activate', keyed_args = self.payload_channel) self.status_value = self.cmd(endpoint=self.psyllid_interface, specifier='request_status', keyed_args = self.payload_channel) - if self.status_value == 4: - logger.info('Psyllid is activated') - else: - logger.error(f'Psyllid is not activated. Status in numbers is <{self.status_value}> ') + if self.status_value == 4: + logger.info('Psyllid is activated') + else: + logger.error(f'Psyllid is not activated. Status in numbers is <{self.status_value}> ') @property From 5c58a256fed22b6f1b9bf3a0e9ccab1ad8dfba4d Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 14 Nov 2025 15:24:45 -0800 Subject: [PATCH 40/55] Moved files out of folders --- .../daq_run_interface.py | 0 .../psyllid_provider.py | 0 .../extensions/psyllid_provider/__init__.py | 22 ------------------ .../{roach2_interface => }/r2daq.py | 0 .../roach2_daq_run_interface/__init__.py | 22 ------------------ .../roach2_interface.py | 0 .../extensions/roach2_interface/__init__.py | 23 ------------------- .../roach_daq_run_interface.py | 0 8 files changed, 67 deletions(-) rename dripline/extensions/{roach2_daq_run_interface => }/daq_run_interface.py (100%) rename dripline/extensions/{psyllid_provider => }/psyllid_provider.py (100%) delete mode 100644 dripline/extensions/psyllid_provider/__init__.py rename dripline/extensions/{roach2_interface => }/r2daq.py (100%) delete mode 100644 dripline/extensions/roach2_daq_run_interface/__init__.py rename dripline/extensions/{roach2_interface => }/roach2_interface.py (100%) delete mode 100644 dripline/extensions/roach2_interface/__init__.py rename dripline/extensions/{roach2_daq_run_interface => }/roach_daq_run_interface.py (100%) diff --git a/dripline/extensions/roach2_daq_run_interface/daq_run_interface.py b/dripline/extensions/daq_run_interface.py similarity index 100% rename from dripline/extensions/roach2_daq_run_interface/daq_run_interface.py rename to dripline/extensions/daq_run_interface.py diff --git a/dripline/extensions/psyllid_provider/psyllid_provider.py b/dripline/extensions/psyllid_provider.py similarity index 100% rename from dripline/extensions/psyllid_provider/psyllid_provider.py rename to dripline/extensions/psyllid_provider.py diff --git a/dripline/extensions/psyllid_provider/__init__.py b/dripline/extensions/psyllid_provider/__init__.py deleted file mode 100644 index 025c6f20..00000000 --- a/dripline/extensions/psyllid_provider/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -__all__ = [] - -import pkg_resources - -import scarab -a_ver = '0.0.0' #note that this is updated in the following block -try: - a_ver = pkg_resources.get_distribution('dragonfly').version - print('version is: {}'.format(a_ver)) -except: - print('fail!') - pass -version = scarab.VersionSemantic() -version.parse(a_ver) -version.package = 'project8/dragonfly' -version.commit = '---' -__all__.append("version") - -from .psyllid_provider import * -from .psyllid_provider import __all__ as __psyllid_provider_all -__all__ += __psyllid_provider_all - diff --git a/dripline/extensions/roach2_interface/r2daq.py b/dripline/extensions/r2daq.py similarity index 100% rename from dripline/extensions/roach2_interface/r2daq.py rename to dripline/extensions/r2daq.py diff --git a/dripline/extensions/roach2_daq_run_interface/__init__.py b/dripline/extensions/roach2_daq_run_interface/__init__.py deleted file mode 100644 index 64afa9b1..00000000 --- a/dripline/extensions/roach2_daq_run_interface/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -__all__ = [] - -import pkg_resources - -import scarab -a_ver = '0.0.0' #note that this is updated in the following block -try: - a_ver = pkg_resources.get_distribution('dragonfly').version - print('version is: {}'.format(a_ver)) -except: - print('fail!') - pass -version = scarab.VersionSemantic() -version.parse(a_ver) -version.package = 'project8/dragonfly' -version.commit = '---' -__all__.append("version") - -from .roach_daq_run_interface import * -from .roach_daq_run_interface import __all__ as __roach_daq_run_interface_all -__all__ += __roach_daq_run_interface_all - diff --git a/dripline/extensions/roach2_interface/roach2_interface.py b/dripline/extensions/roach2_interface.py similarity index 100% rename from dripline/extensions/roach2_interface/roach2_interface.py rename to dripline/extensions/roach2_interface.py diff --git a/dripline/extensions/roach2_interface/__init__.py b/dripline/extensions/roach2_interface/__init__.py deleted file mode 100644 index 1f359c6c..00000000 --- a/dripline/extensions/roach2_interface/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -__all__ = [] - -import pkg_resources - -import scarab -a_ver = '0.0.0' #note that this is updated in the following block -try: - a_ver = pkg_resources.get_distribution('dragonfly').version - print('version is: {}'.format(a_ver)) -except: - print('fail!') - pass -version = scarab.VersionSemantic() -version.parse(a_ver) -version.package = 'project8/dragonfly' -version.commit = '---' -__all__.append("version") - -from .roach2_interface import * -from .roach2_interface import __all__ as __roach2_interface_all -from .r2daq import * -__all__ += __roach2_interface_all - diff --git a/dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py b/dripline/extensions/roach_daq_run_interface.py similarity index 100% rename from dripline/extensions/roach2_daq_run_interface/roach_daq_run_interface.py rename to dripline/extensions/roach_daq_run_interface.py From add927773caa44a19da5997fd9ec64b9d8bc98cc Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 13 Feb 2026 10:25:56 -0800 Subject: [PATCH 41/55] Removed corr portion of FpgaClient import. --- dripline/extensions/r2daq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dripline/extensions/r2daq.py b/dripline/extensions/r2daq.py index 7c7f0de4..000bc8ef 100644 --- a/dripline/extensions/r2daq.py +++ b/dripline/extensions/r2daq.py @@ -25,7 +25,7 @@ zeros, ) from scipy.signal import firwin2, freqz -from corr.katcp_wrapper import FpgaClient +from katcp_wrapper import FpgaClient from copy import deepcopy from datetime import datetime From db97c6d2be110d881b5da0041c82b5d14113689d Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 13 Feb 2026 10:40:54 -0800 Subject: [PATCH 42/55] Adjusted init --- dripline/extensions/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dripline/extensions/__init__.py b/dripline/extensions/__init__.py index 69e3be50..bccf202f 100644 --- a/dripline/extensions/__init__.py +++ b/dripline/extensions/__init__.py @@ -1 +1,13 @@ +__all__ = [] + __path__ = __import__('pkgutil').extend_path(__path__, __name__) + +# Subdirectories + +# Modules in this directory + +from .daq_run_interface import * +from .psyllid_provider import * +from .r2daq import * +from .roach_daq_run_interface import * +from .roach2_interface import * \ No newline at end of file From 4ac20c7c7aa6575d536a6b72f4b4201b7c2d4bc7 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 13 Feb 2026 11:06:45 -0800 Subject: [PATCH 43/55] actually saving changes --- .github/workflows/build.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d70a3163..a1256700 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -10,11 +10,7 @@ on: env: BASE_IMAGE_USER: driplineorg BASE_IMAGE_REPO: dripline-python -<<<<<<< HEAD - BASE_IMAGE_VER: 'v4.5.8' -======= BASE_IMAGE_VER: 'v5.1.4' ->>>>>>> develop REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} From bf4784f15d5cc535b429d76b22c106c63aecf6fd Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 13 Feb 2026 11:09:22 -0800 Subject: [PATCH 44/55] missed merge conflict resolution. --- .github/workflows/build.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a1256700..968c727b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -127,10 +127,6 @@ jobs: img_repo=${{ env.BASE_IMAGE_REPO }} img_tag=${{ env.BASE_IMAGE_TAG }} tags: ${{ steps.docker_meta.outputs.tags }} -<<<<<<< HEAD - platforms: linux/amd64 -# platforms: linux/amd64,linux/arm/v7,linux/arm64 -======= platforms: linux/amd64,linux/arm64,linux/arm/v7 - name: Release with a changelog @@ -141,4 +137,3 @@ jobs: path: 'changelog.md' title-template: 'Dragonfly v{version} -- Release Notes' tag-template: 'v{version}' ->>>>>>> develop From 47725eb3f2a536e394572f45be8bdfd441dbb2ac Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 13 Feb 2026 11:44:02 -0800 Subject: [PATCH 45/55] Adding try except block to catch missing dependencies --- Dockerfile-DAQ | 18 ++++++++++++++++++ dripline/extensions/__init__.py | 17 +++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 Dockerfile-DAQ diff --git a/Dockerfile-DAQ b/Dockerfile-DAQ new file mode 100644 index 00000000..f50b69e8 --- /dev/null +++ b/Dockerfile-DAQ @@ -0,0 +1,18 @@ +ARG img_user=ghcr.io/driplineorg +ARG img_repo=dripline-python +#ARG img_tag=develop-dev +ARG img_tag=v5.1.5 + +FROM ${img_user}/${img_repo}:${img_tag} + +COPY . /usr/local/src_dragonfly + +# DAQ specific dependencies +RUN git clone https://github.com/your-repo/katcp_wrapper.git && \ + pip install -e katcp_wrapper + +WORKDIR /usr/local/src_dragonfly +RUN pip install docker pymodbus +RUN pip install . + +WORKDIR / \ No newline at end of file diff --git a/dripline/extensions/__init__.py b/dripline/extensions/__init__.py index a87d5f8f..d4090f6e 100644 --- a/dripline/extensions/__init__.py +++ b/dripline/extensions/__init__.py @@ -5,12 +5,17 @@ # Subdirectories # Modules in this directory - -from .daq_run_interface import * -from .psyllid_provider import * -from .r2daq import * -from .roach_daq_run_interface import * -from .roach2_interface import * +try: + import katcp_wrapper + import r2daq +except ImportError: + print("Missing DAQ dependency(s). Not importing DAQ-related modules.") +else: + from .daq_run_interface import * + from .psyllid_provider import * + from .r2daq import * + from .roach_daq_run_interface import * + from .roach2_interface import * from .add_auth_spec import * from .cmd_endpoint import * from .asteval_endpoint import * From 5e40a619614210aa06e3752b9efe144269079938 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 13 Feb 2026 12:18:31 -0800 Subject: [PATCH 46/55] That caused an error haha --- dripline/extensions/__init__.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/dripline/extensions/__init__.py b/dripline/extensions/__init__.py index d4090f6e..5ce3ec70 100644 --- a/dripline/extensions/__init__.py +++ b/dripline/extensions/__init__.py @@ -5,17 +5,11 @@ # Subdirectories # Modules in this directory -try: - import katcp_wrapper - import r2daq -except ImportError: - print("Missing DAQ dependency(s). Not importing DAQ-related modules.") -else: - from .daq_run_interface import * - from .psyllid_provider import * - from .r2daq import * - from .roach_daq_run_interface import * - from .roach2_interface import * +from .daq_run_interface import * +from .psyllid_provider import * +from .r2daq import * +from .roach_daq_run_interface import * +from .roach2_interface import * from .add_auth_spec import * from .cmd_endpoint import * from .asteval_endpoint import * From 498dbef539ae369266a4f68c038b348ba602ca4d Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 20 Feb 2026 10:28:50 -0800 Subject: [PATCH 47/55] removed module from init --- dripline/extensions/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dripline/extensions/__init__.py b/dripline/extensions/__init__.py index 5ce3ec70..851e9dfb 100644 --- a/dripline/extensions/__init__.py +++ b/dripline/extensions/__init__.py @@ -10,7 +10,6 @@ from .r2daq import * from .roach_daq_run_interface import * from .roach2_interface import * -from .add_auth_spec import * from .cmd_endpoint import * from .asteval_endpoint import * from .thermo_fisher_endpoint import * From 99621d54e925423c491be4f3895c0358ec95c311 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 20 Feb 2026 10:44:55 -0800 Subject: [PATCH 48/55] removing versioning from jitter init --- dripline/extensions/jitter/__init__.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/dripline/extensions/jitter/__init__.py b/dripline/extensions/jitter/__init__.py index ab3554f4..97ffbd91 100644 --- a/dripline/extensions/jitter/__init__.py +++ b/dripline/extensions/jitter/__init__.py @@ -1,21 +1,5 @@ __all__ = [] -import pkg_resources - -import scarab -a_ver = '0.0.0' #note that this is updated in the following block -try: - a_ver = pkg_resources.get_distribution('dragonfly').version - print('version is: {}'.format(a_ver)) -except: - print('fail!') - pass -version = scarab.VersionSemantic() -version.parse(a_ver) -version.package = 'project8/dragonfly' -version.commit = '---' -__all__.append("version") - from .jitter_endpoint import * from .jitter_endpoint import __all__ as __jitter_all __all__ += __jitter_all From 91141395e8c5ea9981ce8c3d593e1e86142e6247 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 20 Feb 2026 10:47:04 -0800 Subject: [PATCH 49/55] adjusted setup --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a6e23caa..96530a7e 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_namespace_packages -packages = find_namespace_packages('.', include=['dripline.extensions.*']) +packages = find_namespace_packages('.', include=['dripline.extensions','dripline.extensions.*']) print('packages are: {}'.format(packages)) setup( From c241cf4f7175cdfed33f96b743399661345b2bf7 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 20 Feb 2026 10:52:52 -0800 Subject: [PATCH 50/55] Added katcp wrapper --- dripline/extensions/katcp_wrapper.py | 1094 ++++++++++++++++++++++++++ dripline/extensions/r2daq.py | 2 +- 2 files changed, 1095 insertions(+), 1 deletion(-) create mode 100644 dripline/extensions/katcp_wrapper.py diff --git a/dripline/extensions/katcp_wrapper.py b/dripline/extensions/katcp_wrapper.py new file mode 100644 index 00000000..dc3d55c5 --- /dev/null +++ b/dripline/extensions/katcp_wrapper.py @@ -0,0 +1,1094 @@ +"""Client for communicating with a ROACH board over KATCP. + + @author Simon Cross + @modified Jason Manley + @Revised 2010/11/08 to log incomming log informs + @Revised 2010/06/28 to include qdr stuff + @Revised 2010/01/07 to include bulkread + @Revised 2009/12/01 to include print 10gbe core details. + """ + +import struct, threading, socket, logging, time, os + +from katcp import * +log = logging.getLogger("katcp") + +class FpgaAsyncRequest: + """A class to hold information about a specific KATCP request made by a Fpga. + """ + def __init__(self, host, request, request_id, inform_cb = None, reply_cb = None): + self.host = host + self.request = request + self.request_id = request_id + self.time_tx = time.time() + self.informs = [] + self.inform_times = [] + self.reply = None + self.reply_time = -1 + self.reply_cb = reply_cb + self.inform_cb = inform_cb + def __str__(self): + return '%s(%s)@(%10.5f) - reply%s - informs(%i)' % (self.request, self.request_id, self.time_tx, str(self.reply), len(self.informs)) + def got_reply(self, reply_message): + if not (reply_message.name == self.request): + error_string = 'rx reply(%s) does not match request(%s)' % (reply_message.name, self.request) + print(error_string) + raise RuntimeError(error_string) + self.reply = reply_message + self.reply_time = time.time() + if self.reply_cb != None: + self.reply_cb(self.host, self.request_id) + def got_inform(self, inform_message): + if self.reply != None: + raise RuntimeError('Received inform for message(%s,%s) after reply. Invalid?' % (self.request, self.request_id)) + if not (inform_message.name == self.request): + error_string = 'rx inform(%s) does not match request(%s)' % (inform_message.name, self.request) + print(error_string) + raise RuntimeError(error_string) + self.informs.append(inform_message) + self.inform_times.append(time.time()) + if self.inform_cb != None: + self.inform_cb(self.host, self.request_id) + def complete_ok(self): + '''Has this request completed successfully? + ''' + if self.reply == None: + return False + return self.reply.arguments[0] == Message.OK + +#class FpgaClient(BlockingClient): +class FpgaClient(CallbackClient): + """Client for communicating with a ROACH board. + + Notes: + - All commands are blocking. + - If there is no response to an issued command, an exception is thrown + with appropriate message after a timeout waiting for the response. + - If the TCP connection dies, an exception is thrown with an + appropriate message. + """ + + def __init__(self, host, port=7147, tb_limit=20, timeout=10.0, logger=log): + """Create a basic DeviceClient. + + @param self This object. + @param host String: host to connect to. + @param port Integer: port to connect to. + @param tb_limit Integer: maximum number of stack frames to + send in error traceback. + @param timeout Float: seconds to wait before timing out on + client operations. + @param logger Object: Logger to log to. + """ + super(FpgaClient, self).__init__(host, port, tb_limit = tb_limit, timeout = timeout, logger = logger) + self.host = host + self._timeout = timeout + self.start(True) + + # async stuff + self._nb_request_id_lock = threading.Lock() + self._nb_request_id = 0 + self._nb_requests_lock = threading.Lock() + self._nb_requests = {} + self._nb_max_requests = 100 + + """**********************************************************************************""" + """**********************************************************************************""" + + def _nb_get_request_by_id(self, request_id): + try: + return self._nb_requests[request_id] + except KeyError: + return None + + def _nb_pop_request_by_id(self, request_id): + try: + self._nb_requests_lock.acquire() + r = self._nb_requests.pop(request_id) + self._nb_requests_lock.release() + return r + except KeyError: + return None + + def _nb_pop_oldest_request(self): + req = self._nb_requests[list(self._nb_requests.keys())[0]] + for k, v in self._nb_requests.items(): + if v.time_tx < req.time_tx: + req = v + self._nb_requests_lock.acquire() + r = self._nb_pop_request_by_id(req.request_id) + self._nb_requests_lock.release() + return r + + def _nb_get_request_result(self, request_id): + req = self._nb_get_request_by_id(request_id) + return req.reply, req.informs + + def _nb_add_request(self, request_name, request_id, inform_cb, reply_cb): + if request_id in self._nb_requests: + raise RuntimeError('Trying to add request with id(%s) but it already exists.' % request_id) + self._nb_requests_lock.acquire() + self._nb_requests[request_id] = FpgaAsyncRequest(self.host, request_name, request_id, inform_cb, reply_cb) + self._nb_requests_lock.release() + + def _nb_get_next_request_id(self): + self._nb_request_id_lock.acquire() + self._nb_request_id += 1 + reqid = self._nb_request_id + self._nb_request_id_lock.release() + return str(reqid) + + def _nb_replycb(self, msg, *userdata): + """The callback for request replies. Check that the ID exists and call that request's got_reply function. + """ + request_id = ''.join(userdata) + if request_id not in self._nb_requests: + raise RuntimeError('Recieved reply for request_id(%s), but no such stored request.' % request_id) + self._nb_requests[request_id].got_reply(msg.copy()) + + def _nb_informcb(self, msg, *userdata): + """The callback for request informs. Check that the ID exists and call that request's got_inform function. + """ + request_id = ''.join(userdata) + if request_id not in self._nb_requests: + raise RuntimeError('Recieved inform for request_id(%s), but no such stored request.' % request_id) + self._nb_requests[request_id].got_inform(msg.copy()) + + def _nb_request(self, request, inform_cb = None, reply_cb = None, *args): + """Make a non-blocking request. + @param self This object. + @param request The request string. + @param inform_cb An optional callback function, called upon receipt of every inform to the request. + @param inform_cb An optional callback function, called upon receipt of the reply to the request. + @param args Arguments to the katcp.Message object. + """ + if len(self._nb_requests) == self._nb_max_requests: + oldreq = self._nb_pop_oldest_request() + self._logger.info("Request list full, removing oldest one(%s,%s)." % (oldreq.request, oldreq.request_id)) + print("Request list full, removing oldest one(%s,%s)." % (oldreq.request, oldreq.request_id)) + request_id = self._nb_get_next_request_id() + self._nb_add_request(request, request_id, inform_cb, reply_cb) + self.callback_request(msg = Message.request(request, *args), reply_cb = self._nb_replycb, inform_cb = self._nb_informcb, user_data = request_id) + return {'host': self.host, 'request': request, 'id': request_id} + + """**********************************************************************************""" + """**********************************************************************************""" + + def _request(self, name, request_timeout, *args): + """Make a blocking request and check the result. + + Raise an error if the reply indicates a request failure. + + @param self This object. + @param name String: name of the request message to send. + @param args List of strings: request arguments. + @return Tuple: containing the reply and a list of inform messages. + """ + request = Message.request(name, *args) + reply, informs = self.blocking_request(request, timeout = request_timeout) + #reply, informs = self.blocking_request(request,keepalive=True) + + if reply.arguments[0] != Message.OK: + self._logger.error("Request %s failed.\n Request: %s\n Reply: %s." + % (request.name, request, reply)) + + raise RuntimeError("Request %s failed.\n Request: %s\n Reply: %s." + % (request.name, request, reply)) + return reply, informs + + def listdev(self): + """Return a list of register / device names. + + @param self This object. + @return A list of register names. + """ + reply, informs = self._request("listdev", self._timeout) + return [i.arguments[0] for i in informs] + + def listbof(self): + """Return a list of executable files. + + @param self This object. + @return List of strings: list of executable files. + """ + reply, informs = self._request("listbof", self._timeout) + return [i.arguments[0] for i in informs] + + def listcmd(self): + """Return a list of available commands. this should not be made + available to the user, but can be used internally to query if a + command is supported. + + @todo Implement or remove. + @param self This object. + """ + raise NotImplementedError("LISTCMD not implemented by client.") + + def progdev(self, boffile): + """Program the FPGA with the specified boffile. + + @param self This object. + @param boffile String: name of the BOF file. + @return String: device status. + """ + if boffile=='' or boffile==None: + reply, informs = self._request("progdev", self._timeout) + self._logger.info("Deprogramming FPGA... %s."%(reply.arguments[0])) + else: + reply, informs = self._request("progdev", self._timeout, boffile) + self._logger.info("Programming FPGA with %s... %s."%(boffile,reply.arguments[0])) + return reply.arguments[0] + + def config_10gbe_core(self,device_name,mac,ip,port,arp_table,gateway=1,subnet_mask=0xffffff00): + """Hard-codes a 10GbE core with the provided params. It does a blindwrite, so there is no verifcation that configuration was successful (this is necessary since some of these registers are set by the fabric depending on traffic received). + + @param self This object. + @param device_name String: name of the device. + @param mac integer: MAC address, 48 bits. + @param ip integer: IP address, 32 bits. + @param port integer: port of fabric interface (16 bits). + @param subnet_mask integer: Subnet mask (32 bits). + @param arp_table list of integers: MAC addresses (48 bits ea). + """ + #assemble struct for header stuff... + #0x00 - 0x07: My MAC address + #0x08 - 0x0b: Not used + #0x0c - 0x0f: Gateway addr + #0x10 - 0x13: my IP addr + #0x14 - 0x17: Not assigned + #0x18 - 0x1b: Buffer sizes + #0x1c - 0x1f: Not assigned + #0x20 : soft reset (bit 0) + #0x21 : fabric enable (bit 0) + #0x22 - 0x23: fabric port + + #0x24 - 0x27: XAUI status (bit 2,3,4,5=lane sync, bit6=chan_bond) + #0x28 - 0x2b: PHY config + + #0x28 : RX_eq_mix + #0x29 : RX_eq_pol + #0x2a : TX_preemph + #0x2b : TX_diff_ctrl + + #0x38 - 0x3b: subnet mask + + #0x1000 : CPU TX buffer + #0x2000 : CPU RX buffer + #0x3000 : ARP tables start + + ctrl_pack=struct.pack('>QLLLLLLBBH',mac, 0, gateway, ip, 0, 0, 0, 0, 1, port) + subnet_mask_pack=struct.pack('>L',subnet_mask) + arp_pack=struct.pack('>256Q',*arp_table) + self.blindwrite(device_name,ctrl_pack,offset=0) + self.blindwrite(device_name,subnet_mask_pack,offset=0x38) + self.write(device_name,arp_pack,offset=0x3000) + + def tap_start(self, tap_dev, device, mac, ip, port): + """Program a 10GbE device and start the TAP driver. + + @param self This object. + @param device String: name of the device (as in simulink name). + @param tap_dev String: name of the tap device (a Linux identifier). If you want to destroy a device later, you need to use this name. + @param mac integer: MAC address, 48 bits. + @param ip integer: IP address, 32 bits. + @param port integer: port of fabric interface (16 bits). + + Please note that the function definition changed from corr-0.4.0 to corr-0.4.1 to include the tap_dev identifier. + """ + if len(tap_dev) > 8: + raise RuntimeError("Tap device identifier must be shorter than 9 characters. You specified %s for device %s." % (tap_dev, device)) + + ip_1 = (ip/(2**24)) + ip_2 = (ip%(2**24))/(2**16) + ip_3 = (ip%(2**16))/(2**8) + ip_4 = (ip%(2**8)) + mac0 = (mac & ((1<<48)-(1<<40))) >> 40 + mac1 = (mac & ((1<<40)-(1<<32))) >> 32 + mac2 = (mac & ((1<<32)-(1<<24))) >> 24 + mac3 = (mac & ((1<<24)-(1<<16))) >> 16 + mac4 = (mac & ((1<<16)-(1<<8))) >> 8 + mac5 = (mac & ((1<<8)-(1<<0))) >> 0 + + mac_str = "%02X:%02X:%02X:%02X:%02X:%02X"%(mac0,mac1,mac2,mac3,mac4,mac5) + ip_str = "%i.%i.%i.%i"%(ip_1,ip_2,ip_3,ip_4) + port_str = "%i"%port + + self._logger.info("Starting tgtap driver instance for %s: %s %s %s %s %s"%("tap-start", tap_dev, device, ip_str, port_str, mac_str)) + reply, informs = self._request("tap-start", self._timeout, tap_dev, device, ip_str, port_str, mac_str) + if reply.arguments[0]=='ok': return + else: raise RuntimeError("Failure starting tap device %s with mac %s, %s:%s"%(device,mac_str,ip_str,port_str)) + + def tap_stop(self, device): + """Stop a TAP driver. + @param self This object. + @param device String: name of the device you want to stop. + """ + reply, informs = self._request("tap-stop", self._timeout, device) + if reply.arguments[0]=='ok': return + else: raise RuntimeError("Failure stopping tap device %s."%(device)) + + def tap_multicast_add_send(self, tap_dev, ip, n_addresses=1): + """Adds a range of address to which the ROACH must send to (this is only needed if you plan to send multicast packets from the PPC; the FPGA fabric doesn't need anything here). Note that subsequent calls to this function will overwrite previous calls (you can only subscribe to one set of addresses). + @param self This object. + @param tap_dev String: name of the tap device (a Linux identifier). If you want to destroy a device later, you need to use this name. + @param ip integer: IP address, 32 bits. This should be 2^N bounded (ie if you're subscribing to 4 addresses, this address should have zeros in its lowest two bits). + @param n_addresses integer: Adjacent number of addresses to subscribe to. Note that this needs to be a power of 2 due to HW restrictions. + """ + if len(tap_dev) > 8: + raise RuntimeError("Tap device identifier must be shorter than 9 characters. You specified %s." % (tap_dev)) + if n_addresses<1: + raise RuntimeError("You need to subscribe to at least 1 address!") + if n_addresses&(n_addresses-1) != 0: + raise RuntimeError("The number of addresses needs to be a power of 2. You specified %s for device %s." % (n_addresses, tap_dev)) + + first_ip=ip&(0xffffffff-n_addresses+1) + last_ip=first_ip+ n_addresses + + ip_str_first=ip_to_a(first_ip) + ip_str_last=ip_to_a(last_ip) + + self._logger.info("Joining the multicast groups %s - %s." %(ip_str_first, ip_str_last)) + if n_addresses==1: + reply, informs = self._request("tap-multicast-add", self._timeout, tap_dev, 'send', str(ip_str_first)) + else: + reply, informs = self._request("tap-multicast-add", self._timeout, tap_dev, 'send', str(ip_str_first) + '+' + str(n_addresses-1)) + if reply.arguments[0]=='ok': return + else: raise RuntimeError("Failure adding multicast addresses %s-%s to tap device %s." %(ip_str_first,ip_str_last, tap_dev)) + + def tap_multicast_add_recv(self, tap_dev, ip, n_addresses=1): + """Adds a range of address to which the ROACH must receive from. Note that subsequent calls to this function will overwrite previous calls (you can only subscribe to one set of addresses). + @param self This object. + @param tap_dev String: name of the tap device (a Linux identifier). If you want to destroy a device later, you need to use this name. + @param ip integer: IP address, 32 bits. This should be 2^N bounded (ie if you're subscribing to 4 addresses, this address should have zeros in its lowest two bits). + @param n_addresses integer: Adjacent number of addresses to subscribe to. Note that this needs to be a power of 2 due to HW restrictions. Default of 1 means only this address. + """ + if len(tap_dev) > 8: + raise RuntimeError("Tap device identifier must be shorter than 9 characters. You specified %s." % (tap_dev)) + if n_addresses<1: + raise RuntimeError("You need to subscribe to at least 1 address!") + if n_addresses&(n_addresses-1) != 0: + raise RuntimeError("The number of addresses needs to be a power of 2. You specified %s for device %s." % (n_addresses, tap_dev)) + + first_ip=ip&(0xffffffff-n_addresses+1) + last_ip=first_ip+ n_addresses-1 + + ip_str_first=ip_to_a(first_ip) + ip_str_last=ip_to_a(last_ip) + + self._logger.info("Subscribing to the multicast groups %s - %s." %(ip_str_first, ip_str_last)) + #work around initial interface bug with '+x' off-by-one error: + if n_addresses==1: + reply, informs = self._request("tap-multicast-add", self._timeout, tap_dev, 'recv', str(ip_str_first)) + else: + reply, informs = self._request("tap-multicast-add", self._timeout, tap_dev, 'recv', str(ip_str_first) + '+' + str(n_addresses-1)) + if reply.arguments[0]=='ok': return + else: raise RuntimeError("Failure subscribing to multicast addresses %s-%s to tap device %s." %(ip_str_first,ip_str_last, tap_dev)) + + def tap_multicast_remove(self, tap_dev): + """Stop subscribing to all multicast addresses on specified TAP device. + + @param self This object. + @param tap_dev String: name of the device you want to stop. + """ + + reply, informs = self._request("tap-multicast_remove", self._timeout, tap_dev) + if reply.arguments[0]=='ok': return + else: raise RuntimeError("Failure stopping tap device %s." % (tap_dev)) + + def upload_program_bof(self, bof_file, port, timeout = 30): + """Upload a BORPH file to the ROACH board for execution. + @param self This object. + @param bof_file The path and/or filename of the bof file to upload. + @param port The port to use for uploading. + @param timeout The timeout to use for uploading. + @return + """ + # does the bof file exist on the local filesystem? + try: + os.path.getsize(bof_file) + except: + raise IOError('BOF file not found.') + import time, queue + def makerequest(result_queue): + try: + result = self._request('upload', timeout, port) + if(result[0].arguments[0] == Message.OK): + result_queue.put('OK') + else: + result_queue.put('Request to client returned, but not Message.OK.') + except: + result_queue.put('Request to client failed.') + def uploadbof(filename, result_queue): + upload_socket = socket.socket() + stime = time.time() + connected = False + while (not connected) and (time.time() < (stime + 2)): + try: + upload_socket = socket.socket() + upload_socket.connect((self.host, port)) + connected = True + except: + time.sleep(0.1) + if not connected: + result_queue.put('Could not connect to upload port.') + try: + upload_socket.send(open(filename).read()) + except: + result_queue.put('Could not send file to upload port.') + result_queue.put('OK') + # request thread + request_queue = queue.Queue() + request_thread = threading.Thread(target = makerequest, args = (request_queue,)) + # upload thread + upload_queue = queue.Queue() + upload_thread = threading.Thread(target = uploadbof, args = (bof_file, upload_queue,)) + # start the threads and join + old_timeout = self._timeout + self._timeout = timeout + request_thread.start() + upload_thread.start() + request_thread.join() + self._timeout = old_timeout + request_result = request_queue.get() + upload_result = upload_queue.get() + if (request_result != 'OK') or (upload_result != 'OK'): + raise Exception('Error: request(%s), upload(%s)' %(request_result, upload_result)) + debugstr = "Bof file upload for '%s': request (%s), upload (%s)" % (bof_file, request_result, upload_result) + self._logger.info(debugstr) + stime = time.time() + done = False + while (not done) and (time.time() < stime + 15): + try: + self.listdev() + done = True + #print "Got a successful listdev from %s!"%self.host + except: + time.sleep(0.1) + if not done: + raise RuntimeError('BOF file seemed to upload, but is not running?') + + def status(self): + """Return the status of the FPGA. + @param self This object. + @return String: FPGA status. + """ + reply, informs = self._request("status", self._timeout) + return reply.arguments[1] + + def ping(self): + """Tries to ping the FPGA. + @param self This object. + @return boolean: ping result. + """ + reply, informs = self._request("watchdog", self._timeout) + if reply.arguments[0].decode()=='ok': return True + else: return False + + def execcmd(self, string): + """Not yet supported. + + @todo Implement or remove. + @param self This object. + """ + raise NotImplementedError( + "EXEC not implemented by client.") + + def bulkread(self, device_name, size, offset=0): + """Return size_bytes of binary data with carriage-return escape-sequenced. + Uses much fast bulkread katcp command which returns data in pages + using informs rather than one read reply, which has significant buffering + overhead on the ROACH. + + @param self This object. + @param device_name String: name of device / register to read from. + @param size Integer: amount of data to read (in bytes). + @param offset Integer: offset to read data from (in bytes). + @return Bindary string: data read. + """ + reply, informs = self._request("bulkread", self._timeout, device_name, str(offset), str(size)) + return ''.join([i.arguments[0] for i in informs]) + + def read(self, device_name, size, offset=0): + """Return size_bytes of binary data with carriage-return + escape-sequenced. + + @param self This object. + @param device_name String: name of device / register to read from. + @param size Integer: amount of data to read (in bytes). + @param offset Integer: offset to read data from (in bytes). + @return Bindary string: data read. + """ + reply, informs = self._request("read", self._timeout, device_name, str(offset), + str(size)) + return reply.arguments[1] + + def read_dram(self, size, offset=0,verbose=False): + """Reads data from a ROACH's DRAM. Reads are done up to 1MB at a time. + The 64MB indirect address register is automatically incremented as necessary. + It returns a string, as per the normal 'read' function. + ROACH has a fixed device name for the DRAM (dram memory). + Uses bulkread internally. + + @param self This object. + @param size Integer: amount of data to read (in bytes). + @param offset Integer: offset to read data from (in bytes). + @return Binary string: data read. + """ + #Modified 2010-01-07 to use bulkread. + data=[] + n_reads=0 + last_dram_page = -1 + + dram_indirect_page_size=(64*1024*1024) + #read_chunk_size=(1024*1024) + if verbose: print('Reading a total of %8i bytes from offset %8i...'%(size,offset)) + + while n_reads < size: + dram_page=(offset+n_reads)/dram_indirect_page_size + local_offset = (offset+n_reads)%(dram_indirect_page_size) + #local_reads = min(read_chunk_size,size-n_reads,dram_indirect_page_size-(offset%dram_indirect_page_size)) + local_reads = min(size-n_reads,dram_indirect_page_size-(offset%dram_indirect_page_size)) + if verbose: print('Reading %8i bytes from indirect address %4i at local offset %8i...'%(local_reads,dram_page,local_offset)) + if last_dram_page != dram_page: + self.write_int('dram_controller',dram_page) + last_dram_page = dram_page + local_data=(self.bulkread('dram_memory',local_reads,local_offset)) + data.append(local_data) + #print 'done' + n_reads += local_reads + return ''.join(data) + + def write_dram(self, data, offset=0,verbose=False): + """Writes data to a ROACH's DRAM. Writes are done up to 512KiB at a time. + The 64MB indirect address register is automatically incremented as necessary. + ROACH has a fixed device name for the DRAM (dram memory) and so the user does not need to specify the write register. + + @param self This object. + @param data Binary packed string to write. + @param offset Integer: offset to read data from (in bytes). + @return Binary string: data read. + """ + size=len(data) + n_writes=0 + last_dram_page = -1 + + dram_indirect_page_size=(64*1024*1024) + write_chunk_size=(1024*512) + if verbose: print('writing a total of %8i bytes from offset %8i...'%(size,offset)) + + while n_writes < size: + dram_page=(offset+n_writes)/dram_indirect_page_size + local_offset = (offset+n_writes)%(dram_indirect_page_size) + local_writes = min(write_chunk_size,size-n_writes,dram_indirect_page_size-(offset%dram_indirect_page_size)) + if verbose: print('Writing %8i bytes from indirect address %4i at local offset %8i...'%(local_writes,dram_page,local_offset)) + if last_dram_page != dram_page: + self.write_int('dram_controller',dram_page) + last_dram_page = dram_page + + self.blindwrite('dram_memory',data[n_writes:n_writes+local_writes],local_offset) + n_writes += local_writes + + def write(self, device_name, data, offset=0): + """Should issue a read command after the write and compare return to + the string argument to confirm that data was successfully written. + + Throw exception if not match. (alternative command 'blindwrite' does + not perform this confirmation). + + @see blindwrite + @param self This object. + @param device_name String: name of device / register to write to. + @param data Byte string: data to write. + @param offset Integer: offset to write data to (in bytes) + """ + self.blindwrite(device_name, data, offset) + new_data = self.read(device_name, len(data), offset) + if new_data != data: + + unpacked_wrdata=struct.unpack('>L',data[0:4])[0] + unpacked_rddata=struct.unpack('>L',new_data[0:4])[0] + + self._logger.error("Verification of write to %s at offset %d failed. Wrote 0x%08x... but got back 0x%08x..." + % (device_name, offset, unpacked_wrdata, unpacked_rddata)) + raise RuntimeError("Verification of write to %s at offset %d failed. Wrote 0x%08x... but got back 0x%08x..." + % (device_name, offset, unpacked_wrdata, unpacked_rddata)) + + def blindwrite(self, device_name, data, offset=0): + """Unchecked data write. + + @see write + @param self This object. + @param device_name String: name of device / register to write to. + @param data Byte string: data to write. + @param offset Integer: offset to write data to (in bytes) + """ + assert (type(data)==bytes) , 'You need to supply binary packed string data!' + assert (len(data)%4) ==0 , 'You must write 32bit-bounded words!' + assert ((offset%4) ==0) , 'You must write 32bit-bounded words!' + self._request("write", self._timeout, device_name, str(offset), data) + + def read_int(self, device_name, offset=0): + """Calls .read() command with size=4, offset=0 and + unpacks returned four bytes into signed 32bit integer. + + @see read + @param self This object. + @param device_name String: name of device / register to read. + @param offset int: The offset (in 32bit words) at which to read; default is zero. + @return Integer: value read. + """ + data = self.read(device_name, 4, offset*4) + return struct.unpack(">i", data)[0] + + def write_int(self, device_name, integer, blindwrite=False, offset=0): + """Calls .write() with optional offset and integer packed into 4 bytes. + + @see write + @param self This object. + @param device_name String: name of device / register to write to. + @param integer Integer: value to write. + @param blindwrite Boolean: if true, don't verify the write (calls blindwrite instead of write function). + @param offset Integer: position in 32-bit words where to write data. + """ + # careful of packing input data into 32 bit - check range: if + # negative, must be signed int; if positive over 2^16, must be unsigned + # int. + if integer < 0: + data = struct.pack(">i", integer) + else: + data = struct.pack(">I", integer) + if blindwrite: + self.blindwrite(device_name,data,offset*4) + self._logger.debug("Blindwrite %8x to register %s at offset %d done." + % (integer, device_name, offset)) + else: + self.write(device_name, data, offset*4) + self._logger.debug("Write %8x to register %s at offset %d ok." + % (integer, device_name, offset)) + + def read_uint(self, device_name,offset=0): + """As in .read_int(), but unpack into 32 bit unsigned int. Optionally read at an offset 32-bit register. + + @see read_int + @param self This object. + @param device_name String: name of device / register to read from. + @param offset int: The offset (in 32bit words) at which to read; default is zero. + @return Integer: value read. + """ + data = self.read(device_name, 4, offset*4) + return struct.unpack(">I", data)[0] + + def stop(self): + """Stop the client. + + @param self This object. + """ + super(FpgaClient,self).stop() + self.join(timeout=self._timeout) + + def get_10gbe_core_details(self, dev_name): + """Prints 10GbE core details. + @param dev_name string: Name of the core. + """ + #assemble struct for header stuff... + #0x00 - 0x07: My MAC address + #0x08 - 0x0b: Not used + #0x0c - 0x0f: Gateway addr + #0x10 - 0x13: my IP addr + #0x14 - 0x17: Not assigned + #0x18 - 0x1b: Buffer sizes + #0x1c - 0x1f: Not assigned + #0x20 : soft reset (bit 0) + #0x21 : fabric enable (bit 0) + #0x22 - 0x23: fabric port + #0x24 - 0x27: XAUI status (bit 2,3,4,5=lane sync, bit6=chan_bond) + #0x28 - 0x2b: PHY config + #0x28 : RX_eq_mix + #0x29 : RX_eq_pol + #0x30 - 0x33: Multicast IP RX base address + #0x34 : Multicast IP RX IP mask + #0x38 - 0x3b: Subnet mask + #0x2a : TX_preemph + #0x2b : TX_diff_ctrl + #0x1000 : CPU TX buffer + #0x2000 : CPU RX buffer + #0x3000 : ARP tables start + + port_dump=list(struct.unpack('>16384B',self.read(dev_name,16384))) + #ip_prefix = '%3d.%3d.%3d.'%(port_dump[0x10],port_dump[0x11],port_dump[0x12]) + + rv={} + mymac=((port_dump[0o2]<<40) + (port_dump[0o3]<<32) + (port_dump[0o4]<<24) + (port_dump[0o5]<<16) + (port_dump[0o6]<<8) + port_dump[0o7]) + rv['mymac']=mymac + + gateway=((port_dump[0x0c]<<24) + (port_dump[0x0d]<<16) + (port_dump[0x0e]<<8) + (port_dump[0x0f])) + rv['gateway_ip']=gateway + + my_ip=((port_dump[0x10]<<24) + (port_dump[0x11]<<16) + (port_dump[0x12]<<8) + (port_dump[0x13])) + rv['my_ip']=my_ip + + rv['multicast_rx_base_ip']=((port_dump[0x30]<<24) + (port_dump[0x31]<<16) + (port_dump[0x32]<<8) + (port_dump[0x33])) + rv['multicast_rx_mask'] = ((port_dump[0x34]<<24) + (port_dump[0x35]<<16) + (port_dump[0x36]<<8) + (port_dump[0x37])) + rv['subnet_mask'] = ((port_dump[0x38]<<24) + (port_dump[0x39]<<16) + (port_dump[0x3a]<<8) + (port_dump[0x3b])) + possible_addresses=[rv['multicast_rx_base_ip']] + for i in range(32): + if not ((rv['multicast_rx_mask']>>i)&1): + #print "Found a zero!" + new_ips=[] + for ip in possible_addresses: + new_ips.append(ip&(~(1<16384B',self.read(dev_name,16384))) + ip_prefix= '%3d.%3d.%3d.'%(port_dump[0x10],port_dump[0x11],port_dump[0x12]) + + print('------------------------') + print('GBE0 Configuration...') + print('My MAC: ', end=' ') + for m in port_dump[0o2:0o2+6]: + print('%02X'%m, end=' ') + print('') + + print('Gateway: ', end=' ') + for g in port_dump[0x0c:0x0c+4]: + print('%3d'%g, end=' ') + print('') + + print('This IP: ', end=' ') + for i in port_dump[0x10:0x10+4]: + print('%3d'%i, end=' ') + print('') + + print('Subnet Mask: ', end=' ') + for i in port_dump[0x38:0x38+4]: + print('%3d'%i, end=' ') + print('') + + print('Gateware Port: ', end=' ') + print('%5d'%(port_dump[0x22]*(2**8)+port_dump[0x23])) + + print('Fabric interface is currently: ', end=' ') + if port_dump[0x21]&1: print('Enabled') + else: print('Disabled') + + + print('XAUI Status: ', end=' ') + print('%02X%02X%02X%02X'%(port_dump[0x24],port_dump[0x25],port_dump[0x26],port_dump[0x27])) + print('\t lane sync 0: %i'%bool(port_dump[0x27]&4)) + print('\t lane sync 1: %i'%bool(port_dump[0x27]&8)) + print('\t lane sync 2: %i'%bool(port_dump[0x27]&16)) + print('\t lane sync 3: %i'%bool(port_dump[0x27]&32)) + print('\t Channel bond: %i'%bool(port_dump[0x27]&64)) + + print('XAUI PHY config: ') + print('\tRX_eq_mix: %2X'%port_dump[0x28]) + print('\tRX_eq_pol: %2X'%port_dump[0x29]) + print('\tTX_pre-emph: %2X'%port_dump[0x2a]) + print('\tTX_diff_ctrl: %2X'%port_dump[0x2b]) + + if arp: + print('ARP Table: ') + for i in range(256): + print('IP: %s%3d: MAC:'%(ip_prefix,i), end=' ') + for m in port_dump[0x3000+i*8+2:0x3000+i*8+8]: + print('%02X'%m, end=' ') + print('') + + if cpu: + print('CPU TX Interface (at offset 4096bytes):') + print('Byte offset: Contents (Hex)') + for i in range(4096/8): + print('%04i: '%(i*8), end=' ') + for l in range(8): print('%02x'%port_dump[4096+8*i+l], end=' ') + print('') + print('------------------------') + + print('CPU RX Interface (at offset 8192bytes):') + print('CPU packet RX buffer unacknowledged data: %i'%port_dump[6*4+3]) + print('Byte offset: Contents (Hex)') + for i in range(port_dump[6*4+3]+8): + print('%04i: '%(i*8), end=' ') + for l in range(8): print('%02x'%port_dump[8192+8*i+l], end=' ') + print('') + print('------------------------') + + def est_brd_clk(self): + """Returns the approximate clock rate of the FPGA in MHz.""" + firstpass=self.read_uint('sys_clkcounter') + time.sleep(2) + secondpass=self.read_uint('sys_clkcounter') + if firstpass>secondpass: secondpass=secondpass+(2**32) + return (secondpass-firstpass)/2000000. + + def qdr_status(self,qdr): + """Checks QDR status (PHY ready and Calibration). NOT TESTED. + \n@param qdr integer QDR controller to query. + \n@return dictionary of calfail and phyrdy boolean responses.""" + #offset 0 is reset (write 0x111111... to reset). offset 4, bit 0 is phyrdy. bit 8 is calfail. + assert((type(qdr)==int)) + qdr_ctrl = struct.unpack(">I",self.read('qdr%i_ctrl'%qdr, 4, 4))[0] + return {'phyrdy':bool(qdr_ctrl&0x01),'calfail':bool(qdr_ctrl&(1<<8))} + + def qdr_rst(self,qdr): + """Performs a reset of the given QDR controller (tries to re-calibrate). NOT TESTED. + \n@param qdr integer QDR controller to query. + \n@returns nothing.""" + assert((type(qdr)==int)) + self.write_int('qdr%i_ctrl'%qdr,0xffffffff,blindwrite=True) + + def get_snap(self, dev_name, brams, man_trig=False, man_valid=False, wait_period=1, offset=-1, circular_capture=False,word_mult=1): + """Grabs all brams from a single snap block on this FPGA device.\n + \tdev_name: string, name of the snap block.\n + \tman_trig: boolean, Trigger the snap block manually.\n + \toffset: integer, wait this number of valids before beginning capture. Set to negative value if your hardware doesn't support this or the circular capture function.\n + \tcircular_capture: boolean, Enable the circular capture function.\n + \twait_period: integer, wait this number of seconds between triggering and trying to read-back the data. Make it negative to wait forever.\n + \tbrams: list, names of the bram components.\n + \tword_mult: The snap block reports how many words were captured. Here we can specify how wide a word is (in 32-bit multiples) in order to retrieve the correct number of bytes. set to 2 for 64b snap blocks, 4 for 128b etc. Else we assume a 32-bit wide bram and pull addr*4 bytes.\n + \tRETURNS: dictionary with keywords: \n + \t\tlengths: list of integers matching number of valids captured off each fpga.\n + \t\toffset: optional (depending on snap block version) list of number of valids elapsed since last trigger on each fpga. + \t\t{brams}: list of data from each fpga for corresponding bram.\n""" + #print "Deprecation warning: get_snap is to be deprecated. Please replace your design's %s block with a 'snapshot' block and use the snapshot_get function instead."%dev_name + self._logger.warn("Deprecation warning: get_snap is to be deprecated. Please replace your design's %s block with a 'snapshot' block and use the snapshot_get function instead."%dev_name) + #2011-02-03 JRM added circular capture to trigger statement. + # Invert logic for end detect. + # Added wait forever option. + # copy-paste errors from corr_functions :( really need to consolodate these snap functions. + #2010-02-19 JRM Updated to match snap_x. + #WORKING OK 2009-07-01 + if offset >= 0: + self.write_int(dev_name+'_trig_offset',offset) + #print 'Capturing from snap offset %i'%offset + + #print 'Triggering Capture...', + self.write_int(dev_name+'_ctrl',(0 + (man_trig<<1) + (man_valid<<2) + (circular_capture<<3))) + self.write_int(dev_name+'_ctrl',(1 + (man_trig<<1) + (man_valid<<2) + (circular_capture<<3))) + + done=False + start_time=time.time() + while not (done and (offset>=0 or circular_capture)) and ((time.time()-start_time)=0): + #print 'offset: %i,tr_en_cnt: %i'%(offset,self.read_uint(dev_name+'_tr_en_cnt')) + bram_dmp['offset']=self.read_uint(dev_name+'_tr_en_cnt') + offset - bram_size + else: bram_dmp['offset']=0 + + if (bram_dmp['offset'] < 0): + #you got a trigger and then a stop before the bram could even fill. + bram_dmp['offset']=0 + + for b,bram in enumerate(brams): + bram_path = dev_name+'_'+bram + if (bram_size == 0): + bram_dmp[bram]=[] + else: + bram_dmp[bram]=(self.read(bram_path,(bram_size+1)*4*word_mult)) + return bram_dmp + + def get_rcs(self,rcs_block_name='rcs'): + """Retrieves and decodes a revision control block.""" + rv={} + rv['user']=self.read_uint(rcs_block_name+'_user') + app=self.read_uint(rcs_block_name+'_app') + lib=self.read_uint(rcs_block_name+'_lib') + if lib&(1<<31): + rv['compile_timestamp']=lib&((2**31)-1) + else: + if lib&(1<<30): + #type is svn + rv['lib_rcs_type']='svn' + else: + #type is git + rv['lib_rcs_type']='git' + if lib&(1<<28): + #dirty bit + rv['lib_dirty']=True + else: + rv['lib_dirty']=False + rv['lib_rev']=lib&((2**28)-1) + if app&(1<<31): + rv['app_last_modified']=app&((2**31)-1) + else: + if app&(1<<30): + #type is svn + rv['app_rcs_type']='svn' + else: + #type is git + rv['app_rcs_type']='git' + if app&(1<<28): + #dirty bit + rv['app_dirty']=True + else: + rv['lib_dirty']=False + rv['app_rev']=app&((2**28)-1) + return rv + + def snapshot_arm(self, dev_name, man_trig=False, man_valid=False, offset=-1, circular_capture=False): + if offset >=0: + self.write_int(dev_name+'_trig_offset', offset) + #print 'Capturing from snap offset %i'%offset + #print 'Triggering Capture...', + self.write_int(dev_name + '_ctrl', (0 + (man_trig<<1) + (man_valid<<2) + (circular_capture<<3))) + self.write_int(dev_name + '_ctrl', (1 + (man_trig<<1) + (man_valid<<2) + (circular_capture<<3))) + + def snapshot_get(self, dev_name, man_trig=False, man_valid=False, wait_period=1, offset=-1, circular_capture=False, get_extra_val=False, arm=True): + """Grabs all brams from a single snap block on this FPGA device.\n + \tdev_name: string, name of the snap block.\n + \tman_trig: boolean, Trigger the snap block manually.\n + \toffset: integer, wait this number of bytes before beginning capture. Set to negative to ignore.\n + \tcircular_capture: boolean, Enable the circular capture function.\n + \twait_period: integer, wait this number of seconds between triggering and trying to read-back the data. Make it negative to wait forever.\n + \tRETURNS: dictionary with keywords: \n + \t\tlengths: number of bytes captured.\n + \t\toffset: number of bytes since last trigger.\n + \t\tdata: list of data from each fpga for corresponding bram.\n""" + # new snapshot block support (bytes instead of words) with hardware-configurable datawidth and user-selectable features. + #TODO Test offset, get_extra_val and circular capture modes. + if arm: + self.snapshot_arm(dev_name=dev_name, man_trig=man_trig, man_valid=man_valid, offset=offset, circular_capture=circular_capture) + done=False + start_time=time.time() + while not done and ((time.time()-start_time)>24),((ip&(0xff<<16))>>16),((ip&(0xff<<8))>>8),(ip&(0xff))) diff --git a/dripline/extensions/r2daq.py b/dripline/extensions/r2daq.py index 000bc8ef..0ab70374 100644 --- a/dripline/extensions/r2daq.py +++ b/dripline/extensions/r2daq.py @@ -25,7 +25,7 @@ zeros, ) from scipy.signal import firwin2, freqz -from katcp_wrapper import FpgaClient +from .katcp_wrapper import FpgaClient from copy import deepcopy from datetime import datetime From ae8b2f6cf55a91c49493bb5c4f1172f2a21fc612 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 20 Feb 2026 13:03:14 -0800 Subject: [PATCH 51/55] setup now reads from more directories. Dockerfile can install dependencies for DAQ --- Dockerfile | 10 ++++++++++ setup.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c22a1a9b..5e142801 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,9 +5,19 @@ ARG img_tag=v5.1.5 FROM ${img_user}/${img_repo}:${img_tag} +ARG enable_daq +ENV ENABLE_DAQ=${enable_daq} + COPY . /usr/local/src_dragonfly WORKDIR /usr/local/src_dragonfly +RUN if [ "$ENABLE_DAQ" = "true" ]; then \ + pip install \ + numpy==1.26.4 \ + scipy==1.14.1 \ + backports.ssl_match_hostname==3.7.0.1 \ + katcp==0.9.3 \ + ; fi RUN pip install docker pymodbus RUN pip install . diff --git a/setup.py b/setup.py index 96530a7e..a6a97060 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_namespace_packages -packages = find_namespace_packages('.', include=['dripline.extensions','dripline.extensions.*']) +packages = find_namespace_packages('.', include=['dripline.extensions','dripline.extensions.*','dragonfly','dragonfly.*']) print('packages are: {}'.format(packages)) setup( From 9583dae004f009831b9f58ee0e3ab0e69ddcacd6 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 20 Feb 2026 13:12:35 -0800 Subject: [PATCH 52/55] Adding installation of adc5g --- Dockerfile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5e142801..134fe6c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,8 +16,13 @@ RUN if [ "$ENABLE_DAQ" = "true" ]; then \ numpy==1.26.4 \ scipy==1.14.1 \ backports.ssl_match_hostname==3.7.0.1 \ - katcp==0.9.3 \ - ; fi + katcp==0.9.3; \ + git clone https://github.com/project8/adc_tests.git; \ + cd adc_tests; \ + git checkout master; \ + pip install .; \ + cd ..; \ + fi RUN pip install docker pymodbus RUN pip install . From 9363f6f008ee6c2964b7372818611b3e1bd8632a Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Fri, 27 Feb 2026 11:41:27 -0800 Subject: [PATCH 53/55] Added missing ping for roach2_interface is_running --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 134fe6c5..6da8cf85 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,8 @@ RUN if [ "$ENABLE_DAQ" = "true" ]; then \ git checkout master; \ pip install .; \ cd ..; \ + apt update; \ + apt install -y iputils-ping; \ fi RUN pip install docker pymodbus RUN pip install . From 65f47560a58e109f663ea13b0b76f552dd7ac480 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Sun, 1 Mar 2026 17:55:59 -0800 Subject: [PATCH 54/55] Adjusted get frequency, renamed file to dl3 paradigm. --- dripline/extensions/__init__.py | 2 +- ...yllid_provider.py => psyllid_interface.py} | 30 ++++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) rename dripline/extensions/{psyllid_provider.py => psyllid_interface.py} (94%) diff --git a/dripline/extensions/__init__.py b/dripline/extensions/__init__.py index 851e9dfb..323f1ab8 100644 --- a/dripline/extensions/__init__.py +++ b/dripline/extensions/__init__.py @@ -6,7 +6,7 @@ # Modules in this directory from .daq_run_interface import * -from .psyllid_provider import * +from .psyllid_interface import * from .r2daq import * from .roach_daq_run_interface import * from .roach2_interface import * diff --git a/dripline/extensions/psyllid_provider.py b/dripline/extensions/psyllid_interface.py similarity index 94% rename from dripline/extensions/psyllid_provider.py rename to dripline/extensions/psyllid_interface.py index db6bfe3f..4b3bf598 100644 --- a/dripline/extensions/psyllid_provider.py +++ b/dripline/extensions/psyllid_interface.py @@ -16,8 +16,8 @@ logger = logging.getLogger(__name__) -__all__.append('PsyllidProvider') -class PsyllidProvider(core.Service): +__all__.append('PsyllidInterface') +class PsyllidInterface(core.Service): ''' Provider for direct communication with up to 3 Psyllid instances with a single stream each ''' @@ -48,9 +48,7 @@ def check_all_psyllid_instances(self): try: self.request_status(channel) self.get_acquisition_mode(channel) - if self.freq_dict[channel] == None: - self.freq_dict[channel] = 50.0e6 - self.set_central_frequency(channel, self.freq_dict[channel]) + self.get_central_frequency(channel) except core.ThrowReply: self.status_dict[channel] = None self.status_value_dict[channel] = None @@ -59,7 +57,7 @@ def check_all_psyllid_instances(self): # Summary logger.info('Status of channels: {}'.format(self.status_value_dict)) - logger.info('Set central frequencies: {}'.format(self.freq_dict)) + logger.info('Current central frequencies: {}'.format(self.freq_dict)) logger.info('Streaming or triggering mode: {}'.format(self.mode_dict)) @@ -109,7 +107,8 @@ def get_acquisition_mode(self, channel): Tests whether psyllid is in streaming or triggering mode ''' request = 'node-list.{}'.format(self.channel_dict[channel]) - node_list = self.get(endpoint=self.queue_dict[channel], specifier=request)['nodes'] + reply = self.get(endpoint=self.queue_dict[channel], specifier=request) + node_list = reply['nodes'] if 'trw' in node_list: self.mode_dict[channel] = 'triggering' @@ -125,9 +124,9 @@ def request_status(self, channel): Asks the psyllid instance what state it is in and returns that state ''' logger.info('Checking Psyllid status of channel {}'.format(channel)) - result = self.get(endpoint=self.queue_dict[channel], specifier='daq-status', timeout_s=5) - self.status_dict[channel] = result['server']['status'] - self.status_value_dict[channel] = result['server']['status-value'] + reply = self.get(endpoint=self.queue_dict[channel], specifier='daq-status', timeout_s=5) + self.status_dict[channel] = reply['server']['status'] + self.status_value_dict[channel] = reply['server']['status-value'] logger.info('Psyllid is running. Status is {}'.format(self.status_dict[channel])) logger.info('Status in numbers: {}'.format(self.status_value_dict[channel])) return self.status_value_dict[channel] @@ -242,11 +241,17 @@ def get_central_frequency(self, channel): if self.mode_dict[channel] == None: logger.error('Acquisition mode is None. Cannot get central frequency from psyllid') raise core.ThrowReply('DriplineGenericDAQError', 'Acquisition mode is None. Update mode by using get_acquisition_mode command') + if self.request_status(channel) != 4: + logger.info('Psyllid instance is not activated. Getting inactive node configuration.') + status_query = 'node' + else: + logger.info('Psyllid instance is activated. Getting active node configuration.') + status_query = 'active' routing_key_map = { 'streaming':'strw', 'triggering':'trw' } - request = 'active-config.{}.{}'.format(self.channel_dict[channel], routing_key_map[self.mode_dict[channel]]) + request = '{}-config.{}.{}'.format(status_query, self.channel_dict[channel], routing_key_map[self.mode_dict[channel]]) result = self.get(endpoint=self.queue_dict[channel], specifier=request) logger.info('Psyllid says cf is {}'.format(result['center-freq'])) self.freq_dict[channel]=result['center-freq'] @@ -260,6 +265,9 @@ def set_central_frequency(self, channel, cf): if self.mode_dict[channel] == None: logger.error('Acquisition mode is None. Cannot set central frequency from psyllid') raise core.ThrowReply('DriplineGenericDAQError', 'Acquisition mode is None. Update mode by using get_acquisition_mode command') + if self.request_status(channel) != 4: + logger.error('Psyllid instance is not activated. Cannot set central frequency') + raise core.ThrowReply('DriplineGenericDAQError', 'Psyllid instance is not activated. Activate psyllid before setting central frequency') routing_key_map = { 'streaming':'strw', 'triggering':'trw' From 3fbb85372091fe359a98e00d10a8a7fd1efcddf9 Mon Sep 17 00:00:00 2001 From: Paul Kolbeck Date: Thu, 5 Mar 2026 21:42:01 -0800 Subject: [PATCH 55/55] manual calibration sets flag to calibrated. Additional checks on making a mask. --- dripline/extensions/psyllid_interface.py | 16 +++++++++++++--- dripline/extensions/roach2_interface.py | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/dripline/extensions/psyllid_interface.py b/dripline/extensions/psyllid_interface.py index 4b3bf598..9115683b 100644 --- a/dripline/extensions/psyllid_interface.py +++ b/dripline/extensions/psyllid_interface.py @@ -494,13 +494,21 @@ def get_n_triggers(self, channel='a'): return n_triggers - def make_trigger_mask(self, channel='a', filename='~/fmt_mask.json'): + def make_trigger_mask(self, channel='a', filename='/fmt_mask.json'): ''' Tells psyllid to record a frequency mask, write it to a json file and prepare for triggering run ''' + logger.info('Checking if psyllid is in triggering mode') + self.get_acquisition_mode(channel) if self.mode_dict[channel] != 'triggering': logger.error('Psyllid instance is not in triggering mode') raise core.ThrowReply('DriplineGenericDAQError', 'Psyllid instance is not in triggering mode') + + logger.info('Checking if psyllid is activated') + self.request_status(channel) + if self.status_value_dict[channel] != 4: + logger.error('Psyllid instance is not activated') + raise core.ThrowReply('DriplineGenericDAQError', 'Psyllid instance is not activated') logger.info('Switch tf_roach_receiver to freq-only') request = 'run-daq-cmd.{}.tfrr.freq-only'.format(str(self.channel_dict[channel])) @@ -514,9 +522,11 @@ def make_trigger_mask(self, channel='a', filename='~/fmt_mask.json'): logger.info('Telling psyllid to not use monarch when starting next run') self.set(endpoint=self.queue_dict[channel], specifier='use-monarch', value=False) + mask_duration = 1000 # ms logger.info('Start short run to record mask') - self.start_run(channel ,1000, self.temp_file) - time.sleep(1) + self.start_run(channel, mask_duration, self.temp_file) + # make sure run is done + time.sleep(mask_duration/1000 + 1) self._write_trigger_mask(channel, filename) diff --git a/dripline/extensions/roach2_interface.py b/dripline/extensions/roach2_interface.py index 923bbcce..7563ee2e 100644 --- a/dripline/extensions/roach2_interface.py +++ b/dripline/extensions/roach2_interface.py @@ -352,6 +352,7 @@ def calibrate_manually(self, gain1=None, gain2=None, gain3=None, gain4=None, off adc5g.set_spi_phase(self.roach2, 0, 3, phase3) if phase4 is not None: adc5g.set_spi_phase(self.roach2, 0, 4, phase4) + self.calibrated = True @property