3 Commits 1ca0c906ec ... 005e133213

Author SHA1 Message Date
  Timo Dritschler 005e133213 Added support for setting Gain and Offset of more than 4 channels 3 years ago
  Timo Dritschler 5965971070 Changed line ending convention to unix 3 years ago
  Timo Dritschler 9e2f1b7fa3 Revert Bank Switching 3 years ago
2 changed files with 955 additions and 884 deletions
  1. 1 6
      KCG/base/backend/DataSet.py
  2. 954 878
      KCG/base/backend/board/board_config.py

+ 1 - 6
KCG/base/backend/DataSet.py

@@ -196,13 +196,8 @@ class DataSet(object):
 
         finedelay_factor = board_config.get("chip_delay_factor")
 
-        #TRIAGE:
-        #FMC Banks are currently (18.12.2020) switched, so the slots for the
-        #ADC delay slots 1,2,3,4 are for ADCs 5,6,7,8 and vice versa
         fdelays = board_config.get("chip_delay")
-        finedelays = fdelays[4:] + fdelays[:4]
-        
-        finedelays = [i * finedelay_factor for i in finedelays]
+        finedelays = [i * finedelay_factor for i in fdelays]
 
 
 

+ 954 - 878
KCG/base/backend/board/board_config.py

@@ -1,878 +1,954 @@
-"""
-Configuration for each board
-"""
-
-import os
-import sys
-if sys.version_info[:3] < (3,0):
-    import ConfigParser as configparser
-else:
-    import configparser
-import numpy as np
-import logging
-from PyQt4 import QtGui, QtCore
-from time import sleep
-
-from .communication import *
-from .... import config as kcg_config
-
-
-
-class BoardConfiguration(QtGui.QWidget):
-    """
-    The Main configuration class for boards.
-    """
-    callback_signal = QtCore.pyqtSignal(str, list)
-    
-    def __init__(self, identifier, config_file=None):
-        from . import sequences
-        super(BoardConfiguration, self).__init__()
-        self.callback_signal.connect(self._notify_observers_receiver)
-        self.identifier = identifier
-        self._config = {}
-        self._observers = {}
-        self._observers_for_all = []
-        self._observers_write = {}
-        self._set_defaults()
-        self._get_board_version()
-        if self._config['board_version'] > 10:
-            logging.critical('Unknown Board Version - gui not working! - restart with active board')
-            print('Unknown Board Version - gui not working! - restart with active board')
-        else:
-            logging.info('Detected Board Version: {}'.format(self._config["board_version"]))
-            print('Detected Board Version: {}'.format(self._config["board_version"]))
-
-        self._sequences = sequences.read_sequence(self._config["board_version"])
-        self.set_default_observers()
-
-        if self.is_KAPTURE2():
-            self.update('bunches_per_turn', kcg_config.bunches_per_turn)
-        else:
-            self._config['delay_330_factor'] = 150
-            self._config['delay_330_max'] = 15
-            self._config['chip_delay'] = [3,3,3,3]
-
-        if config_file:
-            self.load_config(config_file)
-
-        #self.notify_all_observers(True)
-
-    def _set_defaults(self):
-        """
-        Set default values
-        """
-        self._config = {
-            'board_version' : 7,
-            'lastDataSet': None,
-
-            'adc_number': 8,
-            'samplingrate': 1,
-
-            'bunches_per_turn': 184,
-            'header_byte_size': 32,
-
-            'chip_delay_max': 31,
-            'chip_delay' : [0,0,0,0,0,0,0,0],
-            'bunch_shift' : [2,2,2,2,2,2,2,2], #Offset by +2 to encode -2 to +2 range
-            'chip_delay_factor': 3,
-
-            
-            'delay_330_max': 20, #technical 522 #be aware: changing this makes the Delay Calibration invalid!
-            'delay_330_factor': 330,
-            'delay_330_th':   0,
-            'delay_330_adc':  1,
-            'delay_330_fpga': 1,
-
-            #-Kapture 2 only--------------------
-            'delay_25_max' : 23, #be aware: changing this makes the Delay Calibration invalid!
-            'delay_25_factor': 25,
-            'delay_25_th':   0,
-            'delay_25_adc':  10,
-            'delay_25_fpga': 0,
-
-            #-Second FMC------------------
-            'delay_330_th_2':   0,
-            'delay_330_adc_2':   1,
-            #'delay_330_fpga_2':   0,
-            'delay_25_th_2':   4,
-            'delay_25_adc_2':  10,
-            #'delay_25_fpga_2': 0,
-            'default_25_th_2': 4,
-
-            'delay_cascade_max': 20,
-            'delay_cascade_factor': 330,
-            'delay_cascade': 0,
-            'delay_cascade_25': 14,
-
-            #-Clock Division--------------
-            'clk_div': 6,
-            'clk_div_th':   6,
-            'clk_div_adc':  6,
-            'clk_div_fpga': 6,
-            'clk_div_cascade': 24,
-
-            #-Kapture 1 only-------------------
-            'th_to_adc_cycles': 7,
-            #'adc_1_delay_individual': -1,
-            #----------------------------------            
-            
-            'turns_observe': 1000,
-            'turns_skip': 0,
-            'acquisition_count': 10,
-            'turns_wait_time': 15,
-            
-            'trigger_skip': 0,
-            'trigger_timeout': 12,
-            'trigger_method': 1,
-            'use_trigger': False,
-
-            'build_spectrograms': False,
-            'pilot_bunch': True,
-            'header': True if kcg_config.save_header is True else False,
-
-            #Kapture 2
-            'adc_offset': [0x0000, 0x0000, 0x0000, 0x0000,  0x0000, 0x0000, 0x0000, 0x0000],
-            'adc_gain':   [0x0000, 0x0000, 0x0000, 0x0000,  0x0000, 0x0000, 0x0000, 0x0000]
-        }
-
-    def _get_board_version(self):
-        try:
-            self._config["board_version"] = int(pci.read(self.identifier, reg='0x9030')[0])
-            if self._config["board_version"] == 6:
-                self._config["board_version"] = 5
-            elif self._config["board_version"] == 7:
-                self._config["adc_number"] = 8
-
-        except Exception as e:
-            self._config["board_version"] = 0xDEAD
-
-
-    def is_KAPTURE2(self):
-        return self._config["board_version"] >4
-
-        ##########################################################################
-        #  .d8888b.
-        # d88P  Y88b
-        # Y88b.
-        #  "Y888b.   .d88b.  .d88888888  888 .d88b. 88888b.  .d8888b .d88b.
-        #     "Y88b.d8P  Y8bd88" 888888  888d8P  Y8b888 "88bd88P"   d8P  Y8b
-        #       "88888888888888  888888  88888888888888  888888     88888888
-        # Y88b  d88PY8b.    Y88b 888Y88b 888Y8b.    888  888Y88b.   Y8b.
-        #  "Y8888P"  "Y8888  "Y88888 "Y88888 "Y8888 888  888 "Y8888P "Y8888
-        #                        888
-        #                        888
-        #                        888
-        #
-
-    def get_sequence_list(self):
-        if self._sequences =={}:
-            return "No Sequences"
-        return self._sequences["sequence_names"] 
-
-    def get_init_order(self):
-        if self._sequences =={}:
-            return "No Sequences"
-        return self._sequences["initialization_sequence_order"]
-
-    def get_sequence_comment(self, sequence_name):
-        if self._sequences =={}:
-            return "No Sequences"
-        return self._sequences[sequence_name]["Comment"]
-
-    def get_sequence_status(self, sequence_name):
-        if self._sequences =={}:
-            return "No Sequences"
-        return self._sequences[sequence_name]["status_val"]
-
-    def run_sequence(self, name, progressbar=None):
-        if self._sequences == {}:
-            return False
-        from . import sequences
-        return sequences.run_sequence(self.identifier, self._sequences[name]["sequence"], progressbar)
-
-
-        ##########################################################################
-        ##########################################################################
-    def load_config(self, filename):
-        """
-        Load a config from a file
-        :param filename: the configuration file
-        :return:
-        """
-        if filename:
-            config = configparser.RawConfigParser()
-            if not config.read(str(filename)):
-                return False
-
-            for key in list(self._config.keys()):
-                try:
-                    if type(self._config[key]) == int:
-                        self._config[key] = config.getint('Config', key)
-                    elif type(self._config[key]) == bool:
-                        self._config[key] = config.getboolean('Config', key)
-                    logging.vinfo("Read '%s' for '%s' from '%s'"%(str(self._config[key]), key, str(filename)))
-                except configparser.NoOptionError as e:
-                    pass
-                except configparser.NoSectionError as e:
-                    pass
-            return True
-        else:
-            return False
-
-    def save_config(self, filename):
-        """
-        Save the current configuration to a file
-        :param filename: the file to write to
-        """
-        if filename:
-            # with open(filename, 'w') as f:
-            try:
-                f = open(filename, 'w')
-                cp = configparser.RawConfigParser()
-                cp.add_section('Config')
-                for key in list(self._config.keys()):
-                    cp.set('Config', key, self._config[key])
-                f.write('#\n'
-                        '#  KCG   (KAPTURE Control Gui) Configuration file\n'
-                        '#\n'
-                        '#  (c) Karlsruhe Institute of Technology, 2015\n'
-                        '#  All rights reserved.\n'
-                        '#\n'
-                        '#  Applicable Gui Version(s): 1.0 - 1.0.2\n'
-                        '#\n'
-                        '#  Saved at: ' + time.asctime() + '\n'
-                        '#\n\n')
-                cp.write(f)
-            except (IOError, TypeError):
-                return False
-            return True
-        else:
-            return False
-
-    def get(self, key):
-        """
-        Get the configuration value for key
-        :param key: the key to get the value for
-        :return: the value of the configuration for key
-        """
-        if not key in self._config:
-            raise NoSuchKeyError(key+" is not registered in BoardConfiguration for board "+str(self.identifier))
-        return self._config.get(key, None)
-
-    def dump(self):
-        """
-        Dump all configuration values
-        :return: all configuration values as list
-        """
-        s = ""
-        for key in list(self._config.keys()):
-            s += key + ": " + str(self.get(key)) + ", "
-        return s[:-1]
-
-
-        ##########################################################################
-        #  .d88888b. 888
-        # d88P" "Y88b888
-        # 888     888888
-        # 888     88888888b. .d8888b  .d88b. 888d888888  888 .d88b. 888d888.d8888b
-        # 888     888888 "88b88K     d8P  Y8b888P"  888  888d8P  Y8b888P"  88K
-        # 888     888888  888"Y8888b.88888888888    Y88  88P88888888888    "Y8888b.
-        # Y88b. .d88P888 d88P     X88Y8b.    888     Y8bd8P Y8b.    888         X88
-        # "Y88888P" 88888P"  88888P' "Y8888 888      Y88P   "Y8888 888     88888P'
-               
-
-    def set_default_observers(self):
-        """
-        Set observers that are always used
-        """
-        #def obsprint(x,y):
-        #    print(x, y)
-        #self.observe_all(obsprint)
-        self.observe_write(self._update_header, 'header')
-        self.observe_write(self._update_pilot_bunch, 'pilot_bunch')
-        
-        self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x9020'), 'turns_observe')
-        self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x9028'), 'turns_skip')
-        if self._config['board_version'] > 4: # everything for KAPTURE 2
-            self.observe_write(lambda x: pci.write(self.identifier, hex(x-2), '0x90E0'), 'bunches_per_turn')
-            self.observe_write(self._set_adc_gain,   'adc_gain')
-            self.observe_write(self._set_adc_offset, 'adc_offset')
-
-            #Attention: the th delays are inverted! Means: due to the internal setup by increasing the value the sampling
-                #would be earlier. This is not intuitive therefore the gui inverts the value by using the delay_max as offset.
-            #Setup Long delay 330ps. Value needs to be shifted to the left because first bit is used to set halfstep
-            self.observe_write(lambda x: pci.write(self.identifier, hex((x+5)<<1), '0x9090'), 'delay_330_adc')
-            self.observe_write(lambda x: pci.write(self.identifier, hex((self._config['delay_330_max']-x+5)<<1), '0x90B0'), 'delay_330_th')
-            self.observe_write(lambda x: pci.write(self.identifier, hex((x+5)<<1), '0x90C0'), 'delay_330_fpga')
-
-            self.observe_write(lambda x: pci.write(self.identifier, hex((x+5)<<1), '0x90D0'), 'delay_cascade')
-            
-            #Setup short Delay 25ps.
-            self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x9094'), 'delay_25_adc')
-            self.observe_write(lambda x: pci.write(self.identifier, hex(self._config['delay_25_max']-x), '0x90B4'), 'delay_25_th')
-            #self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x90C4'), 'delay_25_fpga')
-
-            #Setup Clock Div
-            self.observe_write(self._set_clk_div_all , 'clk_div') #one to set it all - normaly they should be all the same
-            self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x9098'), 'clk_div_adc')
-            self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x90B8'), 'clk_div_th')
-            self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x90C8'), 'clk_div_fpga')
-            self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x90D8'), 'clk_div_cascade')
-
-            self.observe_write(self._set_chip_delay,   'chip_delay')
-            self.observe_write(self._set_samplingrate, 'samplingrate')
-            self.observe_write(self._set_bunch_shift, 'bunch_shift')
-
-            if self._config['adc_number'] > 4:
-                #Setup Long delay 330ps. Value needs to be shifted to the left because first bit is used vor halfstep
-                self.observe_write(lambda x: pci.write(self.identifier, hex((x+5)<<1), '0x90A0'), 'delay_330_adc_2')
-                self.observe_write(lambda x: pci.write(self.identifier, hex((self._config['delay_330_max']-x+5)<<1), '0x90A8'), 'delay_330_th_2')
-                
-                #Setup short Delay 25ps
-                self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x90A4'), 'delay_25_adc_2')
-                self.observe_write(lambda x: pci.write(self.identifier, hex(self._config['delay_25_max']-x), '0x90AC'), 'delay_25_th_2')
-
-
-        else:
-            self.observe_write(self._set_fpga_delay, 'delay_330_fpga')
-            self.observe_write(self._set_chip_delay, 'chip_delay')
-            self.observe_write(self._set_th_delay,   'delay_330_th')
-            self.observe_write(self._set_adc_delay,  'delay_330_adc')
-
-    def notify_all_observers(self, write=False):
-        """
-        Notify all observers not only the ones that are affected by a change
-        """
-        for key, value in list(self._config.items()):
-            self._notify_observers(key, value, write)
-
-            # observers = self._observers.get(key, None)
-            # if observers:
-            #     for (who, callback) in observers:
-            #         callback(self.get(key))
-
-
-    def update(self, key, value, entry=None, write=True):
-        """
-        Update the value for key in the configuration
-        :param key: the key to update
-        :param value: the value to set the configuration for key to
-        :param entry: if key is array to select the array index.
-        """
-        if entry is not None:
-            self._config[key][entry] = value
-        else:    
-            self._config[key] = value
-        self._notify_observers(key, self._config[key], write)
-
-    def updateSilent(self, key, value, entry=None):
-        """
-        Update the configuration without notifying observers
-        :param key: the key to updae
-        :param value: the value to set the configuration of key to
-        :param entry: if key is array to select the array index.
-        """
-        if entry is not None:
-            self._config[key][entry] = value
-        else:    
-            self._config[key] = value
-
-    def observe(self, who, callback, key):
-        """
-        Register an observer. (A callback that is called when the according configuration value changes)
-        :param who: who is observing
-        :param callback: the callback to call on change of the configuration value
-        :param key: the key to observe
-        """
-        if key not in list(self._config.keys()):
-            raise ObserverError(str("Key '%s' is unknown." % key))
-
-        if self._observers.get(key, None) is None:
-            self._observers[key] = []
-
-        self._observers[key].append([who, callback])
-
-    def observe_all(self, callback):
-        """
-        Register a observer that is called when any key changes
-        :param callback: the callback to register
-        """
-        if callback not in self._observers_for_all:
-            self._observers_for_all.append(callback)
-        else:
-            raise ObserverError("Observer already registered")
-
-    def observe_write(self, callback, key):
-        """
-        Register an observer that writes the value for Key on the Board
-        :param callback: the callback to call on change of the configuration value
-        :param key: the key to observe
-        """
-        if key not in list(self._config.keys()):
-            raise ObserverError(str("Key '%s' is unknown." % key))
-
-        if self._observers_write.get(key, None) is None:
-            self._observers_write[key] = []
-
-        self._observers_write[key].append(callback)
-
-    def unobserve(self, who, key=None):
-        """
-        Remove an observer
-        :param who: the observing entity
-        :param key: the key to remove it from
-        """
-        if key is not None:
-            observers = np.array(self._observers.get(key, None))
-            if observers is None:
-                return
-            if who not in observers[:, 0]:
-                return
-            for i, _obs in enumerate(self._observers[key]):
-                if _obs[0] is who:
-                    del self._observers[key][i]
-                    if not self._observers[key]:
-                        del self._observers[key]
-            return
-
-        for _key in list(self._observers.keys()):
-            for i, _obs in enumerate(self._observers[_key]):
-                if _obs[0] is who:
-                    del self._observers[_key][i]
-                    if not self._observers[_key]:
-                        del self._observers[_key]
-
-    def unobserve_all_observer(self, callback):
-        """
-        Unobserve an observer that observes all keys.
-        :param callback: the callback to unobserve
-        """
-        if callback in self._observers_for_all:
-            del self._observers_for_all[self._observers_for_all.index(callback)]
-
-    def _notify_observers_receiver(self, key, args):
-        """
-        The pyqt signal slot for notifications of observers
-        :param key: the key that changed
-        :param value: the new value
-        """
-        observers = self._observers.get(str(key), None)
-        
-        value = args[0]
-        write = args[1]
-        if observers is not None:
-            for (who, callback) in observers:
-                try:
-                    callback(value)
-                except Exception as e:
-                    log.error('Observer Callback error: {}'.format(e))
-
-        for cb in self._observers_for_all:
-            try:
-                cb(key, value)
-            except:
-                pass
-
-        if write:
-            observers = self._observers_write.get(str(key), None)
-            if observers is not None:
-                for callback in observers:
-                    try:
-                        #log.debug('write ' + key , args)
-                        callback(value)
-                    except Exception as e:
-                        log.error('Observer Callback error: {}'.format(e))
-                    time.sleep(0.025)
-
-    def _notify_observers(self, key, value, write=True):
-        """
-        Notify observers. This emits a pyqt signal to make it thread save
-        :param key: the key that changed
-        :param value: the new value
-        """
-        #print("_notify_observers", key, value, write)
-        self.callback_signal.emit(key, [value, write])
-
-    def make_uint(self, value, maximum, name=None):
-        """
-        Convert a value to an uint
-        :param value: the value
-        :param maximum: the maximum of the returned value
-        :param name: the name of this value
-        :return: the converted value
-        """
-        if value is None:
-            raise ValueError(str("%s Value is invalid (None)" % name))
-
-        val = None
-        try:
-            val = int(value)
-        except ValueError:
-            raise ValueError(str("%s Value is not a valid number" % name))
-
-        if maximum is not None:
-            if val > maximum:
-                raise ValueError(str("%s Value is too large (>%i)" % (name, maximum)))
-
-        if val < 0:
-            raise ValueError(str("%s Values below 0 are not allowed" % name))
-
-        return val
-
-    def _set_clk_div_all(self, value):
-        self.update('clk_div_adc', value)
-        self.update('clk_div_th', value)
-        self.update('clk_div_fpga', value)
-        self.update('clk_div_cascade', value)
-
-
-    def _set_chip_delay(self, values):
-        """
-        Set the chip_delays on the board
-        :param values: the value to set the delays to
-        """
-    
-        _values = []
-        for value in values[:4]:
-            _values.append(self.make_uint(value, self.get('chip_delay_max'), 'ADC_Value'))
-        #print("ADC1: ", values, _values)
-        reg_value = ''
-        mask = ''
-        # Chip Delays are stored as 'ADC_4 ADC_3 ADC_2 ADC_1' in the register.
-        # Therefore, we need to traverse the factors array in reverse order
-        for value in _values: #reversed(_values): # not for Kapture 2
-            if value is not None:
-                reg_value += '{0:02x}'.format(value)
-                mask += 'ff'
-            else:
-                reg_value += '00'
-                mask += '00'
-
-        if self._config['adc_number'] > 4:
-            pci.write(self.identifier, reg_value, '0x9084', hex_mask=mask)
-        else:
-            pci.write(self.identifier, reg_value, '0x9080', hex_mask=mask)
-        
-        s = "Setting ADC Delays:"
-        for (adc, value) in enumerate(_values):
-            s += ' ADC_%i Fine Delay: %i,' % (adc, value)
-        s = s[:-1]  # cut away the last dangling ','
-        logging.vinfo(s)
-        
-        if self._config['adc_number'] > 4:
-            _values = []
-            for value in values[4:]:
-                _values.append(self.make_uint(value, self.get('chip_delay_max'), 'ADC_Value'))
-            #print("ADC 2", values, _values)
-            reg_value = ''
-            mask = ''
-            # Chip Delays are stored as 'ADC_4 ADC_3 ADC_2 ADC_1' in the register.
-            # Therefore, we need to traverse the factors array in reverse order
-            for value in _values: #reversed(_values): # not for Kapture 2
-                if value is not None:
-                    reg_value += '{0:02x}'.format(value)
-                    mask += 'ff'
-                else:
-                    reg_value += '00'
-                    mask += '00'
-
-            pci.write(self.identifier, reg_value, '0x9080', hex_mask=mask)
-            
-            s = "Setting ADC Delays:"
-            for (adc, value) in enumerate(_values):
-                s += ' ADC_%i Fine Delay: %i,' % (adc+4, value)
-            s = s[:-1]  # cut away the last dangling ','
-            logging.vinfo(s)
-
-
-
-    def _set_bunch_shift(self, values):
-        #print("Setting bunch shifts: ", values)
-
-        #Banks are flipped!
-        #0x9320:  5,6,7,8
-        #0x9330:  1,2,3,4
-
-        bank1 = values[4:8]
-        bank2 = values[0:4]
-        adcs = bank1 + bank2
-
-        base_reg = 0x9320
-
-        for i, val in enumerate(adcs):
-            reg = hex(base_reg+(i*4))
-            set = "0x{0:08x}".format(val)
-
-            logging.vinfo("Setting %s to %s"%(reg,set))
-            pci.write(self.identifier, set, reg)
-
-
-
-    def _update_header(self, state):
-        """
-        Set the flag to write Header to files when acquiring.
-        :param state: True to enabling header and False to disable
-        :return: -
-        """
-        try:
-            control = pci.read(self.identifier, 1, '0x9040')[0]
-            control_bits = '{0:032b}'.format(int(control, 16))
-            if state:
-                control_bits = control_bits[:3] + '1' + control_bits[4:]
-            else:
-                control_bits = control_bits[:3] + '0' + control_bits[4:]
-            dec_val_bits = int(control_bits, 2)
-            pci.write(self.identifier, hex(dec_val_bits), '0x9040')
-        except BoardError as e:
-            reason = str(e) if str(e) != '' else "Unknown"
-            logging.error("Error in Board Communication, was unable to write value to board "+reason)
-
-    def _update_pilot_bunch(self, state):
-        """
-        Set the flag to write Header to files when acquiring.
-        :param state: True to enabling header and False to disable
-        :return: -
-        """
-        try:
-            control = pci.read(self.identifier, 1, '0x9040')[0]
-            control_bits = '{0:032b}'.format(int(control, 16))
-            if state:
-                control_bits = control_bits[:1] + '1' + control_bits[2:]
-            else:
-                control_bits = control_bits[:1] + '0' + control_bits[2:]
-            dec_val_bits = int(control_bits, 2)
-            pci.write(self.identifier, hex(dec_val_bits), '0x9040')
-        except BoardError as e:
-            reason = str(e) if str(e) != '' else "Unknown"
-            logging.error("Error in Board Communication, was unable to write value to board "+reason)
-
-    def _set_adc_gain(self, x):
-        self._select_adc(1)
-        pci.write(self.identifier, '46{:04x}'.format(int(x[0])), '0x9064')
-        pci.write(self.identifier, '56{:04x}'.format(int(x[1])), '0x9064')
-
-        self._select_adc(3)
-        pci.write(self.identifier, '46{:04x}'.format(int(x[2])), '0x9064')
-        pci.write(self.identifier, '56{:04x}'.format(int(x[3])), '0x9064')
-
-        if len(x) > 4:
-            logging.vinfo("Gain update not defined for more than 4 adc")
-
-    def _set_adc_offset(self, x):
-        #ADC1
-        self._select_adc(1)
-        pci.write(self.identifier, '44{:04x}'.format(int(x[0])), '0x9064')
-        pci.write(self.identifier, '54{:04x}'.format(int(x[1])), '0x9064')
-
-        self._select_adc(3)
-        pci.write(self.identifier, '44{:04x}'.format(int(x[2])), '0x9064')
-        pci.write(self.identifier, '54{:04x}'.format(int(x[3])), '0x9064')
-
-        if len(x) > 4:
-            logging.vinfo("Offset update not defined for more than 4 adc")
-
-    def _select_adc(self, nr):
-        val = 0
-        if nr == 0:
-            val = 0
-        if nr < 3:
-            val = 4
-        elif nr < 5:
-            val = 2
-        pci.write(self.identifier, hex(val), '0x9044')
-
-    def _set_samplingrate(self, rate):
-        return
-        if rate == 1: 
-            #500 MHz
-            self.update('delay_25_adc', 10)
-            self.update('delay_330_adc', 1)
-            self.update('delay_25_adc_2', 10)
-            self.update('delay_330_adc_2', 1)
-        elif rate == 2:
-            #1 GHz
-            self.update('delay_25_adc', 10)
-            self.update('delay_330_adc', 0)
-            self.update('delay_25_adc_2', 10)
-            self.update('delay_330_adc_2', 0)
-
-    def set_startup(self):
-        #print('board_config setting startup')
-        #self.update('delay_330_th', 0)
-        #sleep(0.1)
-        #self.update('delay_25_th', 0)
-        #sleep(0.1)
-        self.update('delay_330_th_2', 0)
-        sleep(0.1)
-        self.update('delay_25_th_2', 3)
-        sleep(0.1)
-        self.update('delay_25_th_2', 4)
-        sleep(0.1)
-        self.update('chip_delay', [0,0,0,0, 0,0,0,0])
-        sleep(0.1)
-        #Bunch Shifts are offset by +2 to encode -2 to +2 as 0x0 to 0x4 in hardware
-        self.update('bunch_shift', [2,2,2,2, 2,2,2,2])
-        sleep(0.1)
-        self.update('adc_gain', [0,0,0,0, 0,0,0,0])
-        sleep(0.1)
-        self.update('header', 1)
-        sleep(0.1)
-        self.update('pilot_bunch', 1)
-        logging.vinfo('Startup Config Set')
-    def read_from_board(self):
-        """
-        Read values from board and update them in the configuration (Mainly used for skip init functionality)
-        """
-        try:
-            #settings = ['chip_1_delay','chip_2_delay','chip_3_delay','chip_4_delay']
-            # --[ read fine/chip delays ]
-            adc_number = self._config["adc_number"]
-
-            val = pci.read(self.identifier, reg='9080')[0]
-            
-            if adc_number > 4:
-                val = val + pci.read(self.identifier, reg='9084')[0]
-            tmp = np.zeros(adc_number)
-            
-            for i in range(adc_number):
-                selector = [3,2,1,0,7,6,5,4]
-                tmp[selector[i]] = int(val[(adc_number-1-i)*2:(adc_number-i)*2], 16)
-            self.update('chip_delay', tmp , write=False)    
-
-            # --[ read bunch shifts ] --
-            #Banks are flipped!
-            #0x9320:  5,6,7,8
-            #0x9330:  1,2,3,4
-            bunch_shifts = pci.read(self.identifier, reg='9330', amount=4, decimal=True)
-
-            if adc_number > 4:
-                bunch_shifts = bunch_shifts + pci.read(self.identifier, reg='9320', amount=4, decimal=True)
-            self.update('bunch_shift', bunch_shifts, write=False)    
-
-            # --[ read and set th delay ]--
-            val = pci.read(self.identifier, reg='90B0')[0]
-            self.update('delay_330_th', self._config['delay_330_max'] - ((int(val, 16)>>1)-5), write=False)
-            val = pci.read(self.identifier, reg='90B4')[0]
-            self.update('delay_25_th',  self._config['delay_25_max'] - int(val, 16), write=False)
-            val = pci.read(self.identifier, reg='90B8')[0]
-            self.update('clk_div_th', int(val, 16), write=False)
-
-            # --[ read and set adc delay ]--
-            val = pci.read(self.identifier, reg='9090')[0]
-            self.update('delay_330_adc', (int(val, 16)>>1)-5, write=False)
-            val = pci.read(self.identifier, reg='9094')[0]
-            self.update('delay_25_adc', int(val, 16), write=False)
-            val = pci.read(self.identifier, reg='9098')[0]
-            self.update('clk_div_adc', int(val, 16), write=False)
-
-            # --[ read and set fpga delay ]--
-            val = pci.read(self.identifier, reg='90C0')[0]
-            self.update('delay_330_fpga', (int(val, 16)>>1)-5, write=False)
-            #val = pci.read(self.identifier, reg='90C4')[0]
-            #self.update('delay_25_fpga', int(val, 16), write=False)
-            val = pci.read(self.identifier, reg='90C8')[0]
-            self.update('clk_div_fpga', int(val, 16), write=False)
-            
-
-
-            # --[ read and set number of turns to acquire ]--
-            val = pci.read(self.identifier, reg='9020')[0]
-            self.update('turns_observe', int(val, 16), write=False)
-
-            # --[ read and set number of turns to skip ]--
-            val = pci.read(self.identifier, reg='9028')[0]
-            self.update('turns_skip', int(val, 16), write=False)
-
-            
-
-            # --[ read FMC 2 ] --
-            # --[ FMC2 read and set th delay ]--
-            val = pci.read(self.identifier, reg='90A8')[0]
-            self.update('delay_330_th_2', ((int(val, 16)>>1)-5), write=False)
-            val = pci.read(self.identifier, reg='90AC')[0]
-            self.update('delay_25_th_2', int(val, 16), write=False)
-            
-
-            # --[ FCM 2 read and set adc delay ]--
-            val = pci.read(self.identifier, reg='90A0')[0]
-            self.update('delay_330_adc_2', ((int(val, 16)>>1)-5), write=False)
-            val = pci.read(self.identifier, reg='90A4')[0]
-            self.update('delay_25_adc_2', int(val, 16), write=False)
-        
-         
-            
-
-
-            # --[ read and update header ]--
-            control = pci.read(self.identifier, 1, '0x9040')[0]
-            control_bits = '{0:032b}'.format(int(control, 16))
-            if control_bits[3] == '1':
-                self.update('header', True, write=False)
-            else:
-                self.update('header', False, write=False)
-        except IndexError:
-            error(0x002, "Could not Read data from Board. Pci returned wrong amount of data.")
-
-
-
-    # 888    d8P         d8888 8888888b. 88888888888 888     888 8888888b.  8888888888      d888
-    # 888   d8P         d88888 888   Y88b    888     888     888 888   Y88b 888            d8888
-    # 888  d8P         d88P888 888    888    888     888     888 888    888 888              888
-    # 888d88K         d88P 888 888   d88P    888     888     888 888   d88P 8888888          888
-    # 8888888b       d88P  888 8888888P"     888     888     888 8888888P"  888              888
-    # 888  Y88b     d88P   888 888           888     888     888 888 T88b   888       888888 888
-    # 888   Y88b   d8888888888 888           888     Y88b. .d88P 888  T88b  888              888
-    # 888    Y88b d88P     888 888           888      "Y88888P"  888   T88b 8888888888     8888888
-    #
-    #
-    #
-    def _set_fpga_delay(self, value):
-        """
-        observer function to set the fpga_delays on the board
-        :param value: the value to set the delays to
-        """
-        time_factor = self.make_uint(value, self.get('delay_330_max'), 'FPGA_Delay')
-        reg_value = "0x000501" + '{0:01x}'.format(time_factor) + "0"
-        pci.write(self.identifier, reg_value, '0x9060')
-        logging.vinfo("Set FPGA clock delay %i * %i --> %i picoseconds" % (time_factor, self.get('delay_330_factor'), time_factor*self.get('delay_330_factor')))
-        #self.update('fpga_delay', value)
-
-    
-    def _set_th_delay(self, value):
-        """
-        Set the track and hold delay on the board
-        :param value: the value to set the delay to
-        """
-        time_factor = self.make_uint(value, self.get('delay_330_max'), 'TH_Delay')
-        reg_value = "0x000501" + '{0:01x}'.format(time_factor) + "3"
-        pci.write(self.identifier, reg_value, '0x9060')
-        logging.vinfo("Set T/H Signal delay %i * %i --> %i picoseconds" % (time_factor, self.get('delay_330_factor'), time_factor*self.get('delay_330_factor')))
-       
-        self.update('delay_330_adc', time_factor)
-
-    def _set_adc_delay(self, value):
-        """
-        Set the adc delay for the given adc on the board
-        :param value: the value to set the delay to
-        """
-        def write_delay(value, channel):
-            '''write the delays to the board'''
-            value = self.make_uint(value, self.get('delay_330_max'), 'ADC Delay')
-            cmd = '00501' + '%01x' % value + str(channel+4)
-            pci.write(self.identifier, cmd, reg='0x9060')
-            time.sleep(0.005)
-
-        delay = value + self.get('th_to_adc_cycles')
-
-        if delay > self.get('delay_330_max'):
-            delay -= self.get('delay_330_max') + 1
-
-        if self._config['adc_number'] == 4:
-            for adc in range(self._config['adc_number']):
-                write_delay(delay, adc)
-                s = "Setting ADC_%i delay %i * %i --> %i picoseconds" % ((adc+1), delay, self.get('delay_330_factor'), delay*self.get('delay_330_factor'))
-                logging.vinfo(s)
-
-        else:
-            logging.vinfo("adc_delay update not defined for more than 4 adc")
-        
+"""
+Configuration for each board
+"""
+
+import os
+import sys
+import traceback
+if sys.version_info[:3] < (3,0):
+    import ConfigParser as configparser
+else:
+    import configparser
+import numpy as np
+import logging
+from PyQt4 import QtGui, QtCore
+from time import sleep
+
+from .communication import *
+from .... import config as kcg_config
+
+
+
+class BoardConfiguration(QtGui.QWidget):
+    """
+    The Main configuration class for boards.
+    """
+    callback_signal = QtCore.pyqtSignal(str, list)
+
+    def __init__(self, identifier, config_file=None):
+        from . import sequences
+        super(BoardConfiguration, self).__init__()
+        self.callback_signal.connect(self._notify_observers_receiver)
+        self.identifier = identifier
+        self._config = {}
+        self._observers = {}
+        self._observers_for_all = []
+        self._observers_write = {}
+        self._set_defaults()
+        self._get_board_version()
+        if self._config['board_version'] > 10:
+            logging.critical('Unknown Board Version - gui not working! - restart with active board')
+            print('Unknown Board Version - gui not working! - restart with active board')
+        else:
+            logging.info('Detected Board Version: {}'.format(self._config["board_version"]))
+            print('Detected Board Version: {}'.format(self._config["board_version"]))
+
+        self._sequences = sequences.read_sequence(self._config["board_version"])
+        self.set_default_observers()
+
+        if self.is_KAPTURE2():
+            self.update('bunches_per_turn', kcg_config.bunches_per_turn)
+        else:
+            self._config['delay_330_factor'] = 150
+            self._config['delay_330_max'] = 15
+            self._config['chip_delay'] = [3,3,3,3]
+
+        if config_file:
+            self.load_config(config_file)
+
+        #self.notify_all_observers(True)
+
+    def _set_defaults(self):
+        """
+        Set default values
+        """
+        self._config = {
+            'board_version' : 7,
+            'lastDataSet': None,
+
+            'adc_number': 8,
+            'samplingrate': 1,
+
+            'bunches_per_turn': 184,
+            'header_byte_size': 32,
+
+            'chip_delay_max': 31,
+            'chip_delay' : [0,0,0,0,0,0,0,0],
+            'bunch_shift' : [2,2,2,2,2,2,2,2], #Offset by +2 to encode -2 to +2 range
+            'chip_delay_factor': 3,
+
+
+            'delay_330_max': 20, #technical 522 #be aware: changing this makes the Delay Calibration invalid!
+            'delay_330_factor': 330,
+            'delay_330_th':   0,
+            'delay_330_adc':  1,
+            'delay_330_fpga': 1,
+
+            #-Kapture 2 only--------------------
+            'delay_25_max' : 23, #be aware: changing this makes the Delay Calibration invalid!
+            'delay_25_factor': 25,
+            'delay_25_th':   0,
+            'delay_25_adc':  10,
+            'delay_25_fpga': 0,
+
+            #-Second FMC------------------
+            'delay_330_th_2':   0,
+            'delay_330_adc_2':   1,
+            #'delay_330_fpga_2':   0,
+            'delay_25_th_2':   4,
+            'delay_25_adc_2':  10,
+            #'delay_25_fpga_2': 0,
+            'default_25_th_2': 4,
+
+            'delay_cascade_max': 20,
+            'delay_cascade_factor': 330,
+            'delay_cascade': 0,
+            'delay_cascade_25': 14,
+
+            #-Clock Division--------------
+            'clk_div': 6,
+            'clk_div_th':   6,
+            'clk_div_adc':  6,
+            'clk_div_fpga': 6,
+            'clk_div_cascade': 24,
+
+            #-Kapture 1 only-------------------
+            'th_to_adc_cycles': 7,
+            #'adc_1_delay_individual': -1,
+            #----------------------------------
+
+            'turns_observe': 1000,
+            'turns_skip': 0,
+            'acquisition_count': 10,
+            'turns_wait_time': 15,
+
+            'trigger_skip': 0,
+            'trigger_timeout': 12,
+            'trigger_method': 1,
+            'use_trigger': False,
+
+            'build_spectrograms': False,
+            'pilot_bunch': True,
+            'header': True if kcg_config.save_header is True else False,
+
+            #Kapture 2
+            'adc_offset': [0x0000, 0x0000, 0x0000, 0x0000,  0x0000, 0x0000, 0x0000, 0x0000],
+            'adc_gain':   [0x0000, 0x0000, 0x0000, 0x0000,  0x0000, 0x0000, 0x0000, 0x0000]
+        }
+
+    def _get_board_version(self):
+        try:
+            self._config["board_version"] = int(pci.read(self.identifier, reg='0x9030')[0])
+            if self._config["board_version"] == 6:
+                self._config["board_version"] = 5
+            elif self._config["board_version"] == 7:
+                self._config["adc_number"] = 8
+
+        except Exception as e:
+            self._config["board_version"] = 0xDEAD
+
+
+    def is_KAPTURE2(self):
+        return self._config["board_version"] >4
+
+        ##########################################################################
+        #  .d8888b.
+        # d88P  Y88b
+        # Y88b.
+        #  "Y888b.   .d88b.  .d88888888  888 .d88b. 88888b.  .d8888b .d88b.
+        #     "Y88b.d8P  Y8bd88" 888888  888d8P  Y8b888 "88bd88P"   d8P  Y8b
+        #       "88888888888888  888888  88888888888888  888888     88888888
+        # Y88b  d88PY8b.    Y88b 888Y88b 888Y8b.    888  888Y88b.   Y8b.
+        #  "Y8888P"  "Y8888  "Y88888 "Y88888 "Y8888 888  888 "Y8888P "Y8888
+        #                        888
+        #                        888
+        #                        888
+        #
+
+    def get_sequence_list(self):
+        if self._sequences =={}:
+            return "No Sequences"
+        return self._sequences["sequence_names"]
+
+    def get_init_order(self):
+        if self._sequences =={}:
+            return "No Sequences"
+        return self._sequences["initialization_sequence_order"]
+
+    def get_sequence_comment(self, sequence_name):
+        if self._sequences =={}:
+            return "No Sequences"
+        return self._sequences[sequence_name]["Comment"]
+
+    def get_sequence_status(self, sequence_name):
+        if self._sequences =={}:
+            return "No Sequences"
+        return self._sequences[sequence_name]["status_val"]
+
+    def run_sequence(self, name, progressbar=None):
+        if self._sequences == {}:
+            return False
+        from . import sequences
+        return sequences.run_sequence(self.identifier, self._sequences[name]["sequence"], progressbar)
+
+
+        ##########################################################################
+        ##########################################################################
+    def load_config(self, filename):
+        """
+        Load a config from a file
+        :param filename: the configuration file
+        :return:
+        """
+        if filename:
+            config = configparser.RawConfigParser()
+            if not config.read(str(filename)):
+                return False
+
+            for key in list(self._config.keys()):
+                try:
+                    if type(self._config[key]) == int:
+                        self._config[key] = config.getint('Config', key)
+                    elif type(self._config[key]) == bool:
+                        self._config[key] = config.getboolean('Config', key)
+                    logging.vinfo("Read '%s' for '%s' from '%s'"%(str(self._config[key]), key, str(filename)))
+                except configparser.NoOptionError as e:
+                    pass
+                except configparser.NoSectionError as e:
+                    pass
+            return True
+        else:
+            return False
+
+    def save_config(self, filename):
+        """
+        Save the current configuration to a file
+        :param filename: the file to write to
+        """
+        if filename:
+            # with open(filename, 'w') as f:
+            try:
+                f = open(filename, 'w')
+                cp = configparser.RawConfigParser()
+                cp.add_section('Config')
+                for key in list(self._config.keys()):
+                    cp.set('Config', key, self._config[key])
+                f.write('#\n'
+                        '#  KCG   (KAPTURE Control Gui) Configuration file\n'
+                        '#\n'
+                        '#  (c) Karlsruhe Institute of Technology, 2015\n'
+                        '#  All rights reserved.\n'
+                        '#\n'
+                        '#  Applicable Gui Version(s): 1.0 - 1.0.2\n'
+                        '#\n'
+                        '#  Saved at: ' + time.asctime() + '\n'
+                        '#\n\n')
+                cp.write(f)
+            except (IOError, TypeError):
+                return False
+            return True
+        else:
+            return False
+
+    def get(self, key):
+        """
+        Get the configuration value for key
+        :param key: the key to get the value for
+        :return: the value of the configuration for key
+        """
+        if not key in self._config:
+            raise NoSuchKeyError(key+" is not registered in BoardConfiguration for board "+str(self.identifier))
+        return self._config.get(key, None)
+
+    def dump(self):
+        """
+        Dump all configuration values
+        :return: all configuration values as list
+        """
+        s = ""
+        for key in list(self._config.keys()):
+            s += key + ": " + str(self.get(key)) + ", "
+        return s[:-1]
+
+
+        ##########################################################################
+        #  .d88888b. 888
+        # d88P" "Y88b888
+        # 888     888888
+        # 888     88888888b. .d8888b  .d88b. 888d888888  888 .d88b. 888d888.d8888b
+        # 888     888888 "88b88K     d8P  Y8b888P"  888  888d8P  Y8b888P"  88K
+        # 888     888888  888"Y8888b.88888888888    Y88  88P88888888888    "Y8888b.
+        # Y88b. .d88P888 d88P     X88Y8b.    888     Y8bd8P Y8b.    888         X88
+        # "Y88888P" 88888P"  88888P' "Y8888 888      Y88P   "Y8888 888     88888P'
+
+
+    def set_default_observers(self):
+        """
+        Set observers that are always used
+        """
+        #def obsprint(x,y):
+        #    print(x, y)
+        #self.observe_all(obsprint)
+        self.observe_write(self._update_header, 'header')
+        self.observe_write(self._update_pilot_bunch, 'pilot_bunch')
+
+        self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x9020'), 'turns_observe')
+        self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x9028'), 'turns_skip')
+        if self._config['board_version'] > 4: # everything for KAPTURE 2
+            self.observe_write(lambda x: pci.write(self.identifier, hex(x-2), '0x90E0'), 'bunches_per_turn')
+            self.observe_write(self._set_adc_gain,   'adc_gain')
+            self.observe_write(self._set_adc_offset, 'adc_offset')
+
+            #Attention: the th delays are inverted! Means: due to the internal setup by increasing the value the sampling
+                #would be earlier. This is not intuitive therefore the gui inverts the value by using the delay_max as offset.
+            #Setup Long delay 330ps. Value needs to be shifted to the left because first bit is used to set halfstep
+            self.observe_write(lambda x: pci.write(self.identifier, hex((x+5)<<1), '0x9090'), 'delay_330_adc')
+            self.observe_write(lambda x: pci.write(self.identifier, hex((self._config['delay_330_max']-x+5)<<1), '0x90B0'), 'delay_330_th')
+            self.observe_write(lambda x: pci.write(self.identifier, hex((x+5)<<1), '0x90C0'), 'delay_330_fpga')
+
+            self.observe_write(lambda x: pci.write(self.identifier, hex((x+5)<<1), '0x90D0'), 'delay_cascade')
+
+            #Setup short Delay 25ps.
+            self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x9094'), 'delay_25_adc')
+            self.observe_write(lambda x: pci.write(self.identifier, hex(self._config['delay_25_max']-x), '0x90B4'), 'delay_25_th')
+            #self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x90C4'), 'delay_25_fpga')
+
+            #Setup Clock Div
+            self.observe_write(self._set_clk_div_all , 'clk_div') #one to set it all - normaly they should be all the same
+            self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x9098'), 'clk_div_adc')
+            self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x90B8'), 'clk_div_th')
+            self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x90C8'), 'clk_div_fpga')
+            self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x90D8'), 'clk_div_cascade')
+
+            self.observe_write(self._set_chip_delay,   'chip_delay')
+            self.observe_write(self._set_samplingrate, 'samplingrate')
+            self.observe_write(self._set_bunch_shift, 'bunch_shift')
+
+            if self._config['adc_number'] > 4:
+                #Setup Long delay 330ps. Value needs to be shifted to the left because first bit is used vor halfstep
+                self.observe_write(lambda x: pci.write(self.identifier, hex((x+5)<<1), '0x90A0'), 'delay_330_adc_2')
+                self.observe_write(lambda x: pci.write(self.identifier, hex((self._config['delay_330_max']-x+5)<<1), '0x90A8'), 'delay_330_th_2')
+
+                #Setup short Delay 25ps
+                self.observe_write(lambda x: pci.write(self.identifier, hex(x), '0x90A4'), 'delay_25_adc_2')
+                self.observe_write(lambda x: pci.write(self.identifier, hex(self._config['delay_25_max']-x), '0x90AC'), 'delay_25_th_2')
+
+
+        else:
+            self.observe_write(self._set_fpga_delay, 'delay_330_fpga')
+            self.observe_write(self._set_chip_delay, 'chip_delay')
+            self.observe_write(self._set_th_delay,   'delay_330_th')
+            self.observe_write(self._set_adc_delay,  'delay_330_adc')
+
+    def notify_all_observers(self, write=False):
+        """
+        Notify all observers not only the ones that are affected by a change
+        """
+        for key, value in list(self._config.items()):
+            self._notify_observers(key, value, write)
+
+            # observers = self._observers.get(key, None)
+            # if observers:
+            #     for (who, callback) in observers:
+            #         callback(self.get(key))
+
+
+    def update(self, key, value, entry=None, write=True):
+        """
+        Update the value for key in the configuration
+        :param key: the key to update
+        :param value: the value to set the configuration for key to
+        :param entry: if key is array to select the array index.
+        """
+        if entry is not None:
+            self._config[key][entry] = value
+        else:
+            self._config[key] = value
+        self._notify_observers(key, self._config[key], write)
+
+    def updateSilent(self, key, value, entry=None):
+        """
+        Update the configuration without notifying observers
+        :param key: the key to updae
+        :param value: the value to set the configuration of key to
+        :param entry: if key is array to select the array index.
+        """
+        if entry is not None:
+            self._config[key][entry] = value
+        else:
+            self._config[key] = value
+
+    def observe(self, who, callback, key):
+        """
+        Register an observer. (A callback that is called when the according configuration value changes)
+        :param who: who is observing
+        :param callback: the callback to call on change of the configuration value
+        :param key: the key to observe
+        """
+        if key not in list(self._config.keys()):
+            raise ObserverError(str("Key '%s' is unknown." % key))
+
+        if self._observers.get(key, None) is None:
+            self._observers[key] = []
+
+        self._observers[key].append([who, callback])
+
+    def observe_all(self, callback):
+        """
+        Register a observer that is called when any key changes
+        :param callback: the callback to register
+        """
+        if callback not in self._observers_for_all:
+            self._observers_for_all.append(callback)
+        else:
+            raise ObserverError("Observer already registered")
+
+    def observe_write(self, callback, key):
+        """
+        Register an observer that writes the value for Key on the Board
+        :param callback: the callback to call on change of the configuration value
+        :param key: the key to observe
+        """
+        if key not in list(self._config.keys()):
+            raise ObserverError(str("Key '%s' is unknown." % key))
+
+        if self._observers_write.get(key, None) is None:
+            self._observers_write[key] = []
+
+        self._observers_write[key].append(callback)
+
+    def unobserve(self, who, key=None):
+        """
+        Remove an observer
+        :param who: the observing entity
+        :param key: the key to remove it from
+        """
+        if key is not None:
+            observers = np.array(self._observers.get(key, None))
+            if observers is None:
+                return
+            if who not in observers[:, 0]:
+                return
+            for i, _obs in enumerate(self._observers[key]):
+                if _obs[0] is who:
+                    del self._observers[key][i]
+                    if not self._observers[key]:
+                        del self._observers[key]
+            return
+
+        for _key in list(self._observers.keys()):
+            for i, _obs in enumerate(self._observers[_key]):
+                if _obs[0] is who:
+                    del self._observers[_key][i]
+                    if not self._observers[_key]:
+                        del self._observers[_key]
+
+    def unobserve_all_observer(self, callback):
+        """
+        Unobserve an observer that observes all keys.
+        :param callback: the callback to unobserve
+        """
+        if callback in self._observers_for_all:
+            del self._observers_for_all[self._observers_for_all.index(callback)]
+
+    def _notify_observers_receiver(self, key, args):
+        """
+        The pyqt signal slot for notifications of observers
+        :param key: the key that changed
+        :param value: the new value
+        """
+        observers = self._observers.get(str(key), None)
+
+        value = args[0]
+        write = args[1]
+        if observers is not None:
+            for (who, callback) in observers:
+                try:
+                    callback(value)
+                except Exception as e:
+                    log.error('Observer Callback error: {}'.format(e))
+                    print(traceback.format_exc())
+
+        for cb in self._observers_for_all:
+            try:
+                cb(key, value)
+            except:
+                pass
+
+        if write:
+            observers = self._observers_write.get(str(key), None)
+            if observers is not None:
+                for callback in observers:
+                    try:
+                        #log.debug('write ' + key , args)
+                        callback(value)
+                    except Exception as e:
+                        log.error('Observer Callback error: {}'.format(e))
+                        print(traceback.format_exc())
+                    time.sleep(0.025)
+
+    def _notify_observers(self, key, value, write=True):
+        """
+        Notify observers. This emits a pyqt signal to make it thread save
+        :param key: the key that changed
+        :param value: the new value
+        """
+        #print("_notify_observers", key, value, write)
+        self.callback_signal.emit(key, [value, write])
+
+    def make_uint(self, value, maximum, name=None):
+        """
+        Convert a value to an uint
+        :param value: the value
+        :param maximum: the maximum of the returned value
+        :param name: the name of this value
+        :return: the converted value
+        """
+        if value is None:
+            raise ValueError(str("%s Value is invalid (None)" % name))
+
+        val = None
+        try:
+            val = int(value)
+        except ValueError:
+            raise ValueError(str("%s Value is not a valid number" % name))
+
+        if maximum is not None:
+            if val > maximum:
+                raise ValueError(str("%s Value is too large (>%i)" % (name, maximum)))
+
+        if val < 0:
+            raise ValueError(str("%s Values below 0 are not allowed" % name))
+
+        return val
+
+    def _set_clk_div_all(self, value):
+        self.update('clk_div_adc', value)
+        self.update('clk_div_th', value)
+        self.update('clk_div_fpga', value)
+        self.update('clk_div_cascade', value)
+
+
+    def _set_chip_delay(self, values):
+        """
+        Set the chip_delays on the board
+        :param values: the value to set the delays to
+        """
+
+        _values = []
+        for value in values[:4]:
+            _values.append(self.make_uint(value, self.get('chip_delay_max'), 'ADC_Value'))
+        #print("ADC1: ", values, _values)
+        reg_value = ''
+        mask = ''
+        # Chip Delays are stored as 'ADC_4 ADC_3 ADC_2 ADC_1' in the register.
+        # Therefore, we need to traverse the factors array in reverse order
+        for value in _values: #reversed(_values): # not for Kapture 2
+            if value is not None:
+                reg_value += '{0:02x}'.format(value)
+                mask += 'ff'
+            else:
+                reg_value += '00'
+                mask += '00'
+
+        if self._config['adc_number'] > 4:
+            pci.write(self.identifier, reg_value, '0x9084', hex_mask=mask)
+        else:
+            pci.write(self.identifier, reg_value, '0x9080', hex_mask=mask)
+
+        s = "Setting ADC Delays:"
+        for (adc, value) in enumerate(_values):
+            s += ' ADC_%i Fine Delay: %i,' % (adc, value)
+        s = s[:-1]  # cut away the last dangling ','
+        logging.vinfo(s)
+
+        if self._config['adc_number'] > 4:
+            _values = []
+            for value in values[4:]:
+                _values.append(self.make_uint(value, self.get('chip_delay_max'), 'ADC_Value'))
+            #print("ADC 2", values, _values)
+            reg_value = ''
+            mask = ''
+            # Chip Delays are stored as 'ADC_4 ADC_3 ADC_2 ADC_1' in the register.
+            # Therefore, we need to traverse the factors array in reverse order
+            for value in _values: #reversed(_values): # not for Kapture 2
+                if value is not None:
+                    reg_value += '{0:02x}'.format(value)
+                    mask += 'ff'
+                else:
+                    reg_value += '00'
+                    mask += '00'
+
+            pci.write(self.identifier, reg_value, '0x9080', hex_mask=mask)
+
+            s = "Setting ADC Delays:"
+            for (adc, value) in enumerate(_values):
+                s += ' ADC_%i Fine Delay: %i,' % (adc+4, value)
+            s = s[:-1]  # cut away the last dangling ','
+            logging.vinfo(s)
+
+
+
+    def _set_bunch_shift(self, values):
+        #print("Setting bunch shifts: ", values)
+
+        #Banks are flipped!
+        #0x9320:  5,6,7,8
+        #0x9330:  1,2,3,4
+
+        bank1 = values[4:8]
+        bank2 = values[0:4]
+        adcs = bank1 + bank2
+
+        base_reg = 0x9320
+
+        for i, val in enumerate(adcs):
+            reg = hex(base_reg+(i*4))
+            set = "0x{0:08x}".format(val)
+
+            logging.vinfo("Setting %s to %s"%(reg,set))
+            pci.write(self.identifier, set, reg)
+
+
+
+    def _update_header(self, state):
+        """
+        Set the flag to write Header to files when acquiring.
+        :param state: True to enabling header and False to disable
+        :return: -
+        """
+        try:
+            control = pci.read(self.identifier, 1, '0x9040')[0]
+            control_bits = '{0:032b}'.format(int(control, 16))
+            if state:
+                control_bits = control_bits[:3] + '1' + control_bits[4:]
+            else:
+                control_bits = control_bits[:3] + '0' + control_bits[4:]
+            dec_val_bits = int(control_bits, 2)
+            pci.write(self.identifier, hex(dec_val_bits), '0x9040')
+        except BoardError as e:
+            reason = str(e) if str(e) != '' else "Unknown"
+            logging.error("Error in Board Communication, was unable to write value to board "+reason)
+
+    def _update_pilot_bunch(self, state):
+        """
+        Set the flag to write Header to files when acquiring.
+        :param state: True to enabling header and False to disable
+        :return: -
+        """
+        try:
+            control = pci.read(self.identifier, 1, '0x9040')[0]
+            control_bits = '{0:032b}'.format(int(control, 16))
+            if state:
+                control_bits = control_bits[:1] + '1' + control_bits[2:]
+            else:
+                control_bits = control_bits[:1] + '0' + control_bits[2:]
+            dec_val_bits = int(control_bits, 2)
+            pci.write(self.identifier, hex(dec_val_bits), '0x9040')
+        except BoardError as e:
+            reason = str(e) if str(e) != '' else "Unknown"
+            logging.error("Error in Board Communication, was unable to write value to board "+reason)
+
+    def _set_adc_gain(self, x):
+
+        #Bits 1 and 2 of register 9044 control which SPI channels to select.
+        #SPI_1 will route to  ADC-Element 1 (Channel 1 and 2), and SPI_2
+        #will route to ADC-Element 2 (Channel 3 and 4).
+
+        #Bits 16 and 17 of register 9044 control which FMCs to route the
+        #SPI commands to. If both bits are 1, then the SPI commands get routed
+        #to both FMCs at the same time.
+
+        #pci.read response is returned as a list. Since we expect only one
+        #value, we access index [0]
+        initial_mux_config = pci.read(self.identifier, reg='9044')[0]
+
+
+        #Select SPI1 and FMC1
+        updated_mux_config = initial_mux_config | 0x10002
+        pci.write(self.identifier, hex(updated_mux_config), '0x9044')
+
+        pci.write(self.identifier, '46{:04x}'.format(int(x[0])), '0x9064')
+        pci.write(self.identifier, '56{:04x}'.format(int(x[1])), '0x9064')
+
+
+        #Select SPI2 and FMC1
+        updated_mux_config = initial_mux_config | 0x10004
+        pci.write(self.identifier, hex(updated_mux_config), '0x9044')
+
+        pci.write(self.identifier, '46{:04x}'.format(int(x[2])), '0x9064')
+        pci.write(self.identifier, '56{:04x}'.format(int(x[3])), '0x9064')
+
+
+        if len(x) > 4 and len(x) < 9:
+
+            #Select SPI1 and FMC2
+            updated_mux_config = initial_mux_config | 0x20002
+            pci.write(self.identifier, hex(updated_mux_config), '0x9044')
+
+            pci.write(self.identifier, '46{:04x}'.format(int(x[4])), '0x9064')
+            pci.write(self.identifier, '56{:04x}'.format(int(x[5])), '0x9064')
+
+
+            #Select SPI2 and FMC2
+            updated_mux_config = initial_mux_config | 0x20004
+            pci.write(self.identifier, hex(updated_mux_config), '0x9044')
+
+            pci.write(self.identifier, '46{:04x}'.format(int(x[6])), '0x9064')
+            pci.write(self.identifier, '56{:04x}'.format(int(x[7])), '0x9064')
+
+        else:
+            logging.vinfo("Gain update not defined for more than 8 adc")
+
+
+        #Restore the initial MUX Configuration
+        pci.write(self.identifier, hex(initial_mux_config), '0x9044')
+
+
+    def _set_adc_offset(self, x):
+
+        #Bits 1 and 2 of register 9044 control which SPI channels to select.
+        #SPI_1 will route to  ADC-Element 1 (Channel 1 and 2), and SPI_2
+        #will route to ADC-Element 2 (Channel 3 and 4).
+
+        #Bits 16 and 17 of register 9044 control which FMCs to route the
+        #SPI commands to. If both bits are 1, then the SPI commands get routed
+        #to both FMCs at the same time.
+
+        #pci.read response is returned as a list. Since we expect only one
+        #value, we access index [0]
+        initial_mux_config = pci.read(self.identifier, reg='9044')[0]
+
+
+        #Select SPI1 and FMC1
+        updated_mux_config = initial_mux_config | 0x10002
+        pci.write(self.identifier, hex(updated_mux_config), '0x9044')
+
+        pci.write(self.identifier, '44{:04x}'.format(int(x[0])), '0x9064')
+        pci.write(self.identifier, '54{:04x}'.format(int(x[1])), '0x9064')
+
+
+        #Select SPI2 and FMC1
+        updated_mux_config = initial_mux_config | 0x10004
+        pci.write(self.identifier, hex(updated_mux_config), '0x9044')
+
+        pci.write(self.identifier, '44{:04x}'.format(int(x[2])), '0x9064')
+        pci.write(self.identifier, '54{:04x}'.format(int(x[3])), '0x9064')
+
+
+        if len(x) > 4 and len(x) < 9:
+
+            #Select SPI1 and FMC2
+            updated_mux_config = initial_mux_config | 0x20002
+            pci.write(self.identifier, hex(updated_mux_config), '0x9044')
+
+            pci.write(self.identifier, '44{:04x}'.format(int(x[4])), '0x9064')
+            pci.write(self.identifier, '54{:04x}'.format(int(x[5])), '0x9064')
+
+
+            #Select SPI2 and FMC2
+            updated_mux_config = initial_mux_config | 0x20004
+            pci.write(self.identifier, hex(updated_mux_config), '0x9044')
+
+            pci.write(self.identifier, '44{:04x}'.format(int(x[6])), '0x9064')
+            pci.write(self.identifier, '54{:04x}'.format(int(x[7])), '0x9064')
+
+        else:
+            logging.vinfo("Offset update not defined for more than 8 adc")
+
+
+    def _set_samplingrate(self, rate):
+        return
+        if rate == 1:
+            #500 MHz
+            self.update('delay_25_adc', 10)
+            self.update('delay_330_adc', 1)
+            self.update('delay_25_adc_2', 10)
+            self.update('delay_330_adc_2', 1)
+        elif rate == 2:
+            #1 GHz
+            self.update('delay_25_adc', 10)
+            self.update('delay_330_adc', 0)
+            self.update('delay_25_adc_2', 10)
+            self.update('delay_330_adc_2', 0)
+
+    def set_startup(self):
+        #print('board_config setting startup')
+        #self.update('delay_330_th', 0)
+        #sleep(0.1)
+        #self.update('delay_25_th', 0)
+        #sleep(0.1)
+        self.update('delay_330_th_2', 0)
+        sleep(0.1)
+        self.update('delay_25_th_2', 3)
+        sleep(0.1)
+        self.update('delay_25_th_2', 4)
+        sleep(0.1)
+        self.update('chip_delay', [0,0,0,0, 0,0,0,0])
+        sleep(0.1)
+        #Bunch Shifts are offset by +2 to encode -2 to +2 as 0x0 to 0x4 in hardware
+        self.update('bunch_shift', [2,2,2,2, 2,2,2,2])
+        sleep(0.1)
+        self.update('adc_gain', [0,0,0,0, 0,0,0,0])
+        sleep(0.1)
+        self.update('header', 1)
+        sleep(0.1)
+        self.update('pilot_bunch', 1)
+        logging.vinfo('Startup Config Set')
+    def read_from_board(self):
+        """
+        Read values from board and update them in the configuration (Mainly used for skip init functionality)
+        """
+        try:
+            #settings = ['chip_1_delay','chip_2_delay','chip_3_delay','chip_4_delay']
+            # --[ read fine/chip delays ]
+            adc_number = self._config["adc_number"]
+
+            val = pci.read(self.identifier, reg='9080')[0]
+
+            if adc_number > 4:
+                val = val + pci.read(self.identifier, reg='9084')[0]
+            tmp = np.zeros(adc_number)
+
+            for i in range(adc_number):
+                selector = [3,2,1,0,7,6,5,4]
+                tmp[selector[i]] = int(val[(adc_number-1-i)*2:(adc_number-i)*2], 16)
+            self.update('chip_delay', tmp , write=False)
+
+            # --[ read bunch shifts ] --
+            #Banks are flipped!
+            #0x9320:  5,6,7,8
+            #0x9330:  1,2,3,4
+            bunch_shifts = pci.read(self.identifier, reg='9330', amount=4, decimal=True)
+
+            if adc_number > 4:
+                bunch_shifts = bunch_shifts + pci.read(self.identifier, reg='9320', amount=4, decimal=True)
+            self.update('bunch_shift', bunch_shifts, write=False)
+
+            # --[ read and set th delay ]--
+            val = pci.read(self.identifier, reg='90B0')[0]
+            self.update('delay_330_th', self._config['delay_330_max'] - ((int(val, 16)>>1)-5), write=False)
+            val = pci.read(self.identifier, reg='90B4')[0]
+            self.update('delay_25_th',  self._config['delay_25_max'] - int(val, 16), write=False)
+            val = pci.read(self.identifier, reg='90B8')[0]
+            self.update('clk_div_th', int(val, 16), write=False)
+
+            # --[ read and set adc delay ]--
+            val = pci.read(self.identifier, reg='9090')[0]
+            self.update('delay_330_adc', (int(val, 16)>>1)-5, write=False)
+            val = pci.read(self.identifier, reg='9094')[0]
+            self.update('delay_25_adc', int(val, 16), write=False)
+            val = pci.read(self.identifier, reg='9098')[0]
+            self.update('clk_div_adc', int(val, 16), write=False)
+
+            # --[ read and set fpga delay ]--
+            val = pci.read(self.identifier, reg='90C0')[0]
+            self.update('delay_330_fpga', (int(val, 16)>>1)-5, write=False)
+            #val = pci.read(self.identifier, reg='90C4')[0]
+            #self.update('delay_25_fpga', int(val, 16), write=False)
+            val = pci.read(self.identifier, reg='90C8')[0]
+            self.update('clk_div_fpga', int(val, 16), write=False)
+
+
+
+            # --[ read and set number of turns to acquire ]--
+            val = pci.read(self.identifier, reg='9020')[0]
+            self.update('turns_observe', int(val, 16), write=False)
+
+            # --[ read and set number of turns to skip ]--
+            val = pci.read(self.identifier, reg='9028')[0]
+            self.update('turns_skip', int(val, 16), write=False)
+
+
+
+            # --[ read FMC 2 ] --
+            # --[ FMC2 read and set th delay ]--
+            val = pci.read(self.identifier, reg='90A8')[0]
+            self.update('delay_330_th_2', ((int(val, 16)>>1)-5), write=False)
+            val = pci.read(self.identifier, reg='90AC')[0]
+            self.update('delay_25_th_2', int(val, 16), write=False)
+
+
+            # --[ FCM 2 read and set adc delay ]--
+            val = pci.read(self.identifier, reg='90A0')[0]
+            self.update('delay_330_adc_2', ((int(val, 16)>>1)-5), write=False)
+            val = pci.read(self.identifier, reg='90A4')[0]
+            self.update('delay_25_adc_2', int(val, 16), write=False)
+
+
+
+
+
+            # --[ read and update header ]--
+            control = pci.read(self.identifier, 1, '0x9040')[0]
+            control_bits = '{0:032b}'.format(int(control, 16))
+            if control_bits[3] == '1':
+                self.update('header', True, write=False)
+            else:
+                self.update('header', False, write=False)
+        except IndexError:
+            error(0x002, "Could not Read data from Board. Pci returned wrong amount of data.")
+
+
+
+    # 888    d8P         d8888 8888888b. 88888888888 888     888 8888888b.  8888888888      d888
+    # 888   d8P         d88888 888   Y88b    888     888     888 888   Y88b 888            d8888
+    # 888  d8P         d88P888 888    888    888     888     888 888    888 888              888
+    # 888d88K         d88P 888 888   d88P    888     888     888 888   d88P 8888888          888
+    # 8888888b       d88P  888 8888888P"     888     888     888 8888888P"  888              888
+    # 888  Y88b     d88P   888 888           888     888     888 888 T88b   888       888888 888
+    # 888   Y88b   d8888888888 888           888     Y88b. .d88P 888  T88b  888              888
+    # 888    Y88b d88P     888 888           888      "Y88888P"  888   T88b 8888888888     8888888
+    #
+    #
+    #
+    def _set_fpga_delay(self, value):
+        """
+        observer function to set the fpga_delays on the board
+        :param value: the value to set the delays to
+        """
+        time_factor = self.make_uint(value, self.get('delay_330_max'), 'FPGA_Delay')
+        reg_value = "0x000501" + '{0:01x}'.format(time_factor) + "0"
+        pci.write(self.identifier, reg_value, '0x9060')
+        logging.vinfo("Set FPGA clock delay %i * %i --> %i picoseconds" % (time_factor, self.get('delay_330_factor'), time_factor*self.get('delay_330_factor')))
+        #self.update('fpga_delay', value)
+
+
+    def _set_th_delay(self, value):
+        """
+        Set the track and hold delay on the board
+        :param value: the value to set the delay to
+        """
+        time_factor = self.make_uint(value, self.get('delay_330_max'), 'TH_Delay')
+        reg_value = "0x000501" + '{0:01x}'.format(time_factor) + "3"
+        pci.write(self.identifier, reg_value, '0x9060')
+        logging.vinfo("Set T/H Signal delay %i * %i --> %i picoseconds" % (time_factor, self.get('delay_330_factor'), time_factor*self.get('delay_330_factor')))
+
+        self.update('delay_330_adc', time_factor)
+
+    def _set_adc_delay(self, value):
+        """
+        Set the adc delay for the given adc on the board
+        :param value: the value to set the delay to
+        """
+        def write_delay(value, channel):
+            '''write the delays to the board'''
+            value = self.make_uint(value, self.get('delay_330_max'), 'ADC Delay')
+            cmd = '00501' + '%01x' % value + str(channel+4)
+            pci.write(self.identifier, cmd, reg='0x9060')
+            time.sleep(0.005)
+
+        delay = value + self.get('th_to_adc_cycles')
+
+        if delay > self.get('delay_330_max'):
+            delay -= self.get('delay_330_max') + 1
+
+        if self._config['adc_number'] == 4:
+            for adc in range(self._config['adc_number']):
+                write_delay(delay, adc)
+                s = "Setting ADC_%i delay %i * %i --> %i picoseconds" % ((adc+1), delay, self.get('delay_330_factor'), delay*self.get('delay_330_factor'))
+                logging.vinfo(s)
+
+        else:
+            logging.vinfo("adc_delay update not defined for more than 4 adc")
+