diff --git a/scripts/casperfpga_tengbe_status.py b/scripts/casperfpga_tengbe_status.py index 0494551c..565fde2f 100644 --- a/scripts/casperfpga_tengbe_status.py +++ b/scripts/casperfpga_tengbe_status.py @@ -52,6 +52,9 @@ logging.basicConfig(level=eval('logging.%s' % log_level)) except AttributeError: raise RuntimeError('No such log level: %s' % log_level) +# import logging +# logging.basicConfig(filename='/tmp/casperfpga_tengbe_status_curses.log', level=logging.DEBUG) +# logging.info('****************************************************') if args.comms == 'katcp': HOSTCLASS = katcp_fpga.KatcpFpga @@ -155,6 +158,17 @@ def exit_gracefully(sig, frame): signal.signal(signal.SIGINT, exit_gracefully) signal.signal(signal.SIGHUP, exit_gracefully) +# work out the maximum host/core name +max_1st_col_offset = -1 +for fpga in fpgas: + max_1st_col_offset = max(max_1st_col_offset, len(fpga.host)) + for gbe_name in fpga.tengbes.names(): + max_1st_col_offset = max(max_1st_col_offset, len(gbe_name)) +max_fldname = -1 +for hdr in fpga_headers[0]: + max_fldname = max(max_fldname, len(hdr)) +max_1st_col_offset += 5 + # set up the curses scroll screen scroller = scroll.Scroll(debug=False) scroller.screen_setup() @@ -174,54 +188,50 @@ def exit_gracefully(sig, frame): scroller.draw_screen() if time.time() > last_refresh + polltime: scroller.clear_buffer() - scroller.add_line( + line = scroller.add_string( 'Polling %i host%s every %s - %is elapsed.' % ( len(fpgas), '' if len(fpgas) == 1 else 's', 'second' if polltime == 1 else ('%i seconds' % polltime), - time.time() - STARTTIME), 0, 0, absolute=True) - start_pos = 20 - pos_increment = 15 - if len(fpga_headers) == 1: - scroller.add_line('Host', 0, 1, absolute=True) - for reg in fpga_headers[0]: - scroller.add_line( - reg.rjust(pos_increment), start_pos, 1, absolute=True) - start_pos += pos_increment - scroller.set_ypos(newpos=2) - scroller.set_ylimits(ymin=2) - else: - scroller.set_ypos(1) - scroller.set_ylimits(ymin=1) + time.time() - STARTTIME), 0, 0, fixed=True) + start_pos = max_1st_col_offset + pos_increment = max_fldname + 2 + + scroller.set_current_line(1) + scroller.set_ylimits(1) + gbe_data = utils.threaded_fpga_operation(fpgas, 10, get_gbe_data) for ctr, fpga in enumerate(fpgas): + start_pos = max_1st_col_offset fpga_data = gbe_data[fpga.host] - scroller.add_line(fpga.host) + line = scroller.add_string(fpga.host) + for reg in fpga_headers[0]: + fld = '{val:>{width}}'.format(val=reg, width=max_fldname) + line = scroller.add_string(fld, start_pos) + start_pos += pos_increment + scroller.add_string('', cr=True) for core, core_data in fpga_data.items(): tap_running = tap_data[fpga.host][core]['name'] == '' fpga_data[core]['tap_running'] = not tap_running fpga_data[core]['ip'] = tap_data[fpga.host][core]['ip'] - start_pos = 20 - scroller.add_line(core, 5) + start_pos = max_1st_col_offset + line = scroller.add_string(core) for header_register in fpga_headers[0]: core_regname = header_register.replace('gbe', core) - if start_pos < 200: - if core_regname in core_data.keys(): - if not isinstance(core_data[core_regname], str): - regval = '%d' % core_data[core_regname] - else: - regval = core_data[core_regname] - else: - regval = 'n/a' - # all on the same line - scroller.add_line(regval.rjust(pos_increment), - start_pos, - scroller.get_current_line() - 1) - start_pos += pos_increment + if core_regname in core_data.keys(): + fld = '{val:>{width}}'.format( + val=core_data[core_regname], + width=max_fldname) + else: + fld = '{val:>{width}}'.format( + val='n/a', width=max_fldname) + scroller.add_string(fld, start_pos) + start_pos += pos_increment + scroller.add_string('', cr=True) scroller.draw_screen() last_refresh = time.time() else: - time.sleep(0.1) + time.sleep(0.05) except Exception, e: exit_gracefully(None, None) raise diff --git a/setup.py b/setup.py index ff790131..1a8bc619 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,9 @@ provides=['casperfpga'], packages=['casperfpga'], # , 'casperfpga.test'], package_dir={'casperfpga': 'src'}, - scripts=glob.glob('scripts/*') + scripts=glob.glob('scripts/*'), + setup_requires=['katversion'], + use_katversion=True ) # end diff --git a/src/__init__.py b/src/__init__.py index d2c3a523..502faf3d 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -14,4 +14,15 @@ from snap import Snap from tengbe import TenGbe -# end \ No newline at end of file +# BEGIN VERSION CHECK +# Get package version when locally imported from repo or via -e develop install +try: + import katversion as _katversion +except ImportError: + import time as _time + __version__ = "0.0+unknown.{}".format(_time.strftime('%Y%m%d%H%M')) +else: + __version__ = _katversion.get_version(__path__[0]) +# END VERSION CHECK + +# end diff --git a/src/casperfpga.py b/src/casperfpga.py index 0d2f4476..82953be1 100644 --- a/src/casperfpga.py +++ b/src/casperfpga.py @@ -436,8 +436,12 @@ def get_system_information(self, filename=None, fpg_info=None): except KeyError: LOGGER.warn('%s: no sys info key in design info!' % self.host) # and RCS information if included - if '77777_git' in device_dict: - self.rcs_info['git'] = device_dict['77777_git'] + for device_name in device_dict: + if device_name.startswith('77777_git_'): + name = device_name[device_name.find('_', 10) + 1:] + if 'git' not in self.rcs_info: + self.rcs_info['git'] = {} + self.rcs_info['git'][name] = device_dict[device_name] if '77777_svn' in device_dict: self.rcs_info['svn'] = device_dict['77777_svn'] diff --git a/src/katcp_fpga.py b/src/katcp_fpga.py index 4c6132f8..a80e6e3d 100644 --- a/src/katcp_fpga.py +++ b/src/katcp_fpga.py @@ -576,6 +576,27 @@ def __str__(self): (self.host, self._bindaddr[1], 'connected' if self.is_connected() else 'disconnected') + @staticmethod + def _process_git_info(metalist): + """ + Git information in the FPG must be processed. + :param metalist: + :return: + """ + got_git = {} + time_pref = str(int(time.time())).replace('.', '_') + '_' + for ctr, parms in enumerate(metalist): + name, tag, param, value = parms + if name == '77777_git': + assert tag == 'rcs' + newname = name + '_' + time_pref + param + if newname not in got_git: + got_git[newname] = (newname, tag, 'file-name', param) + param = value[0] + value = value[1:] + metalist[ctr] = (newname, tag, param, value) + metalist.extend(got_git.values()) + def _read_design_info_from_host(self, device=None): """ Katcp request for extra system information embedded in the boffile. @@ -617,11 +638,13 @@ def _read_design_info_from_host(self, device=None): value = value[0] name = name.replace('/', '_') metalist.append((name, tag, param, value)) + self._process_git_info(metalist) return create_meta_dictionary(metalist) def _read_coreinfo_from_host(self): """ - Get the equivalent of coreinfo.tab from the host using KATCP listdev commands. + Get the equivalent of coreinfo.tab from the host using + KATCP listdev commands. :return: """ LOGGER.debug('%s: reading coreinfo' % self.host) @@ -629,35 +652,42 @@ def _read_coreinfo_from_host(self): listdev_size = self.listdev(getsize=True) listdev_address = self.listdev(getaddress=True) if len(listdev_address) != len(listdev_size): - raise RuntimeError('Different length listdev(size) and listdev(detail)') + raise RuntimeError('Different length listdev(size) and ' + 'listdev(detail)') for byte_dev, byte_size in listdev_size: matched = False for addrdev, address in listdev_address: if addrdev == byte_dev: byte_size = int(byte_size.split(':')[0]) address = int(address.split(':')[0], 16) - memorymap_dict[byte_dev] = {'address': address, 'bytes': byte_size} + memorymap_dict[byte_dev] = {'address': address, + 'bytes': byte_size} matched = True continue if not matched: - raise RuntimeError('No matching listdev address for device %s' % byte_dev) + raise RuntimeError('No matching listdev address for ' + 'device %s' % byte_dev) return memorymap_dict def get_system_information(self, filename=None, fpg_info=None): """ Get information about the design running on the FPGA. - If filename is given, get it from there, otherwise query the host via KATCP. + If filename is given, get it from there, otherwise query the + host via KATCP. :param filename: fpg filename + :param fpg_info: a tuple with two dictionaries of FPG information :return: the information is populated in the class """ if (not self.is_running()) and (filename is None): - raise RuntimeError('This can only be run on a running device when no file is given.') + raise RuntimeError('This can only be run on a running device ' + 'when no file is given.') if filename is not None: device_dict, memorymap_dict = parse_fpg(filename) else: device_dict = self._read_design_info_from_host() memorymap_dict = self._read_coreinfo_from_host() - super(KatcpFpga, self).get_system_information(fpg_info=(device_dict, memorymap_dict)) + super(KatcpFpga, self).get_system_information( + fpg_info=(device_dict, memorymap_dict)) def unhandled_inform(self, msg): """ diff --git a/src/scroll.py b/src/scroll.py index 754cc1de..fe2509fa 100644 --- a/src/scroll.py +++ b/src/scroll.py @@ -1,6 +1,6 @@ """ -Playing with ncurses in Python to scroll up and down, left and right, through a list of data -that is periodically refreshed. +Playing with ncurses in Python to scroll up and down, left and right, +through a list of data that is periodically refreshed. Revs: 2010-12-11 JRM Added concat for status line to prevent bailing on small terminals. @@ -9,50 +9,76 @@ """ import curses +import logging + +# LOGGER = logging.getLogger(__name__) def screen_teardown(): - """Restore sensible options to the terminal upon exit """ + Restore sensible options to the terminal upon exit + """ + logging.debug('Ncurses screen teardown') curses.nocbreak() curses.echo() curses.endwin() class Screenline(object): - def __init__(self, data, xpos=-1, ypos=-1, absolute=False, attributes=curses.A_NORMAL): + def __init__(self, data, xpos, ypos, cr=True, fixed=False, + attributes=curses.A_NORMAL): + """ + :param data: A String to be placed on the screen + :param xpos: x position, -1 means at current xpos + :param ypos: y position, -1 means at current ypos + :param ypos: if True, start a new line after this one + :param fixed: if True, always at this pos from top left, scrolling + makes no difference + :param attributes: Curses string attributes + :return: + """ assert type(data) == str self.data = data self.xpos = xpos self.ypos = ypos - self.absolute = absolute + self.cr = cr + self.fixed = fixed if not isinstance(attributes, list): attributes = [attributes] self.line_attributes = attributes + self.changed = True + + def __repr__(self): + return '"%s" len(%i) @ (%i,%i)%s' % ( + self.data, len(self.data), self.xpos, self.ypos, + ' cr' if self.cr else '' + ' fixed' if self.fixed else '' + ) class Scroll(object): - """Scrollable ncurses screen. + """ + Scrollable ncurses screen. """ def __init__(self, debug=False): - """Constructor - """ self._instruction_string = '' self._offset_y = 0 self._offset_x = 0 self._screen = None self._curr_y = 0 - self._curr_x = 0 + # self._curr_x = 0 self._sbuffer = [] self._xmin = 0 self._xmax = 0 self._ymin = 0 self._ymax = 0 self._debugging = debug + # LOGGER.debug('New Scroll() created.') # set up the screen def screen_setup(self): - """Set up a curses screen object and associated options + """ + Set up a curses screen object and associated options """ self._screen = curses.initscr() self._screen.keypad(1) @@ -62,6 +88,9 @@ def screen_setup(self): height, width = self._screen.getmaxyx() self._ymax = height - 1 self._xmax = width + # LOGGER.debug('Scroll() set up - max x/y = %i/%i' % ( + # self._xmax, self._ymax + # )) def on_keypress(self): """ @@ -69,6 +98,10 @@ def on_keypress(self): """ key = self._screen.getch() if key > 0: + try: + key_char = chr(key) + except ValueError: + key_char = '_' if key == 259: self._offset_y -= 1 # up elif key == 258: @@ -77,98 +110,144 @@ def on_keypress(self): self._offset_x -= 1 # right elif key == 260: self._offset_x += 1 # left - elif chr(key) == 'q': + elif key_char == 'q': return [-1, 'q'] - elif chr(key) == 'u': + elif key_char == 'u': self._offset_y -= self._ymax + 1 - elif chr(key) == 'd': + elif key_char == 'd': self._offset_y += self._ymax + 1 - elif chr(key) == 'l': + elif key_char == 'l': self._offset_x += self._xmax - elif chr(key) == 'r': + elif key_char == 'r': self._offset_x -= self._xmax - elif chr(key) == 'h': + elif key_char == 'h': self._offset_x = 0 self._offset_y = 0 - try: - char = chr(key) - except ValueError: - char = '_' - return [key, char] + # LOGGER.debug('[%i %s] keypress event' % (key, key_char)) + return [key, key_char] else: return [0, '_'] def clear_screen(self): - """Clear the ncurses screen. + """ + Clear the ncurses screen. """ self._screen.clear() - def add_line(self, new_line, xpos=-1, ypos=-1, absolute=False, attributes=curses.A_NORMAL): - """Add a text line to the screen buffer. + def cr(self): + """ + Carriage return, go to the next line + :return: + """ + self._curr_y += 1 + + def add_string(self, new_str, xpos=-1, ypos=-1, cr=False, + fixed=False, attributes=curses.A_NORMAL): """ - if not isinstance(new_line, str): - raise TypeError('new_line must be a string!') - yposition = ypos - if yposition < 0: - yposition = self._curr_y + Add a string to a position on the screen. + :param new_str: + :param xpos: + :param ypos: + :param cr: + :param fixed: + :param attributes: + :return: + """ + if fixed and ((xpos == -1) or (ypos == -1)): + # LOGGER.error('Cannot have a fixed string with undefined position: ' + # '"%s" @ (%i, %i) fixed' % (new_str, xpos, ypos)) + raise ValueError('Cannot have a fixed string with undefined ' + 'position') + # LOGGER.debug('Added STR len(%i) to position (%i,%i) cr(%s) ' + # 'fixed(%s)' % (len(new_str), xpos, ypos, + # 'yes' if cr else 'no', + # 'yes' if fixed else 'no')) + self._sbuffer.append( + Screenline(new_str, xpos, + ypos if ypos > -1 else self._curr_y, + cr, fixed, attributes) + ) + if cr: self._curr_y += 1 - self._sbuffer.append(Screenline(new_line, xpos, yposition, absolute, attributes)) + return self._sbuffer[-1] + + def add_line(self, new_line, attributes=curses.A_NORMAL): + """ + Add a text line to the screen buffer. + :param new_line: + :param attributes: + :return: + """ + # LOGGER.debug('Added LINE len(%i) to line %i' % ( + # len(new_line), self._curr_y)) + self._sbuffer.append( + Screenline(new_line, 0, self._curr_y, True, False, attributes) + ) + self._curr_y += 1 + return self._sbuffer[-1] def get_current_line(self): - """Return the current y position of the internal screen buffer. + """ + Return the current y position of the internal screen buffer. """ return self._curr_y def set_current_line(self, linenum): - """Set the current y position of the internal screen buffer. + """ + Set the current y position of the internal screen buffer. + :param linenum: + :return: """ self._curr_y = linenum def _load_buffer_from_list(self, screendata): - """Load the internal screen buffer from a given mixed list of + """ + Load the internal screen buffer from a given mixed list of strings and Screenlines. """ if not isinstance(screendata, list): raise TypeError('Provided screen data must be a list!') self._sbuffer = [] + self._curr_y = 0 for line in screendata: if not isinstance(line, Screenline): line = Screenline(line) self._sbuffer.append(line) + self._curr_y += 1 def _sbuffer_y_max(self): - """Work out how many lines the sbuffer needs. """ - maxy = 0 + Work out how many lines the sbuffer needs. + """ + if len(self._sbuffer) == 0: + return 0 + maxy = 1 for sline in self._sbuffer: if sline.ypos == -1: - maxy += 1 - else: - maxy = max(maxy, sline.ypos) + # LOGGER.error('ypos of -1 makes no sense') + raise RuntimeError('ypos of -1 makes no sense') + maxy = max(maxy, sline.ypos) return maxy - def _calculate_screen_pos(self, sline, yposition): - if sline.absolute: - strs = 0 - stre = self._xmax - strx = sline.xpos - stry = sline.ypos - else: - stringx = max(sline.xpos, 0) + self._offset_x - if stringx < 0: - xpos = 0 - strs = stringx * -1 - else: - xpos = stringx - strs = 0 - stre = strs + self._xmax - stry = sline.ypos - if stry == -1: - stry = yposition - yposition += 1 - strx = xpos - stry -= self._offset_y - return strs, stre, strx, stry, yposition + # def _calculate_screen_pos(self, sline): + # """ + # Calculate the current screen position of a ScreenLine, given its + # properties. + # :param sline: a ScreenLine + # :return: (str_start, str_end), (str_xpos, str_ypos) + # """ + # if sline.fixed: + # return (0, self._xmax), (sline.xpos, sline.ypos) + # xpos_shifted = sline.xpos + self._offset_x + # if xpos_shifted < 0: + # xpos = 0 + # strs = xpos_shifted * -1 + # else: + # xpos = xpos_shifted + # strs = 0 + # stre = min(self._xmax, strs + len(sline.data)) + # ypos = sline.ypos + self._offset_y + # return (strs, stre), (xpos, ypos) def draw_screen(self, data=None): """ @@ -178,44 +257,157 @@ def draw_screen(self, data=None): self._screen.clear() if data is not None: self._load_buffer_from_list(data) - num_lines_total = self._sbuffer_y_max() - yposition = 0 - top_line = 0 - for sline in self._sbuffer: - (strs, stre, strx, stry, yposition) = self._calculate_screen_pos(sline, yposition) - # if we want to put the string outside the right-hand edge of the terminal, bail - if strx >= self._xmax: + + height, width = self._screen.getmaxyx() + self._ymax = height - 1 + self._xmax = width - 1 + + # LOGGER.debug('draw_screen:') + # LOGGER.debug('\t%i ScreenLine items to process' % len(self._sbuffer)) + # + # # debug, print the lines to be drawn + # for sctr, sline in enumerate(self._sbuffer): + # LOGGER.debug('\t%i - %s' % (sctr, sline)) + # + # # loop through lines, compute their new x and y pos and and write + # # them to ncurses buffer + # LOGGER.debug('xoff(%i) yoff(%i)' % (self._offset_x, self._offset_y)) + + # work out relative positions + ymax = 0 + curr_ypos = 0 + curr_xpos = 0 + positions = [] + for sctr, sline in enumerate(self._sbuffer): + if sline.fixed: + # fixed position on the screen always + xpos = sline.xpos + ypos = sline.ypos + ymax = max(ymax, ypos) + positions.append((xpos, ypos)) + continue + if sline.xpos == -1: + # tailing on from the last line + xpos = curr_xpos + ypos = curr_ypos if sline.ypos == -1 else sline.ypos + else: + # other lines + xpos = sline.xpos + ypos = curr_ypos if sline.ypos == -1 else sline.ypos + positions.append((xpos, ypos)) + ymax = max(ymax, ypos) + if sline.cr: + curr_xpos = 0 + curr_ypos += 1 + else: + curr_xpos += len(sline.data) + + # LOGGER.debug('Initial positions:') + # for sctr, position in enumerate(positions): + # LOGGER.debug('\tstr_%i: %s' % (sctr, position)) + + # shift them + for sctr, sline in enumerate(self._sbuffer): + if sline.fixed: + continue + pos = positions[sctr] + newpos = ( + pos[0] + self._offset_x, + pos[1] + self._offset_y, + ) + positions[sctr] = newpos + + # LOGGER.debug('Shifted positions:') + # for sctr, position in enumerate(positions): + # LOGGER.debug('\tstr_%i: %s' % (sctr, position)) + + # truncate strings if necessary + string_limits = [] + for sctr, sline in enumerate(self._sbuffer): + pos = positions[sctr] + str_start = 0 + str_end = len(sline.data) + if pos[0] < 0: + str_start = pos[0] * -1 + else: + if pos[0] + str_end > self._xmax: + str_end = self._xmax - pos[0] + if str_end < 0: + str_end = 0 + string_limits.append((str_start, str_end)) + + # LOGGER.debug('Truncated strings:') + # for sctr, sline in enumerate(self._sbuffer): + # lims = string_limits[sctr] + # dstr = sline.data[lims[0]:lims[1]] + # LOGGER.debug('\tstr_%i: %s - %s' % (sctr, lims, dstr)) + + # correct position of truncated strings + for sctr, sline in enumerate(self._sbuffer): + pos = positions[sctr] + positions[sctr] = ( + max(0, pos[0]), + pos[1] + ) + # LOGGER.debug('Corrected positions:') + # for sctr, position in enumerate(positions): + # LOGGER.debug('\tstr_%i: %s' % (sctr, position)) + + # show them + got_data = False + for sctr, sline in enumerate(self._sbuffer): + pos = positions[sctr] + if pos[1] < 0: + continue + if pos[1] >= self._ymax: + continue + if pos[0] > self._xmax: + continue + # are there soft min limits? + if not sline.fixed: + if (self._xmin > 0) and (pos[0] < self._xmin): + continue + if (self._ymin > 0) and (pos[1] < self._ymin): + continue + str_lims = string_limits[sctr] + drstr = sline.data[str_lims[0]:str_lims[1]] + if len(drstr) == 0: continue - drawstring = sline.data[strs:stre] - if self._debugging: - drawstring += '_(%d,%d,[%d:%d])' % (strx, stry, strs, stre) try: - if sline.absolute: - self._screen.addstr(stry, strx, drawstring, *sline.line_attributes) - elif (stry >= self._ymin) and (stry < self._ymax): - self._screen.addstr(stry, strx, drawstring, *sline.line_attributes) - top_line = self._offset_y - yposition - except Exception, e: - e.args = ('strs(%d) stre(%d) strx(%d) stry(%d) xmax(%d) ymax(%d)_\'%s\' - ' % (strs, stre, strx, stry, - self._xmax, self._ymax, - drawstring) + e.args[0],) - raise - if yposition + self._offset_y >= self._ymax - 2: - break - if self._debugging: - self._screen.addstr(self._ymax - 2, 0, 'offsets(%d,%d) dims(%d,%d) sbuf_ymax(%d) xlim(%d,%d) ylim(%d,%d)' % - (self._offset_x, self._offset_y, self._xmax, self._ymax + 1, - num_lines_total, self._xmin, self._xmax, self._ymin, self._ymax)) - stat_line = 'Showing line %i to %i of %i. Column offset %i. %s Scroll with arrow keys. \ + got_data = True + self._screen.addstr(pos[1], pos[0], + drstr, *sline.line_attributes) + except Exception as e: + # LOGGER.error( + # 'ERROR drawing str_%i - currx_y(%i,%i) - start_stop(%i,%i)' + # ' slinex_y(%i,%i) xpos_ypos(%i,%i) -> %s\n' + # 'Exception: %s' % ( + # sctr, curr_xpos, curr_ypos, str_lims[0], str_lims[1], + # sline.xpos, sline.ypos, + # pos[0], pos[1], + # drstr, + # e.message)) + continue + if not got_data: + if (self._offset_x != 0) or (self._offset_y != 0): + drstr = '' + else: + drstr = '' + if len(drstr) > self._xmax: + drstr = drstr[0:self._xmax] + self._screen.addstr(0, 0, drstr) + stat_line = 'Row offset %i. Column offset %i. %s Scroll with arrow keys. \ u, d, l, r = page up, down, left and right. h = home, q = quit.' %\ - (top_line, yposition, num_lines_total, self._offset_x, self._instruction_string) + (self._offset_y * -1, self._offset_x, + self._instruction_string) + stat_line = stat_line[0:self._xmax] self._screen.addstr(self._ymax, 0, stat_line, curses.A_REVERSE) self._screen.refresh() def clear_buffer(self): self._sbuffer = [] self._curr_y = 0 - self._curr_x = 0 + # self._curr_x = 0 def set_xlimits(self, xmin=-1, xmax=-1): if xmin == -1 and xmax == -1: @@ -233,8 +425,8 @@ def set_ylimits(self, ymin=-1, ymax=-1): if ymax > -1: self._ymax = ymax - def set_ypos(self, newpos): - self._curr_y = newpos + # def set_ypos(self, newpos): + # self._curr_y = newpos # set and get the instruction string at the bottom def get_instruction_string(self): @@ -243,22 +435,23 @@ def get_instruction_string(self): def set_instruction_string(self, new_string): self._instruction_string = new_string - def draw_string(self, new_string, **kwargs): - """Draw a new line to the screen, takes an argument as to whether the screen should be - immediately refreshed or not - """ - raise NotImplementedError - try: - refresh = kwargs.pop('refresh') - except KeyError: - refresh = False - self._screen.addstr(self._curr_y, self._curr_x, new_string, **kwargs) - if new_string.endswith('\n'): - self._curr_y += 1 - self._curr_x = 0 - else: - self._curr_x += len(new_string) - if refresh: - self._screen.refresh() + # def draw_string(self, new_string, **kwargs): + # """ + # Draw a new line to the screen, takes an argument as to whether the + # screen should be immediately refreshed or not + # """ + # raise NotImplementedError + # try: + # refresh = kwargs.pop('refresh') + # except KeyError: + # refresh = False + # self._screen.addstr(self._curr_y, self._curr_x, new_string, **kwargs) + # if new_string.endswith('\n'): + # self._curr_y += 1 + # self._curr_x = 0 + # else: + # self._curr_x += len(new_string) + # if refresh: + # self._screen.refresh() # end of file diff --git a/src/tengbe.py b/src/tengbe.py index 437b3f74..844dcfb7 100644 --- a/src/tengbe.py +++ b/src/tengbe.py @@ -78,7 +78,7 @@ def from_roach_hostname(cls, hostname, port_num): hostname = hostname.replace('cbf_oach', 'roach') # /HACK if not hostname.startswith('roach'): - raise RuntimeError('Only hostnames beginning with' + raise RuntimeError('Only hostnames beginning with ' 'roach supported: %s' % hostname) digits = hostname.replace('roach', '') serial = [int(digits[ctr:ctr+2], 16) for ctr in range(0, 6, 2)] @@ -219,6 +219,7 @@ def __init__(self, parent, name, address, length_bytes, device_info=None): self.core_details = None self.snaps = {'tx': None, 'rx': None} self.registers = {'tx': [], 'rx': []} + self.multicast_subscriptions = [] # TODO # if self.parent.is_connected(): # self._check() @@ -321,7 +322,8 @@ def read_counters(self): for direction in ['tx', 'rx']: for reg in self.registers[direction]: tmp = self.parent.memory_devices[reg].read() - results[reg] = tmp['data']['reg'] + keyname = self.name + '_' + direction + 'ctr' + results[keyname] = tmp['data']['reg'] return results def rx_okay(self, wait_time=0.2, checks=10): @@ -398,28 +400,32 @@ def dhcp_start(self): if self.mac is None: # TODO get MAC from EEPROM serial number and assign here self.mac = '0' - reply, _ = self.parent.katcprequest(name="tap-start", request_timeout=5, - require_ok=True, - request_args=(self.name, self.name, '0.0.0.0', - str(self.port), str(self.mac), )) + reply, _ = self.parent.katcprequest( + name='tap-start', request_timeout=5, + require_ok=True, + request_args=(self.name, self.name, '0.0.0.0', + str(self.port), str(self.mac), )) if reply.arguments[0] != 'ok': raise RuntimeError('%s: failure starting tap driver.' % self.name) - reply, _ = self.parent.katcprequest(name="tap-arp-config", request_timeout=1, - require_ok=True, - request_args=(self.name, "mode", "0")) + reply, _ = self.parent.katcprequest( + name='tap-arp-config', request_timeout=1, + require_ok=True, + request_args=(self.name, 'mode', '0')) if reply.arguments[0] != 'ok': raise RuntimeError('%s: failure disabling ARP.' % self.name) - reply, _ = self.parent.katcprequest(name="tap-dhcp", request_timeout=30, - require_ok=True, - request_args=(self.name, )) + reply, _ = self.parent.katcprequest( + name='tap-dhcp', request_timeout=30, + require_ok=True, + request_args=(self.name, )) if reply.arguments[0] != 'ok': raise RuntimeError('%s: failure starting DHCP client.' % self.name) - reply, _ = self.parent.katcprequest(name="tap-arp-config", request_timeout=1, - require_ok=True, - request_args=(self.name, "mode", "-1")) + reply, _ = self.parent.katcprequest( + name='tap-arp-config', request_timeout=1, + require_ok=True, + request_args=(self.name, 'mode', '-1')) if reply.arguments[0] != 'ok': raise RuntimeError('%s: failure re-enabling ARP.' % self.name) @@ -439,9 +445,10 @@ def tap_start(self, restart=False): LOGGER.info('%s: tap already running.' % self.fullname) return LOGGER.info('%s: starting tap driver.' % self.fullname) - reply, _ = self.parent.katcprequest(name="tap-start", request_timeout=-1, require_ok=True, - request_args=(self.name, self.name, str(self.ip_address), - str(self.port), str(self.mac), )) + reply, _ = self.parent.katcprequest( + name='tap-start', request_timeout=-1, require_ok=True, + request_args=(self.name, self.name, str(self.ip_address), + str(self.port), str(self.mac), )) if reply.arguments[0] != 'ok': raise RuntimeError('%s: failure starting tap driver.' % self.fullname) @@ -518,6 +525,8 @@ def multicast_receive(self, ip_str, group_size): request_args=(self.name, 'recv', mcast_group_string, )) if reply.arguments[0] == 'ok': + if mcast_group_string not in self.multicast_subscriptions: + self.multicast_subscriptions.append(mcast_group_string) return else: raise RuntimeError("%s: failed adding multicast receive %s to " @@ -534,10 +543,16 @@ def multicast_remove(self, ip_str): request_args=(self.name, IpAddress.str2ip(ip_str), )) except: - raise RuntimeError("%s: tap-multicast-remove does not seem to " - "be supported on %s" % (self.fullname, + raise RuntimeError('%s: tap-multicast-remove does not seem to ' + 'be supported on %s' % (self.fullname, self.parent.host)) if reply.arguments[0] == 'ok': + if ip_str not in self.multicast_subscriptions: + LOGGER.warning( + '%s: That is odd, %s removed from mcast subscriptions, but ' + 'it was not in its list of sbscribed addresses.' % ( + self.fullname, ip_str)) + self.multicast_subscriptions.remove(ip_str) return else: raise RuntimeError('%s: failed removing multicast address %s ' diff --git a/src/utils.py b/src/utils.py index 04ceea38..054cc97e 100644 --- a/src/utils.py +++ b/src/utils.py @@ -15,17 +15,22 @@ def create_meta_dictionary(metalist): :return: a dictionary of device info, keyed by unique device name """ meta_items = {} - for name, tag, param, value in metalist: - if name not in meta_items.keys(): - meta_items[name] = {} - try: - if meta_items[name]['tag'] != tag: - raise ValueError( - 'Different tags - %s, %s - for the same item %s' % ( - meta_items[name]['tag'], tag, name)) - except KeyError: - meta_items[name]['tag'] = tag - meta_items[name][param] = value + try: + for name, tag, param, value in metalist: + if name not in meta_items: + meta_items[name] = {} + try: + if meta_items[name]['tag'] != tag: + raise ValueError( + 'Different tags - %s, %s - for the same item %s' % ( + meta_items[name]['tag'], tag, name)) + except KeyError: + meta_items[name]['tag'] = tag + meta_items[name][param] = value + except ValueError as e: + for ctr, contents in enumerate(metalist): + print ctr, contents + raise e return meta_items @@ -55,9 +60,11 @@ def parse_fpg(filename): # some versions of mlib_devel may mistakenly have put spaces # as delimiters where tabs should have been used. Rectify that # here. + if line.startswith('?meta '): + LOGGER.warn('An old version of mlib_devel generated %s. Please ' + 'update. Meta fields are seperated by spaces, ' + 'should be tabs.' % filename) line = line.replace(' ', '\t') - LOGGER.warn('An old version of mlib_devel generated %s. Please ' - 'update.' % filename) # and carry on as usual. line = line.replace('\_', ' ').replace('?meta', '') line = line.replace('\n', '').lstrip().rstrip() @@ -285,9 +292,15 @@ def threaded_fpga_function(fpga_list, timeout, target_function): target_function = _check_target_func(target_function) def dofunc(fpga, *args, **kwargs): - rv = eval('fpga.%s' % target_function[0])(*args, **kwargs) - return rv - return threaded_fpga_operation(fpga_list, timeout, (dofunc, target_function[1], target_function[2])) + try: + rv = getattr(fpga, target_function[0])(*args, **kwargs) + return rv + except AttributeError: + LOGGER.error('FPGA %s has no such function: %s' % ( + fpga.host, target_function[0])) + raise + return threaded_fpga_operation( + fpga_list, timeout, (dofunc, target_function[1], target_function[2])) def threaded_fpga_operation(fpga_list, timeout, target_function): @@ -313,7 +326,7 @@ def jobfunc(resultq, fpga): thread_list = [] for fpga_ in fpga_list: thread = threading.Thread(target=jobfunc, args=(result_queue, fpga_)) - thread.daemon = True + thread.setDaemon(True) thread.start() thread_list.append(thread) for thread_ in thread_list: