Repository: linien-org/linien (language: python)

Issue:
Ability to output fast and slow PID on fast-DACs
Modified bitstream and GUI to allow outputting slow-PID to fast OUT1/OUT2 channels.

Useful for ECDL locking or other systems with two feedback channels, this should resolve the issue described in:
resolve #282 

Notes:
* remaining bug: when disabling slow_PID, the GUI element flips to "ANALOG_OUT0" (slow_PID output is off, though)
* merging back into master (instead of develop) because of build errors when testing develop branch.


Discussion / hints:

Fix patch:
diff --git a/.flake8 b/.flake8
index ba57a662..05b1b432 100644
--- a/.flake8
+++ b/.flake8
@@ -1,5 +1,5 @@
-[flake8]
-max-line-length = 88
-per-file-ignores =
-extend-ignore = E203, E402, E731, W503
-    tests/*: D103, D100
+[flake8]
+max-line-length = 88
+per-file-ignores =
+extend-ignore = E203, E402, E731, W503
+    tests/*: D103, D100
diff --git a/README.md b/README.md
index a1ea87c8..d475c597 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ Features
 -   **Logging**: Use
     [linien-influxdb](https://github.com/linien-org/linien-influxdb)
     to log the lock status to influxdb.
--   **Second integrator** on (slow) analog output 0
+-   **Second integrator** for slow control of piezo in an ECDL
 -   **Additional analog outputs** may be used using the GUI or python client (ANALOG_OUT 1, 2 and 3)
 -   **16 GPIO outputs** may be programmed (e.g. for controlling other devices)
 
@@ -439,9 +439,10 @@ License
 -------
 Linien ‒ User-friendly locking of lasers using RedPitaya (STEMlab 125-14) that just works.
 
-Copyright © 2014-2015 Robert Jördens
-Copyright © 2018-2022 Benjamin Wiegand
-Copyright © 2021-2023 Bastian Leykauf
+Copyright © 2014-2015 Robert Jördens\
+Copyright © 2018-2022 Benjamin Wiegand\
+Copyright © 2021-2023 Bastian Leykauf\
+Copyright © 2022 Christian Freier
 
 Linien is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 
diff --git a/gateware/bit2bin.py b/gateware/bit2bin.py
index c882cbbf..5c81cdc7 100644
--- a/gateware/bit2bin.py
+++ b/gateware/bit2bin.py
@@ -32,7 +32,7 @@ def bit2bin(bit, bin, flip=False):
     bitfile = open(bit, "rb")
 
     (l,) = struct.unpack(">H", bitfile.read(2))
-    if l != 9:
+    if l != 9:  # noqa E741
         raise ValueError("Missing <0009> header, not a bit file")
 
     bitfile.read(l)
diff --git a/gateware/linien_module.py b/gateware/linien_module.py
index 290b819b..7dec008a 100644
--- a/gateware/linien_module.py
+++ b/gateware/linien_module.py
@@ -1,6 +1,7 @@
 # Copyright 2014-2015 Robert Jördens <jordens@gmail.com>
 # Copyright 2018-2022 Benjamin Wiegand <benjamin.wiegand@physik.hu-berlin.de>
-# Copyright 2021-2022 Bastian Leykauf <leykauf@physik.hu-berlin.de>
+# Copyright 2021-2023 Bastian Leykauf <leykauf@physik.hu-berlin.de>
+# Copyright 2022 Christian Freier <christian.freier@nomadatomics.com>
 #
 # This file is part of Linien and based on redpid.
 #
@@ -52,43 +53,37 @@
 
 class LinienLogic(Module, AutoCSR):
     def __init__(self, width=14, signal_width=25, chain_factor_width=8, coeff_width=25):
-        self.init_csr(width, signal_width, chain_factor_width)
+        self.init_csr(width, chain_factor_width)
         self.init_submodules(width, signal_width)
         self.connect_pid()
         self.connect_everything(width, signal_width, coeff_width)
 
-    def init_csr(self, width, signal_width, chain_factor_width):
-        factor_reset = 1 << (chain_factor_width - 1)
-        # we use chain_factor_width + 1 for the single channel mode
+    def init_csr(self, width, chain_factor_width):
         self.dual_channel = CSRStorage(1)
+        self.mod_channel = CSRStorage(1)
+        self.control_channel = CSRStorage(1)
+        self.sweep_channel = CSRStorage(2)
+        self.control_slow_channel = CSRStorage(2)
+        self.fast_mode = CSRStorage(1)
+
+        # we use chain_factor_width + 1 for the single channel mode
+        factor_reset = 1 << (chain_factor_width - 1)
         self.chain_a_factor = CSRStorage(chain_factor_width + 1, reset=factor_reset)
         self.chain_b_factor = CSRStorage(chain_factor_width + 1, reset=factor_reset)
-
         self.chain_a_offset = CSRStorage(width)
         self.chain_b_offset = CSRStorage(width)
-        self.chain_a_offset_signed = Signal((width, True))
-        self.chain_b_offset_signed = Signal((width, True))
         self.combined_offset = CSRStorage(width)
         self.combined_offset_signed = Signal((width, True))
         self.out_offset = CSRStorage(width)
-        self.out_offset_signed = Signal((width, True))
-
-        self.mod_channel = CSRStorage(1)
-        self.control_channel = CSRStorage(1)
-        self.sweep_channel = CSRStorage(2)
-
-        self.fast_mode = CSRStorage(1)
+        self.slow_decimation = CSRStorage(bits_for(16))
+        for i in range(1, 4):
+            setattr(self, f"analog_out_{i}", CSRStorage(15, name=f"analog_out_{i}"))
 
         self.slow_value = CSRStatus(width)
 
-        max_decimation = 16
-        self.slow_decimation = CSRStorage(bits_for(max_decimation))
-
-        for i in range(4):
-            if i == 0:
-                continue
-            name = "analog_out_%d" % i
-            setattr(self, name, CSRStorage(15, name=name))
+        self.chain_a_offset_signed = Signal((width, True))
+        self.chain_b_offset_signed = Signal((width, True))
+        self.out_offset_signed = Signal((width, True))
 
     def init_submodules(self, width, signal_width):
         self.submodules.mod = Modulate(width=width)
@@ -97,7 +92,6 @@ def init_submodules(self, width, signal_width):
         self.submodules.limit_fast1 = LimitCSR(width=width, guard=5)
         self.submodules.limit_fast2 = LimitCSR(width=width, guard=5)
         self.submodules.pid = PID(width=signal_width)
-
         self.submodules.autolock = FPGAAutolock(width=width, max_delay=8191)
 
     def connect_pid(self):
@@ -115,13 +109,11 @@ def connect_pid(self):
         ]
 
     def connect_everything(self, width, signal_width, coeff_width):
-        s = signal_width - width
-
         combined_error_signal = Signal((signal_width, True))
         self.control_signal = Signal((signal_width, True))
 
-        # additional IIR filter that prevents aliasing effects when recording
-        # PSD of error signal
+        # additional IIR filter that prevents aliasing effects when recording PSD of
+        # error signal
         self.submodules.raw_acquisition_iir = Iir(
             width=signal_width,
             coeff_width=coeff_width,
@@ -158,7 +150,7 @@ def connect_everything(self, width, signal_width, coeff_width):
                 Array([self.limit_fast1.y, self.limit_fast2.y])[
                     self.control_channel.storage
                 ]
-                << s
+                << signal_width - width
             ),
         ]
 
@@ -166,7 +158,8 @@ def connect_everything(self, width, signal_width, coeff_width):
 class LinienModule(Module, AutoCSR):
     def __init__(self, platform):
         width = 14
-        signal_width, coeff_width = 25, 25
+        signal_width = 25
+        coeff_width = 25
         chain_factor_bits = 8
 
         self.init_submodules(
@@ -191,7 +184,7 @@ def init_submodules(
             pwm = platform.request("pwm", i)
             ds = sys_double(DeltaSigma(width=15))
             self.comb += pwm.eq(ds.out)
-            setattr(self.submodules, "ds%i" % i, ds)
+            setattr(self.submodules, f"ds{i}", ds)
 
         exp = platform.request("exp")
         self.submodules.gpio_n = Gpio(exp.n)
@@ -223,7 +216,7 @@ def init_submodules(
         self.submodules.decimate = sys_double(Decimate(max_decimation))
         self.clock_domains.cd_decimated_clock = ClockDomain()
         decimated_clock = ClockDomainsRenamer("decimated_clock")
-        self.submodules.slow = decimated_clock(SlowChain())
+        self.submodules.slow_chain = decimated_clock(SlowChain())
 
         self.submodules.scopegen = ScopeGen(signal_width)
 
@@ -232,7 +225,7 @@ def init_submodules(
             [
                 ("fast_a", self.fast_a),
                 ("fast_b", self.fast_b),
-                ("slow", self.slow),
+                ("slow_chain", self.slow_chain),
                 ("scopegen", self.scopegen),
                 ("logic", self.logic),
                 ("robust", self.logic.autolock.robust),
@@ -246,7 +239,7 @@ def init_submodules(
             "gpio_p": 31,
             "fast_a": 0,
             "fast_b": 1,
-            "slow": 2,
+            "slow_chain": 2,
             "scopegen": 6,
             "noise": 7,
             "logic": 8,
@@ -299,6 +292,7 @@ def connect_everything(self, width, signal_width, coeff_width, chain_factor_bits
             mixed_limited.eq(self.logic.limit_error_signal.y),
         ]
 
+        # FAST PID ---------------------------------------------------------------------
         pid_out = Signal((width, True))
         self.comb += [
             If(
@@ -310,72 +304,84 @@ def connect_everything(self, width, signal_width, coeff_width, chain_factor_bits
             pid_out.eq(self.logic.pid.pid_out >> s),
         ]
 
-        fast_outs = list(Signal((width + 4, True)) for channel in (0, 1))
+        # SLOW PID ---------------------------------------------------------------------
+        self.comb += [
+            self.slow_chain.pid.running.eq(self.logic.autolock.lock_running.status),
+            self.slow_chain.input.eq(self.logic.control_signal >> s),
+            self.decimate.decimation.eq(self.logic.slow_decimation.storage),
+            self.cd_decimated_clock.clk.eq(self.decimate.output),
+            self.logic.slow_value.status.eq(self.slow_chain.output),
+        ]
 
-        for channel, fast_out in enumerate(fast_outs):
+        # FAST OUTPUTS -----------------------------------------------------------------
+        fast_outs = [Signal((width + 4, True)), Signal((width + 4, True))]
+        for n_channel, fast_out in enumerate(fast_outs):
             self.comb += fast_out.eq(
-                Mux(self.logic.control_channel.storage == channel, pid_out, 0)
-                + Mux(self.logic.mod_channel.storage == channel, self.logic.mod.y, 0)
+                Mux(
+                    self.logic.control_channel.storage == n_channel,
+                    pid_out,
+                    0,
+                )
                 + Mux(
-                    self.logic.sweep_channel.storage == channel, self.logic.sweep.y, 0
+                    self.logic.mod_channel.storage == n_channel,
+                    self.logic.mod.y,
+                    0,
+                )
+                + Mux(
+                    self.logic.sweep_channel.storage == n_channel,
+                    self.logic.sweep.y,
+                    0,
                 )
                 + Mux(
-                    self.logic.sweep_channel.storage == channel,
+                    self.logic.sweep_channel.storage == n_channel,
                     self.logic.out_offset_signed,
                     0,
                 )
+                + Mux(
+                    self.logic.control_slow_channel.storage == n_channel,
+                    self.slow_chain.output,
+                    0,
+                )
             )
 
-        for analog_idx in range(4):
-            if analog_idx == 0:
-                # first analog out gets a special treatment bc it may contain signal of
-                # slow pid or sweep
-                self.comb += self.slow.pid.running.eq(
-                    self.logic.autolock.lock_running.status
+        # ANALOG OUTPUTS ---------------------------------------------------------------
+        # ANALOG OUT 0 gets a special treatment because it may contain signal of  slow
+        # pid or sweep
+        analog_out = Signal((width + 3, True))
+        self.comb += [
+            analog_out.eq(
+                Mux(
+                    self.logic.sweep_channel.storage == ANALOG_OUT0,
+                    self.logic.sweep.y,
+                    0,
                 )
-
-                slow_pid_out = Signal((width, True))
-                self.comb += slow_pid_out.eq(self.slow.output)
-
-                slow_out = Signal((width + 3, True))
-                self.comb += [
-                    slow_out.eq(
-                        slow_pid_out
-                        + Mux(
-                            self.logic.sweep_channel.storage == ANALOG_OUT0,
-                            self.logic.sweep.y,
-                            0,
-                        )
-                        + Mux(
-                            self.logic.sweep_channel.storage == ANALOG_OUT0,
-                            self.logic.out_offset_signed,
-                            0,
-                        )
-                    ),
-                    self.slow.limit.x.eq(slow_out),
-                ]
-
-                slow_out_shifted = Signal(15)
-                self.sync += slow_out_shifted.eq(
-                    # ds0 apparently has 16 bit, but only allowing positive
-                    # values --> "15 bit"?
-                    (self.slow.limit.y << 1)
-                    + (1 << 14)
+                + Mux(
+                    self.logic.sweep_channel.storage == ANALOG_OUT0,
+                    self.logic.out_offset_signed,
+                    0,
                 )
+                + Mux(
+                    self.logic.control_slow_channel.storage == ANALOG_OUT0,
+                    self.slow_chain.output,
+                    0,
+                )
+            ),
+        ]
+        # NOTE: not sure why limit is used
+        self.comb += self.slow_chain.limit.x.eq(analog_out)
+        # ds0 apparently has 16 bit, but only allowing positive  values --> "15 bit"?
+        slow_out_shifted = Signal(15)
+        self.sync += slow_out_shifted.eq((self.slow_chain.limit.y << 1) + (1 << 14))
+        self.comb += self.ds0.data.eq(slow_out_shifted)
+
+        # connect other analog outputs
+        self.comb += [
+            self.ds1.data.eq(self.logic.analog_out_1.storage),
+            self.ds2.data.eq(self.logic.analog_out_2.storage),
+            self.ds3.data.eq(self.logic.analog_out_3.storage),
+        ]
 
-                analog_out = slow_out_shifted
-            else:
-                # 15 bit
-                dc_source = [
-                    None,
-                    self.logic.analog_out_1.storage,
-                    self.logic.analog_out_2.storage,
-                    self.logic.analog_out_3.storage,
-                ][analog_idx]
-                analog_out = dc_source
-
-            dss = [self.ds0, self.ds1, self.ds2, self.ds3]
-            self.comb += dss[analog_idx].data.eq(analog_out)
+        # ------------------------------------------------------------------------------
 
         self.sync += [
             self.logic.autolock.robust.input.eq(self.scopegen.scope_written_data),
@@ -399,11 +405,6 @@ def connect_everything(self, width, signal_width, coeff_width, chain_factor_bits
             ),
             self.analog.dac_a.eq(self.logic.limit_fast1.y),
             self.analog.dac_b.eq(self.logic.limit_fast2.y),
-            # SLOW OUT
-            self.slow.input.eq(self.logic.control_signal >> s),
-            self.decimate.decimation.eq(self.logic.slow_decimation.storage),
-            self.cd_decimated_clock.clk.eq(self.decimate.output),
-            self.logic.slow_value.status.eq(self.slow.limit.y),
         ]
 
         # Having this in a comb statement caused errors. See PR #251.
diff --git a/gateware/logic/chains.py b/gateware/logic/chains.py
index 9933ae19..a8908498 100644
--- a/gateware/logic/chains.py
+++ b/gateware/logic/chains.py
@@ -70,27 +70,26 @@ def __init__(
         def init_submodule(name, submodule):
             setattr(self.submodules, name, submodule)
 
-        # iterate over in-phase and quadrature signal
-        # both have filters and limits
+        # iterate over in-phase and quadrature signal; both have filters and limits
         for sub_channel_idx in (0, 1):
             x_limit = LimitCSR(width=signal_width, guard=1)
-            init_submodule("x_limit_%d" % (sub_channel_idx + 1), x_limit)
+            setattr(self.submodules, f"x_limit_{sub_channel_idx+1}", x_limit)
             iir_c = Iir(
                 width=signal_width,
                 coeff_width=coeff_width,
                 shift=coeff_width - 2,
                 order=1,
             )
-            init_submodule("iir_c_%d" % (sub_channel_idx + 1), iir_c)
+            setattr(self.submodules, f"iir_c_{sub_channel_idx+1}", iir_c)
             iir_d = Iir(
                 width=signal_width,
                 coeff_width=coeff_width,
                 shift=coeff_width - 2,
                 order=2,
             )
-            init_submodule("iir_d_%d" % (sub_channel_idx + 1), iir_d)
+            setattr(self.submodules, f"iir_d_{sub_channel_idx+1}", iir_d)
             y_limit = LimitCSR(width=signal_width, guard=3)
-            init_submodule("y_limit_%d" % (sub_channel_idx + 1), y_limit)
+            setattr(self.submodules, f"y_limit_{sub_channel_idx+1}", y_limit)
 
             self.comb += [
                 x_limit.x.eq(([self.demod.i, self.demod.q][sub_channel_idx] << s) + dx),
@@ -116,8 +115,6 @@ def init_submodule(name, submodule):
 
 class SlowChain(Module, AutoCSR):
     def __init__(self, width=14, signal_width=25):
-        s = signal_width - width
-
         self.input = Signal((width, True))
         self.output = Signal((width, True))
 
@@ -134,12 +131,12 @@ def __init__(self, width=14, signal_width=25):
         self.comb += [
             self.pid.input.eq(self.input),
             self.output.eq(self.pid.pid_out),
-            out.eq(self.limit.y << s),
+            out.eq(self.limit.y << signal_width - width),
         ]
 
 
 def cross_connect(gpio, chains):
-    state_names = ["force"] + ["di%i" % i for i in range(len(gpio.i))]
+    state_names = ["force"] + [f"di{i}" for i in range(len(gpio.i))]
     states = [1, gpio.i]
     signal_names = ["zero"]
     signals = Array([0])
@@ -147,15 +144,15 @@ def cross_connect(gpio, chains):
     for n, c in chains:
         for s in c.state_out:
             states.append(s)
-            state_names.append("%s_%s" % (n, s.backtrace[-1][0]))
+            state_names.append(f"{n}{s.backtrace[-1][0]}")
         for s in c.signal_out:
             signals.append(s)
             name = s.backtrace[-1][0]
-            signal_names.append("%s_%s" % (n, name))
+            signal_names.append(f"{n}_{name}")
             sig = CSRStatus(len(s), name=name)
-            clr = CSR(name="%s_clr" % name)
-            max = CSRStatus(len(s), name="%s_max" % name)
-            min = CSRStatus(len(s), name="%s_min" % name)
+            clr = CSR(name=f"{name}_clr")
+            max = CSRStatus(len(s), name=f"{name}_max")
+            min = CSRStatus(len(s), name=f"{name}_min")
             # setattr(c, sig.name, sig)
             setattr(c, clr.name, clr)
             setattr(c, max.name, max)
@@ -170,27 +167,30 @@ def cross_connect(gpio, chains):
     gpio.state = CSRStatus(len(state))
     gpio.state_clr = CSR()
     gpio.sync += [
-        If(gpio.state_clr.re, gpio.state.status.eq(0),).Else(
+        If(
+            gpio.state_clr.re,
+            gpio.state.status.eq(0),
+        ).Else(
             gpio.state.status.eq(gpio.state.status | state),
         )
     ]
 
     # connect gpio output to "doi%i_en"
     for i, s in enumerate(gpio.o):
-        csr = CSRStorage(len(state), name="do%i_en" % i)
+        csr = CSRStorage(len(state), name=f"do{i}_en")
         setattr(gpio, csr.name, csr)
         gpio.sync += s.eq((state & csr.storage) != 0)
 
     # connect state ins to "%s_en" and signal ins to "%s_sel"
     for n, c in chains:
         for s in c.state_in:
-            csr = CSRStorage(len(state), name="%s_en" % s.backtrace[-1][0])
+            csr = CSRStorage(len(state), name=f"{s.backtrace[-1][0]}_en")
             setattr(c, csr.name, csr)
             c.sync += s.eq((state & csr.storage) != 0)
 
         for s in c.signal_in:
             csr = CSRStorage(
-                bits_for(len(signals) - 1), name="%s_sel" % s.backtrace[-1][0]
+                bits_for(len(signals) - 1), name=f"{s.backtrace[-1][0]}_sel"
             )
             setattr(c, csr.name, csr)
             c.sync += s.eq(signals[csr.storage])
diff --git a/linien-common/linien_common/common.py b/linien-common/linien_common/common.py
index e88b9363..b55a46ef 100644
--- a/linien-common/linien_common/common.py
+++ b/linien-common/linien_common/common.py
@@ -58,9 +58,9 @@ class SpectrumUncorrelatedException(Exception):
 
 def downsample_history(times, values, max_time_diff, max_N=N_POINTS):
     """
-    The history should not grow too much. When recording for long intervals,
-    we want to throw away some datapoints that were recorded with a sampling rate
-    that is too high. This function takes care of this.
+    The history should not grow too much. When recording for long intervals, we want to
+    throw away some datapoints that were recorded with a sampling rate that is too high.
+    This function takes care of this.
     """
     last_time = None
 
@@ -103,8 +103,8 @@ def update_signal_history(
         control_history["values"].append(np.mean(to_plot["control_signal"]))
         control_history["times"].append(time())
 
-        if "slow" in to_plot:
-            control_history["slow_values"].append(to_plot["slow"])
+        if "slow_control_signal" in to_plot:
+            control_history["slow_values"].append(to_plot["slow_control_signal"])
             control_history["slow_times"].append(time())
 
         if "monitor_signal" in to_plot:
diff --git a/linien-gui/linien_gui/app.py b/linien-gui/linien_gui/app.py
index 98680b55..5d198dbc 100644
--- a/linien-gui/linien_gui/app.py
+++ b/linien-gui/linien_gui/app.py
@@ -25,7 +25,7 @@
 from linien_gui.ui.main_window import MainWindow
 from linien_gui.ui.psd_window import PSDWindow
 from linien_gui.ui.version_checker import VersionCheckerThread
-from linien_gui.utils_gui import set_window_icon
+from linien_gui.utils import set_window_icon
 from linien_gui.widgets import UI_PATH, CustomWidget
 from PyQt5 import QtWidgets
 from pyqtgraph.Qt import QtCore
@@ -34,8 +34,6 @@
 
 
 class QTApp(QtCore.QObject):
-    ready = QtCore.pyqtSignal(bool)
-
     def __init__(self):
         self.app = QtWidgets.QApplication(sys.argv)
 
@@ -62,13 +60,9 @@ def client_connected(self, client):
         self.control = client.control
         self.parameters = client.parameters
 
-        self.ready.connect(self.init)
-        self.ready.emit(True)
-
-    def init(self):
         for instance in CustomWidget.instances:
             try:
-                instance.connection_established()
+                instance.on_connection_established()
             except Exception:
                 print(
                     (
@@ -87,7 +81,7 @@ def call_listeners(self):
         if hasattr(self, "client") and self.client and self.client.connected:
             try:
                 self.parameters.call_listeners()
-            except Exception:
+            except AttributeError:
                 print("call_listeners() failed")
                 print_exc()
 
@@ -106,8 +100,7 @@ def shutdown(self):
         self.close()
 
     def open_psd_window(self):
-        # first hiding it, then showing it brings it to foregroud if it is in
-        # background
+        # first hiding it, then showing it brings it to foregroud if it is in background
         self.psd_window.hide()
         self.psd_window.show()
 
diff --git a/linien-gui/linien_gui/config.py b/linien-gui/linien_gui/config.py
index 4fcefbc1..0662b1b1 100644
--- a/linien-gui/linien_gui/config.py
+++ b/linien-gui/linien_gui/config.py
@@ -17,23 +17,25 @@
 
 import os
 import pickle
+from enum import Enum
 
 import appdirs
 import rpyc
 
-COLORS = {
-    "spectrum_1": 0,
-    "spectrum_2": 1,
-    "spectrum_combined": 2,
-    "control_signal": 0,
-    "control_signal_history": 1,
-    "slow_history": 3,
-    "monitor_signal_history": 4,
-}
 # don't plot more often than once per `DEFAULT_PLOT_RATE_LIMIT` seconds
 DEFAULT_PLOT_RATE_LIMIT = 0.1
 
 
+class Color(Enum):
+    SPECTRUM1 = 0
+    SPECTRUM2 = 1
+    SPECTRUM_COMBINED = 2
+    CONTROL_SIGNAL = 0
+    CONTROL_SIGNAL_HISTORY = 1
+    SLOW_HISTORY = 3
+    MONITOR_SIGNAL_HISTORY = 4
+
+
 def get_data_folder():
     folder_name = appdirs.user_data_dir("linien")
 
diff --git a/linien-gui/linien_gui/ui/device_manager.py b/linien-gui/linien_gui/ui/device_manager.py
index 96277f8f..50f0fc7b 100644
--- a/linien-gui/linien_gui/ui/device_manager.py
+++ b/linien-gui/linien_gui/ui/device_manager.py
@@ -27,7 +27,7 @@
 )
 from linien_gui.threads import ConnectionThread
 from linien_gui.ui.new_device_dialog import NewDeviceDialog
-from linien_gui.utils_gui import set_window_icon
+from linien_gui.utils import set_window_icon
 from linien_gui.widgets import CustomWidget
 from PyQt5 import QtCore, QtWidgets
 
diff --git a/linien-gui/linien_gui/ui/general_panel.py b/linien-gui/linien_gui/ui/general_panel.py
index 3d225001..efa4c59f 100644
--- a/linien-gui/linien_gui/ui/general_panel.py
+++ b/linien-gui/linien_gui/ui/general_panel.py
@@ -1,5 +1,6 @@
 # Copyright 2018-2022 Benjamin Wiegand <benjamin.wiegand@physik.hu-berlin.de>
-# Copyright 2021-2022 Bastian Leykauf <leykauf@physik.hu-berlin.de>
+# Copyright 2021-2023 Bastian Leykauf <leykauf@physik.hu-berlin.de>
+# Copyright 2022 Christian Freier <christian.freier@nomadatomics.com>
 #
 # This file is part of Linien and based on redpid.
 #
@@ -23,186 +24,182 @@
     FAST_OUT2,
     convert_channel_mixing_value,
 )
-from linien_gui.utils_gui import param2ui
+from linien_gui.utils import param2ui
 from linien_gui.widgets import CustomWidget
 from PyQt5 import QtWidgets
 
 
 class GeneralPanel(QtWidgets.QWidget, CustomWidget):
     def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
+        super(GeneralPanel, self).__init__(*args, **kwargs)
         self.load_ui("general_panel.ui")
 
-    def ready(self):
-        self.ids.channel_mixing_slider.valueChanged.connect(self.channel_mixing_changed)
-        self.ids.fast_mode.stateChanged.connect(self.fast_mode_changed)
-        self.ids.dual_channel.stateChanged.connect(self.dual_channel_changed)
-
-        self.ids.mod_channel.currentIndexChanged.connect(self.mod_channel_changed)
-        self.ids.control_channel.currentIndexChanged.connect(
-            self.control_channel_changed
+        self.channelMixingSlider.valueChanged.connect(self.on_channel_mixing_changed)
+        self.fastModeCheckBox.stateChanged.connect(self.on_fast_mode_changed)
+        self.dualChannelCheckBox.stateChanged.connect(self.on_dual_channel_changed)
+        self.modulationChannelComboBox.currentIndexChanged.connect(
+            self.on_mod_channel_changed
         )
-        self.ids.sweep_channel.currentIndexChanged.connect(self.sweep_channel_changed)
-        self.ids.slow_control_channel.currentIndexChanged.connect(
-            self.slow_control_channel_changed
+        self.controlChannelComboBox.currentIndexChanged.connect(
+            self.on_control_channel_changed
         )
-
-        self.ids.polarity_fast_out1.currentIndexChanged.connect(
-            self.polarity_fast_out1_changed
+        self.sweepChannelComboBox.currentIndexChanged.connect(
+            self.on_sweep_channel_changed
+        )
+        self.slowControlComboBox.currentIndexChanged.connect(
+            self.on_slow_control_channel_changed
         )
-        self.ids.polarity_fast_out2.currentIndexChanged.connect(
-            self.polarity_fast_out2_changed
+        self.polarityComboBoxFastOut1.currentIndexChanged.connect(
+            self.on_polarity_fast_out1_changed
         )
-        self.ids.polarity_analog_out0.currentIndexChanged.connect(
-            self.polarity_analog_out0_changed
+        self.polarityComboBoxFastOut2.currentIndexChanged.connect(
+            self.on_polarity_fast_out2_changed
+        )
+        self.polarityComboBoxAnalogOut0.currentIndexChanged.connect(
+            self.on_polarity_analog_out0_changed
         )
 
         for idx in range(4):
             if idx == 0:
                 continue
-            element = getattr(self.ids, "analog_out_%d" % idx)
+            element = getattr(self, f"analogOutComboBox{idx}")
             element.setKeyboardTracking(False)
             element.valueChanged.connect(
-                lambda value, idx=idx: self.change_analog_out(idx)
+                lambda _, idx=idx: self.on_analog_out_changed(idx)
             )
 
-    def change_analog_out(self, idx):
-        name = "analog_out_%d" % idx
-        getattr(self.parameters, name).value = int(
-            getattr(self.ids, name).value() / ANALOG_OUT_V
-        )
-        self.control.write_registers()
-
-    def connection_established(self):
+    def on_connection_established(self):
         self.parameters = self.app.parameters
         self.control = self.app.control
 
-        param2ui(self.parameters.fast_mode, self.ids.fast_mode)
-
-        param2ui(self.parameters.fast_mode, self.ids.fast_mode)
+        param2ui(self.parameters.fast_mode, self.fastModeCheckBox)
 
-        def dual_channel_changed(value):
-            self.ids.dual_channel_mixing.setVisible(value)
-            self.ids.fast_in_1_status.setText(
+        def on_dual_channel_changed(value):
+            self.dualChannelMixingGroupBox.setVisible(value)
+            self.fast_in_1_status.setText(
                 "error signal" if not value else "error signal 1"
             )
-            self.ids.fast_in_2_status.setText(
-                "monitor" if not value else "error signal 2"
-            )
+            self.fast_in_2_status.setText("monitor" if not value else "error signal 2")
             return value
 
         param2ui(
-            self.parameters.dual_channel, self.ids.dual_channel, dual_channel_changed
+            self.parameters.dual_channel,
+            self.dualChannelCheckBox,
+            on_dual_channel_changed,
         )
 
         param2ui(
             self.parameters.channel_mixing,
-            self.ids.channel_mixing_slider,
+            self.channelMixingSlider,
             lambda value: value + 128,
         )
         # this is required to update the descriptive labels in the beginning
-        self.channel_mixing_changed()
-
-        param2ui(self.parameters.mod_channel, self.ids.mod_channel)
-        param2ui(self.parameters.control_channel, self.ids.control_channel)
-        param2ui(self.parameters.sweep_channel, self.ids.sweep_channel)
-        param2ui(self.parameters.pid_on_slow_enabled, self.ids.slow_control_channel)
-
-        param2ui(self.parameters.polarity_fast_out1, self.ids.polarity_fast_out1)
-        param2ui(self.parameters.polarity_fast_out2, self.ids.polarity_fast_out2)
-        param2ui(self.parameters.polarity_analog_out0, self.ids.polarity_analog_out0)
-
-        def show_polarity_settings(*args):
-            used_channels = set(
-                (
-                    self.parameters.control_channel.value,
-                    self.parameters.sweep_channel.value,
-                )
-            )
-
-            if self.parameters.pid_on_slow_enabled.value:
-                used_channels.add(ANALOG_OUT0)
-
-            self.ids.polarity_selector.setVisible(len(used_channels) > 1)
+        self.on_channel_mixing_changed()
 
-            def set_visibility(element, channel_id):
-                element.setVisible(channel_id in used_channels)
+        param2ui(self.parameters.mod_channel, self.modulationChannelComboBox)
+        param2ui(self.parameters.control_channel, self.controlChannelComboBox)
+        param2ui(self.parameters.sweep_channel, self.sweepChannelComboBox)
+        param2ui(self.parameters.control_slow_channel, self.slowControlComboBox)
 
-            set_visibility(self.ids.polarity_container_fast_out1, FAST_OUT1)
-            set_visibility(self.ids.polarity_container_fast_out2, FAST_OUT2)
-            set_visibility(self.ids.polarity_container_analog_out0, ANALOG_OUT0)
+        param2ui(self.parameters.polarity_fast_out1, self.polarityComboBoxFastOut1)
+        param2ui(self.parameters.polarity_fast_out2, self.polarityComboBoxFastOut2)
+        param2ui(self.parameters.polarity_analog_out0, self.polarityComboBoxAnalogOut0)
 
-        self.parameters.control_channel.on_change(show_polarity_settings)
-        self.parameters.sweep_channel.on_change(show_polarity_settings)
-        self.parameters.mod_channel.on_change(show_polarity_settings)
-        self.parameters.pid_on_slow_enabled.on_change(show_polarity_settings)
+        self.parameters.control_channel.on_change(self.show_polarity_settings)
+        self.parameters.sweep_channel.on_change(self.show_polarity_settings)
+        self.parameters.mod_channel.on_change(self.show_polarity_settings)
+        self.parameters.control_slow_channel.on_change(self.show_polarity_settings)
+        self.parameters.pid_on_slow_enabled.on_change(self.show_polarity_settings)
 
         for idx in range(4):
             if idx == 0:
                 continue
-            name = "analog_out_%d" % idx
             param2ui(
-                getattr(self.parameters, name),
-                getattr(self.ids, name),
+                getattr(self.parameters, f"analog_out_{idx}"),
+                getattr(self, f"analogOutComboBox{idx}"),
                 process_value=lambda v: ANALOG_OUT_V * v,
             )
 
-        def fast_mode_changed(fast_mode_enabled):
+        def on_fast_mode_changed(fast_mode_enabled):
             """Disables controls that are irrelevant if fast mode is enabled"""
             widgets_to_disable = (
-                self.ids.output_ports_group,
-                self.ids.input_ports_group,
+                self.output_ports_group,
+                self.input_ports_group,
             )
             for widget in widgets_to_disable:
                 widget.setEnabled(not fast_mode_enabled)
 
-        self.parameters.fast_mode.on_change(fast_mode_changed)
+        self.parameters.fast_mode.on_change(on_fast_mode_changed)
 
-    def channel_mixing_changed(self):
-        value = int(self.ids.channel_mixing_slider.value()) - 128
+    def on_analog_out_changed(self, idx):
+        getattr(self.parameters, f"analog_out_{idx}").value = int(
+            getattr(self, f"analogOutComboBox{idx}").value() / ANALOG_OUT_V
+        )
+        self.control.write_registers()
+
+    def on_channel_mixing_changed(self):
+        value = int(self.channelMixingSlider.value()) - 128
         self.parameters.channel_mixing.value = value
         self.control.write_registers()
 
-        self.update_channel_mixing_slider(value)
+        # update channel mixing slider
+        a_value, b_value = convert_channel_mixing_value(value)
+        self.chain_a_factor.setText(f"{a_value}")
+        self.chain_b_factor.setText(f"{b_value}")
 
-    def fast_mode_changed(self):
-        self.parameters.fast_mode.value = int(self.ids.fast_mode.checkState() > 0)
+    def on_fast_mode_changed(self):
+        self.parameters.fast_mode.value = int(self.fastModeCheckBox.checkState() > 0)
         self.control.write_registers()
 
-    def dual_channel_changed(self):
-        self.parameters.dual_channel.value = int(self.ids.dual_channel.checkState() > 0)
+    def on_dual_channel_changed(self):
+        self.parameters.dual_channel.value = int(
+            self.dualChannelCheckBox.checkState() > 0
+        )
         self.control.write_registers()
 
-    def update_channel_mixing_slider(self, value):
-        a_value, b_value = convert_channel_mixing_value(value)
-
-        self.ids.chain_a_factor.setText("%d" % a_value)
-        self.ids.chain_b_factor.setText("%d" % b_value)
-
-    def mod_channel_changed(self, channel):
+    def on_mod_channel_changed(self, channel):
         self.parameters.mod_channel.value = channel
         self.control.write_registers()
 
-    def control_channel_changed(self, channel):
+    def on_control_channel_changed(self, channel):
         self.parameters.control_channel.value = channel
         self.control.write_registers()
 
-    def slow_control_channel_changed(self, channel):
-        self.parameters.pid_on_slow_enabled.value = bool(channel)
+    def on_slow_control_channel_changed(self, channel):
+        if channel > 2:
+            # disabled state
+            self.parameters.pid_on_slow_enabled.value = False
+        else:
+            self.parameters.control_slow_channel.value = channel
+            self.parameters.pid_on_slow_enabled.value = True
         self.control.write_registers()
 
-    def sweep_channel_changed(self, channel):
+    def on_sweep_channel_changed(self, channel):
         self.parameters.sweep_channel.value = channel
         self.control.write_registers()
 
-    def polarity_fast_out1_changed(self, polarity):
+    def on_polarity_fast_out1_changed(self, polarity):
         self.parameters.polarity_fast_out1.value = bool(polarity)
         self.control.write_registers()
 
-    def polarity_fast_out2_changed(self, polarity):
+    def on_polarity_fast_out2_changed(self, polarity):
         self.parameters.polarity_fast_out2.value = bool(polarity)
         self.control.write_registers()
 
-    def polarity_analog_out0_changed(self, polarity):
+    def on_polarity_analog_out0_changed(self, polarity):
         self.parameters.polarity_analog_out0.value = bool(polarity)
         self.control.write_registers()
+
+    def show_polarity_settings(self, *args):
+        used_channels = {
+            self.parameters.control_channel.value,
+            self.parameters.sweep_channel.value,
+        }
+
+        if self.parameters.pid_on_slow_enabled.value:
+            used_channels.add(self.parameters.control_slow_channel.value)
+
+        self.polaritySelectorGroupBox.setVisible(len(used_channels) > 1)
+        self.polarityContainerFastOut1.setVisible(FAST_OUT1 in used_channels)
+        self.polarityContainerFastOut2.setVisible(FAST_OUT2 in used_channels)
+        self.polarityContainerAnalogOut0.setVisible(ANALOG_OUT0 in used_channels)
diff --git a/linien-gui/linien_gui/ui/general_panel.ui b/linien-gui/linien_gui/ui/general_panel.ui
index b2356b28..7063aec6 100644
--- a/linien-gui/linien_gui/ui/general_panel.ui
+++ b/linien-gui/linien_gui/ui/general_panel.ui
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>400</width>
-    <height>1058</height>
+    <height>1054</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -31,7 +31,7 @@
        </widget>
       </item>
       <item>
-       <widget class="QCheckBox" name="fast_mode">
+       <widget class="QCheckBox" name="fastModeCheckBox">
         <property name="text">
          <string>enable fast mode</string>
         </property>
@@ -68,13 +68,19 @@
          </widget>
         </item>
         <item>
-         <widget class="QComboBox" name="control_channel">
+         <widget class="QComboBox" name="controlChannelComboBox">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
+          <property name="minimumSize">
+           <size>
+            <width>100</width>
+            <height>0</height>
+           </size>
+          </property>
           <item>
            <property name="text">
             <string>FAST OUT 1</string>
@@ -105,7 +111,7 @@
          </widget>
         </item>
         <item>
-         <widget class="QComboBox" name="sweep_channel">
+         <widget class="QComboBox" name="sweepChannelComboBox">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
             <horstretch>0</horstretch>
@@ -147,13 +153,19 @@
          </widget>
         </item>
         <item>
-         <widget class="QComboBox" name="mod_channel">
+         <widget class="QComboBox" name="modulationChannelComboBox">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
+          <property name="minimumSize">
+           <size>
+            <width>100</width>
+            <height>0</height>
+           </size>
+          </property>
           <item>
            <property name="text">
             <string>FAST OUT 1</string>
@@ -184,7 +196,7 @@
          </widget>
         </item>
         <item>
-         <widget class="QComboBox" name="slow_control_channel">
+         <widget class="QComboBox" name="slowControlComboBox">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
             <horstretch>0</horstretch>
@@ -193,7 +205,12 @@
           </property>
           <item>
            <property name="text">
-            <string>disabled</string>
+            <string>FAST OUT 1</string>
+           </property>
+          </item>
+          <item>
+           <property name="text">
+            <string>FAST OUT 2</string>
            </property>
           </item>
           <item>
@@ -201,12 +218,17 @@
             <string>ANALOG OUT 0</string>
            </property>
           </item>
+          <item>
+           <property name="text">
+            <string>disabled</string>
+           </property>
+          </item>
          </widget>
         </item>
        </layout>
       </item>
       <item>
-       <widget class="QGroupBox" name="polarity_selector">
+       <widget class="QGroupBox" name="polaritySelectorGroupBox">
         <property name="styleSheet">
          <string notr="true">background: #ffe7b5</string>
         </property>
@@ -237,7 +259,7 @@
           </widget>
          </item>
          <item>
-          <widget class="QWidget" name="polarity_container_fast_out1" native="true">
+          <widget class="QWidget" name="polarityContainerFastOut1" native="true">
            <layout class="QHBoxLayout" name="horizontalLayout_10">
             <property name="spacing">
              <number>6</number>
@@ -268,7 +290,7 @@
              </widget>
             </item>
             <item>
-             <widget class="QComboBox" name="polarity_fast_out1">
+             <widget class="QComboBox" name="polarityComboBoxFastOut1">
               <property name="sizePolicy">
                <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
                 <horstretch>0</horstretch>
@@ -291,7 +313,7 @@
           </widget>
          </item>
          <item>
-          <widget class="QWidget" name="polarity_container_fast_out2" native="true">
+          <widget class="QWidget" name="polarityContainerFastOut2" native="true">
            <layout class="QHBoxLayout" name="horizontalLayout_12">
             <property name="leftMargin">
              <number>0</number>
@@ -319,7 +341,7 @@
              </widget>
             </item>
             <item>
-             <widget class="QComboBox" name="polarity_fast_out2">
+             <widget class="QComboBox" name="polarityComboBoxFastOut2">
               <property name="sizePolicy">
                <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
                 <horstretch>0</horstretch>
@@ -342,7 +364,7 @@
           </widget>
          </item>
          <item>
-          <widget class="QWidget" name="polarity_container_analog_out0" native="true">
+          <widget class="QWidget" name="polarityContainerAnalogOut0" native="true">
            <layout class="QHBoxLayout" name="horizontalLayout_11">
             <property name="leftMargin">
              <number>0</number>
@@ -370,7 +392,7 @@
              </widget>
             </item>
             <item>
-             <widget class="QComboBox" name="polarity_analog_out0">
+             <widget class="QComboBox" name="polarityComboBoxAnalogOut0">
               <property name="sizePolicy">
                <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
                 <horstretch>0</horstretch>
@@ -484,14 +506,14 @@
           </widget>
          </item>
          <item>
-          <widget class="QCheckBox" name="dual_channel">
+          <widget class="QCheckBox" name="dualChannelCheckBox">
            <property name="text">
             <string>use as second error signal</string>
            </property>
           </widget>
          </item>
          <item>
-          <widget class="QGroupBox" name="dual_channel_mixing">
+          <widget class="QGroupBox" name="dualChannelMixingGroupBox">
            <property name="title">
             <string>Mixing of channel 1 and 2</string>
            </property>
@@ -571,7 +593,7 @@
              </layout>
             </item>
             <item>
-             <widget class="QSlider" name="channel_mixing_slider">
+             <widget class="QSlider" name="channelMixingSlider">
               <property name="maximum">
                <number>255</number>
               </property>
@@ -660,7 +682,7 @@
          </widget>
         </item>
         <item>
-         <widget class="CustomDoubleSpinBoxNoSign" name="analog_out_1">
+         <widget class="CustomDoubleSpinBoxNoSign" name="analogOutComboBox1">
           <property name="keyboardTracking">
            <bool>false</bool>
           </property>
@@ -703,7 +725,7 @@
          </widget>
         </item>
         <item>
-         <widget class="CustomDoubleSpinBoxNoSign" name="analog_out_2">
+         <widget class="CustomDoubleSpinBoxNoSign" name="analogOutComboBox2">
           <property name="keyboardTracking">
            <bool>false</bool>
           </property>
@@ -746,7 +768,7 @@
          </widget>
         </item>
         <item>
-         <widget class="CustomDoubleSpinBoxNoSign" name="analog_out_3">
+         <widget class="CustomDoubleSpinBoxNoSign" name="analogOutComboBox3">
           <property name="keyboardTracking">
            <bool>false</bool>
           </property>
diff --git a/linien-gui/linien_gui/ui/lock_status_panel.py b/linien-gui/linien_gui/ui/lock_status_panel.py
index 554e3f21..3c91b8ba 100644
--- a/linien-gui/linien_gui/ui/lock_status_panel.py
+++ b/linien-gui/linien_gui/ui/lock_status_panel.py
@@ -16,7 +16,7 @@
 # You should have received a copy of the GNU General Public License
 # along with Linien.  If not, see <http://www.gnu.org/licenses/>.
 
-from linien_gui.utils_gui import param2ui
+from linien_gui.utils import param2ui
 from linien_gui.widgets import CustomWidget
 from PyQt5 import QtWidgets
 
@@ -32,7 +32,7 @@ def ready(self):
             self.control_signal_history_length_changed
         )
 
-    def connection_established(self):
+    def on_connection_established(self):
         self.parameters = self.app.parameters
         self.control = self.app.control
 
diff --git a/linien-gui/linien_gui/ui/locking_panel.py b/linien-gui/linien_gui/ui/locking_panel.py
index 7b1b3989..f3f277d1 100644
--- a/linien-gui/linien_gui/ui/locking_panel.py
+++ b/linien-gui/linien_gui/ui/locking_panel.py
@@ -17,7 +17,7 @@
 # along with Linien.  If not, see <http://www.gnu.org/licenses/>.
 
 from linien_common.common import FAST_AUTOLOCK
-from linien_gui.utils_gui import param2ui
+from linien_gui.utils import param2ui
 from linien_gui.widgets import CustomWidget
 from PyQt5 import QtWidgets
 
@@ -55,7 +55,7 @@ def ready(self):
             self.autolock_mode_preference_changed
         )
 
-    def connection_established(self):
+    def on_connection_established(self):
         self.parameters = self.app.parameters
         self.control = self.app.control
 
diff --git a/linien-gui/linien_gui/ui/main_window.py b/linien-gui/linien_gui/ui/main_window.py
index 5b4151c8..bc2df639 100644
--- a/linien-gui/linien_gui/ui/main_window.py
+++ b/linien-gui/linien_gui/ui/main_window.py
@@ -25,9 +25,9 @@
 import numpy as np
 from linien_common.common import check_plot_data
 from linien_common.config import N_COLORS
-from linien_gui.config import COLORS
+from linien_gui.config import Color
 from linien_gui.ui.plot_widget import INVALID_POWER
-from linien_gui.utils_gui import color_to_hex
+from linien_gui.utils import color_to_hex
 from linien_gui.widgets import CustomWidget
 from PyQt5 import QtWidgets
 
@@ -152,7 +152,7 @@ def import_parameters(self):
 
             self.control.write_registers()
 
-    def connection_established(self):
+    def on_connection_established(self):
         self.control = self.app.control
         self.parameters = self.app.parameters
 
@@ -187,24 +187,26 @@ def change_sweep_control_visibility(*args):
         self.parameters.lock.on_change(lambda *args: self.reset_std_history())
 
         def update_legend_color(*args):
-            def set_color(el, color_name):
+            def set_color(el, color: Color):
                 return el.setStyleSheet(
                     "color: "
                     + color_to_hex(
-                        getattr(
-                            self.parameters, "plot_color_%d" % COLORS[color_name]
-                        ).value
+                        getattr(self.parameters, f"plot_color_{color.value}").value
                     )
                 )
 
-            set_color(self.ids.legend_spectrum_1, "spectrum_1")
-            set_color(self.ids.legend_spectrum_2, "spectrum_2")
-            set_color(self.ids.legend_spectrum_combined, "spectrum_combined")
-            set_color(self.ids.legend_error_signal, "spectrum_combined")
-            set_color(self.ids.legend_control_signal, "control_signal")
-            set_color(self.ids.legend_control_signal_history, "control_signal_history")
-            set_color(self.ids.legend_slow_signal_history, "slow_history")
-            set_color(self.ids.legend_monitor_signal_history, "monitor_signal_history")
+            set_color(self.ids.legend_spectrum_1, Color.SPECTRUM1)
+            set_color(self.ids.legend_spectrum_2, Color.SPECTRUM2)
+            set_color(self.ids.legend_spectrum_combined, Color.SPECTRUM_COMBINED)
+            set_color(self.ids.legend_error_signal, Color.SPECTRUM_COMBINED)
+            set_color(self.ids.legend_control_signal, Color.CONTROL_SIGNAL)
+            set_color(
+                self.ids.legend_control_signal_history, Color.CONTROL_SIGNAL_HISTORY
+            )
+            set_color(self.ids.legend_slow_signal_history, Color.SLOW_HISTORY)
+            set_color(
+                self.ids.legend_monitor_signal_history, Color.MONITOR_SIGNAL_HISTORY
+            )
 
         for color_idx in range(N_COLORS):
             getattr(self.parameters, "plot_color_%d" % color_idx).on_change(
diff --git a/linien-gui/linien_gui/ui/modulation_sweep_panel.py b/linien-gui/linien_gui/ui/modulation_sweep_panel.py
index 5849ec1f..f4733102 100644
--- a/linien-gui/linien_gui/ui/modulation_sweep_panel.py
+++ b/linien-gui/linien_gui/ui/modulation_sweep_panel.py
@@ -17,7 +17,7 @@
 # along with Linien.  If not, see <http://www.gnu.org/licenses/>.
 
 from linien_common.common import MHz, Vpp
-from linien_gui.utils_gui import param2ui
+from linien_gui.utils import param2ui
 from linien_gui.widgets import CustomWidget
 from PyQt5 import QtWidgets
 
@@ -40,7 +40,7 @@ def ready(self):
 
         self.ids.spectroscopyTabs.setCurrentIndex(0)
 
-    def connection_established(self):
+    def on_connection_established(self):
         self.parameters = self.app.parameters
         self.control = self.app.control
 
diff --git a/linien-gui/linien_gui/ui/optimization_panel.py b/linien-gui/linien_gui/ui/optimization_panel.py
index b1604402..1a6100a2 100644
--- a/linien-gui/linien_gui/ui/optimization_panel.py
+++ b/linien-gui/linien_gui/ui/optimization_panel.py
@@ -17,7 +17,7 @@
 # along with Linien.  If not, see <http://www.gnu.org/licenses/>.
 
 from linien_common.common import MHz, Vpp
-from linien_gui.utils_gui import param2ui
+from linien_gui.utils import param2ui
 from linien_gui.widgets import CustomWidget
 from PyQt5 import QtWidgets
 
@@ -65,7 +65,7 @@ def optim_enabled_changed(_, param_name=param_name):
 
             getattr(self.ids, param_name).stateChanged.connect(optim_enabled_changed)
 
-    def connection_established(self):
+    def on_connection_established(self):
         self.parameters = self.app.parameters
         self.control = self.app.control
 
diff --git a/linien-gui/linien_gui/ui/plot_widget.py b/linien-gui/linien_gui/ui/plot_widget.py
index c22aaaf6..7baab1bf 100644
--- a/linien-gui/linien_gui/ui/plot_widget.py
+++ b/linien-gui/linien_gui/ui/plot_widget.py
@@ -33,20 +33,19 @@
     update_signal_history,
 )
 from linien_common.config import N_COLORS
-from linien_gui.config import COLORS, DEFAULT_PLOT_RATE_LIMIT
+from linien_gui.config import DEFAULT_PLOT_RATE_LIMIT, Color
 from linien_gui.widgets import CustomWidget
 from PyQt5 import QtGui, QtWidgets
 from PyQt5.QtCore import pyqtSignal
 from pyqtgraph.Qt import QtCore
 
-# NOTE: this is required for using a pen_width > 1.
-# There is a bug though that causes the plot to be way too small. Therefore,
-# we call PlotWidget.resize() after a while
+# NOTE: this is required for using a pen_width > 1. There is a bug though that causes
+# the plot to be way too small. Therefore, we call PlotWidget.resize() after a while
 pg.setConfigOptions(
     useOpenGL=True,
-    # by default, pyqtgraph tries to clean some things up using atexit.
-    # This causes problems with rpyc objects as their connection is already
-    # closed. Therefore, we disable this cleanup.
+    # by default, pyqtgraph tries to clean some things up using atexit. This causes
+    # problems with rpyc objects as their connection is already closed. Therefore, we
+    # disable this cleanup.
     exitCleanup=False,
 )
 
@@ -80,7 +79,7 @@ def sweep_speed(self):
     def lock(self):
         return self.parameters.lock
 
-    def connection_established(self):
+    def on_connection_established(self):
         # we have to wait until parameters (of parent) is available
         QtCore.QTimer.singleShot(100, self.listen_to_parameter_changes)
 
@@ -121,15 +120,11 @@ def __init__(self, *args, **kwargs):
         super().__init__(
             *args,
             axisItems={"bottom": TimeXAxis(orientation="bottom", parent=self)},
-            **kwargs
+            **kwargs,
         )
 
-        # self.hideAxis("bottom")
-        # self.hideAxis('left')
         self.getAxis("bottom").enableAutoSIPrefix(False)
 
-        # self.setMouseEnabled(x=False, y=False)
-        # self.setMenuEnabled(False)
         self.showGrid(x=True, y=True)
         self.setLabel("bottom", "time", units="s")
 
@@ -150,12 +145,11 @@ def __init__(self, *args, **kwargs):
 
         self.getViewBox().setLimits(xMin=0, xMax=2048, yMin=-1, yMax=1)
 
-        # NOTE: increasing the pen width requires OpenGL, otherwise painting
-        # gets horribly slow.
-        # See: https://github.com/pyqtgraph/pyqtgraph/issues/533
+        # NOTE: increasing the pen width requires OpenGL, otherwise painting gets
+        # horribly slow. See: https://github.com/pyqtgraph/pyqtgraph/issues/533
         # OpenGL is enabled in the beginning of this file.
-        # NOTE: OpenGL has a bug that causes the plot to be way too small.
-        # Therefore, self.resize() is called below.
+        # NOTE: OpenGL has a bug that causes the plot to be way too small. Therefore,
+        # self.resize() is called below.
 
         self.crosshair = pg.InfiniteLine(pos=N_POINTS / 2, pen=pg.mkPen("w", width=1))
         self.addItem(self.crosshair)
@@ -227,25 +221,24 @@ def _to_data_coords(self, event):
         x, y = pos.x(), pos.y()
         return x, y
 
-    def connection_established(self):
+    def on_connection_established(self):
         self.parameters = self.app.parameters
         self.control = self.app.control
 
-        def set_pens(color):
+        def set_pens(*args):
             pen_width = self.parameters.plot_line_width.value
 
-            for curve, name in {
-                self.signal1: "spectrum_1",
-                self.signal2: "spectrum_2",
-                self.combined_signal: "spectrum_combined",
-                self.control_signal: "control_signal",
-                self.control_signal_history: "control_signal_history",
-                self.slow_history: "slow_history",
-                self.monitor_signal_history: "monitor_signal_history",
+            for curve, color in {
+                self.signal1: Color.SPECTRUM1,
+                self.signal2: Color.SPECTRUM2,
+                self.combined_signal: Color.SPECTRUM_COMBINED,
+                self.control_signal: Color.CONTROL_SIGNAL,
+                self.control_signal_history: Color.CONTROL_SIGNAL_HISTORY,
+                self.slow_history: Color.SLOW_HISTORY,
+                self.monitor_signal_history: Color.MONITOR_SIGNAL_HISTORY,
             }.items():
-                color_idx = COLORS[name]
                 r, g, b, *stuff = getattr(
-                    self.parameters, "plot_color_%d" % color_idx
+                    self.parameters, f"plot_color_{color.value}"
                 ).value
                 a = self.parameters.plot_line_opacity.value
                 curve.setPen(pg.mkPen((r, g, b, a), width=pen_width))
@@ -383,7 +376,7 @@ def mouseReleaseEvent(self, event):
                             # transmitted which blocks the autolock
                             *sorted([x0, x]),
                             pickle.dumps(last_combined_error_signal),
-                            additional_spectra=pickle.dumps(self._cached_plot_data)
+                            additional_spectra=pickle.dumps(self._cached_plot_data),
                         )
 
                         (
@@ -421,17 +414,16 @@ def replot(self, to_plot):
             time_beginning - self.last_plot_time <= self.plot_rate_limit
             and not self._plot_paused
         ):
-            # don't plot too often at it only causes unnecessary load
-            # this does not apply if plot is paused, because in this case we want
-            # to collect all the data that we can get in order to pass it to the
-            # autolock
+            # don't plot too often as it only causes unnecessary load this does not
+            # apply if plot is paused, because in this case we want to collect all the
+            # data that we can get in order to pass it to the autolock
             return
 
         self.last_plot_time = time_beginning
 
-        # NOTE: this is necessary if OpenGL is activated. Otherwise, the
-        # plot is way too small. This command apparently causes a repaint
-        # and works fine even though the values are nonsense.
+        # NOTE: this is necessary if OpenGL is activated. Otherwise, the plot is way too
+        # small. This command apparently causes a repaint and works fine even though the
+        # values are nonsense.
         if not self._fixed_opengl_bug:
             self._fixed_opengl_bug = True
             self.resize(
@@ -451,8 +443,8 @@ def replot(self, to_plot):
             if not check_plot_data(self.parameters.lock.value, to_plot):
                 return
 
-            # we also call this if the laser is not locked because it resets
-            # the history in this case
+            # we also call this if the laser is not locked because it resets the history
+            # in this case
             history, slow_history = self.update_signal_history(to_plot)
 
             if self.parameters.lock.value:
@@ -668,8 +660,8 @@ def plot_autolock_target_line(self, combined_error_signal):
             self.lock_target_line.setVisible(False)
 
     def keyPressEvent(self, event):
-        # we listen here in addition to the main window because some events
-        # are only caught here
+        # we listen here in addition to the main window because some events are only
+        # caught here
         self.keyPressed.emit(event.key())
 
     def update_signal_history(self, to_plot):
diff --git a/linien-gui/linien_gui/ui/psd_plot_widget.py b/linien-gui/linien_gui/ui/psd_plot_widget.py
index 704b0d10..c17c800e 100644
--- a/linien-gui/linien_gui/ui/psd_plot_widget.py
+++ b/linien-gui/linien_gui/ui/psd_plot_widget.py
@@ -109,7 +109,7 @@ def __init__(self, *args, **kwargs):
 
         self.recalculate_min_max()
 
-    def connection_established(self):
+    def on_connection_established(self):
         self.parameters = self.app.parameters
         self.control = self.app.control
 
diff --git a/linien-gui/linien_gui/ui/psd_table_widget.py b/linien-gui/linien_gui/ui/psd_table_widget.py
index 8f9b6ec9..3e0c4ee1 100644
--- a/linien-gui/linien_gui/ui/psd_table_widget.py
+++ b/linien-gui/linien_gui/ui/psd_table_widget.py
@@ -18,7 +18,7 @@
 
 from datetime import datetime
 
-from linien_gui.utils_gui import color_to_hex
+from linien_gui.utils import color_to_hex
 from linien_gui.widgets import CustomWidget
 from PyQt5 import QtCore, QtWidgets
 from PyQt5.QtCore import pyqtSignal
@@ -35,7 +35,7 @@ def __init__(self, *args, **kwargs):
 
         self.uuids = []
 
-    def connection_established(self):
+    def on_connection_established(self):
         self.parameters = self.app.parameters
         self.control = self.app.control
 
diff --git a/linien-gui/linien_gui/ui/psd_window.py b/linien-gui/linien_gui/ui/psd_window.py
index 0967c688..0c7acfcc 100644
--- a/linien-gui/linien_gui/ui/psd_window.py
+++ b/linien-gui/linien_gui/ui/psd_window.py
@@ -22,7 +22,7 @@
 import linien_gui
 from linien_common.common import PSD_ALGORITHM_LPSD, PSD_ALGORITHM_WELCH
 from linien_gui.dialogs import error_dialog
-from linien_gui.utils_gui import RandomColorChoser, param2ui, set_window_icon
+from linien_gui.utils import RandomColorChoser, param2ui, set_window_icon
 from linien_gui.widgets import CustomWidget
 from PyQt5 import QtWidgets
 
@@ -72,7 +72,7 @@ def change_psd_algorithm(self, index):
             index
         ]
 
-    def connection_established(self):
+    def on_connection_established(self):
         self.parameters = self.app.parameters
         self.control = self.app.control
 
diff --git a/linien-gui/linien_gui/ui/right_panel.py b/linien-gui/linien_gui/ui/right_panel.py
index a6a6de04..5ff64329 100644
--- a/linien-gui/linien_gui/ui/right_panel.py
+++ b/linien-gui/linien_gui/ui/right_panel.py
@@ -24,7 +24,7 @@ class RightPanel(QtWidgets.QWidget, CustomWidget):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
-    def connection_established(self):
+    def on_connection_established(self):
         self.parameters = self.app.parameters
         self.control = self.app.control
 
diff --git a/linien-gui/linien_gui/ui/spectroscopy_panel.py b/linien-gui/linien_gui/ui/spectroscopy_panel.py
index 4e6e942f..6da34ea3 100644
--- a/linien-gui/linien_gui/ui/spectroscopy_panel.py
+++ b/linien-gui/linien_gui/ui/spectroscopy_panel.py
@@ -17,7 +17,7 @@
 # along with Linien.  If not, see <http://www.gnu.org/licenses/>.
 
 from linien_common.common import HIGH_PASS_FILTER, LOW_PASS_FILTER
-from linien_gui.utils_gui import param2ui
+from linien_gui.utils import param2ui
 from linien_gui.widgets import CustomWidget
 from PyQt5 import QtWidgets
 
@@ -98,7 +98,7 @@ def invert_changed(value):
 
         self.ids.invert.stateChanged.connect(invert_changed)
 
-    def connection_established(self):
+    def on_connection_established(self):
         self.parameters = self.app.parameters
         self.control = self.app.control
 
diff --git a/linien-gui/linien_gui/ui/sweep_control.py b/linien-gui/linien_gui/ui/sweep_control.py
index cce9c217..299c0494 100644
--- a/linien-gui/linien_gui/ui/sweep_control.py
+++ b/linien-gui/linien_gui/ui/sweep_control.py
@@ -28,7 +28,7 @@ def ready(self):
         # initialize sweep slider boundaries
         self.ids.sweep_slider.init()
 
-    def connection_established(self):
+    def on_connection_established(self):
         self.control = self.app.control
         self.parameters = self.app.parameters
 
diff --git a/linien-gui/linien_gui/ui/view_panel.py b/linien-gui/linien_gui/ui/view_panel.py
index 910bdc0a..93612c67 100644
--- a/linien-gui/linien_gui/ui/view_panel.py
+++ b/linien-gui/linien_gui/ui/view_panel.py
@@ -22,7 +22,7 @@
 
 import numpy as np
 from linien_common.config import N_COLORS
-from linien_gui.utils_gui import color_to_hex, param2ui
+from linien_gui.utils import color_to_hex, param2ui
 from linien_gui.widgets import CustomWidget
 from PyQt5 import QtGui, QtWidgets
 
@@ -58,7 +58,7 @@ def edit_color(self, color_idx):
         print("set color", color_idx, color.getRgb())
         param.value = (r, g, b, a)
 
-    def connection_established(self):
+    def on_connection_established(self):
         self.parameters = self.app.parameters
         self.control = self.app.control
 
diff --git a/linien-gui/linien_gui/utils_gui.py b/linien-gui/linien_gui/utils.py
similarity index 90%
rename from linien-gui/linien_gui/utils_gui.py
rename to linien-gui/linien_gui/utils.py
index a01173ac..80c227a3 100644
--- a/linien-gui/linien_gui/utils_gui.py
+++ b/linien-gui/linien_gui/utils.py
@@ -31,13 +31,14 @@
 
 
 def param2ui(parameter, element, process_value=lambda x: x):
-    """Updates ui elements according to parameter values.
+    """
+    Updates ui elements according to parameter values.
 
     Listens to parameter changes and sets the value of `element` automatically.
-    Optionally, the value can be processed using `process_value`.
-    This function should be used because it automatically blocks signal
-    emission from the target element; otherwise this can cause nasty
-    endless loops when quickly changing a paramater multiple times.
+    Optionally, the value can be processed using `process_value`. This function should
+    be used because it automatically blocks signal emission from the target element;
+    otherwise this can cause nasty endless loops when quickly changing a paramater
+    multiple times.
     """
 
     def on_change(value, element=element):
@@ -52,7 +53,7 @@ def on_change(value, element=element):
         elif isinstance(element, (QTabWidget, QComboBox)):
             element.setCurrentIndex(int(value))
         else:
-            raise Exception("unsupported element type %s" % type(element))
+            raise TypeError(f"Unsupported element type {type(element)}")
 
         element.blockSignals(False)
 
diff --git a/linien-gui/linien_gui/widgets.py b/linien-gui/linien_gui/widgets.py
index d14effd8..f69b0661 100644
--- a/linien-gui/linien_gui/widgets.py
+++ b/linien-gui/linien_gui/widgets.py
@@ -48,7 +48,7 @@ def __init__(self, *args, **kwargs):
     def ready(self):
         pass
 
-    def connection_established(self):
+    def on_connection_established(self):
         # This is executed the client succesfully established a connection to the server
         # and can be extended by inheritting classes.
         pass
diff --git a/linien-server/linien_server/acquisition.py b/linien-server/linien_server/acquisition.py
index fde61323..b42c6edc 100644
--- a/linien-server/linien_server/acquisition.py
+++ b/linien-server/linien_server/acquisition.py
@@ -17,7 +17,6 @@
 # along with Linien.  If not, see <http://www.gnu.org/licenses/>.
 
 import atexit
-import sys
 import threading
 from enum import Enum
 from multiprocessing import Pipe, Process
@@ -47,41 +46,43 @@ class AcquisitionProcessSignals(Enum):
 
 class AcquisitionMaster:
     def __init__(self, use_ssh, host):
-        self.on_acquisition = None
+        self.on_new_data_received = None
 
         def receive_acquired_data(conn):
             while True:
                 is_raw, received_data, data_uuid = conn.recv()
-                if self.on_acquisition is not None:
-                    self.on_acquisition(is_raw, received_data, data_uuid)
+                if self.on_new_data_received is not None:
+                    self.on_new_data_received(is_raw, received_data, data_uuid)
 
-        self.acq_process, child_pipe = Pipe()
-        p = Process(
-            target=self.connect_acquisition_process, args=(child_pipe, use_ssh, host)
+        self.parent_conn, child_conn = Pipe()
+        process = Process(
+            target=self.connect_acquisition_process, args=(child_conn, use_ssh, host)
         )
-        p.daemon = True
-        p.start()
+        process.daemon = True
+        process.start()
 
         # wait until connection is established
-        self.acq_process.recv()
+        self.parent_conn.recv()
 
-        t = threading.Thread(target=receive_acquired_data, args=(self.acq_process,))
-        t.daemon = True
-        t.start()
+        thread = threading.Thread(
+            target=receive_acquired_data, args=(self.parent_conn,)
+        )
+        thread.daemon = True
+        thread.start()
 
         atexit.register(self.shutdown)
 
-    def run_data_acquisition(self, on_acquisition):
-        self.on_acquisition = on_acquisition
+    def run_data_acquisition(self, on_new_data_received):
+        self.on_new_data_received = on_new_data_received
 
     def connect_acquisition_process(self, pipe, use_ssh, host):
         if use_ssh:
-            # for debugging, acquisition process may be launched manually on the
-            # server and rpyc can be used to connect to it
+            # for debugging, acquisition process may be launched manually on the server
+            # and rpyc can be used to connect to it
             acquisition_rpyc = rpyc.connect(host, ACQUISITION_PORT)
             acquisition = acquisition_rpyc.root
         else:
-            # this is what happens in production mode
+            # This is what happens in production mode
             from linien_server.acquisition_process import DataAcquisitionService
 
             stop_nginx()
@@ -91,9 +92,8 @@ def connect_acquisition_process(self, pipe, use_ssh, host):
         # tell the main thread that we're ready
         pipe.send(True)
 
-        # run a loop that listens for acquired data and transmits them
-        # to the main thread. Also redirects calls from the main thread
-        # to the acquiry process.
+        # Run a loop that listens for acquired data and transmits them to the main
+        # thread. Also redirects calls from the main thread sto the acquiry process.
         last_hash = None
         while True:
             # check whether the main thread sent a command to the acquiry process
@@ -136,40 +136,40 @@ def connect_acquisition_process(self, pipe, use_ssh, host):
             sleep(0.05)
 
     def shutdown(self):
-        if self.acq_process:
-            self.acq_process.send((AcquisitionProcessSignals.SHUTDOWN,))
+        if self.parent_conn:
+            self.parent_conn.send((AcquisitionProcessSignals.SHUTDOWN,))
 
         start_nginx()
 
     def set_sweep_speed(self, speed):
-        self.acq_process.send((AcquisitionProcessSignals.SET_SWEEP_SPEED, speed))
+        self.parent_conn.send((AcquisitionProcessSignals.SET_SWEEP_SPEED, speed))
 
     def lock_status_changed(self, status):
-        if self.acq_process:
-            self.acq_process.send((AcquisitionProcessSignals.SET_LOCK_STATUS, status))
+        if self.parent_conn:
+            self.parent_conn.send((AcquisitionProcessSignals.SET_LOCK_STATUS, status))
 
     def fetch_additional_signals_changed(self, status):
-        if self.acq_process:
-            self.acq_process.send((AcquisitionProcessSignals.FETCH_QUADRATURES, status))
+        if self.parent_conn:
+            self.parent_conn.send((AcquisitionProcessSignals.FETCH_QUADRATURES, status))
 
     def set_csr(self, key, value):
-        self.acq_process.send((AcquisitionProcessSignals.SET_CSR, (key, value)))
+        self.parent_conn.send((AcquisitionProcessSignals.SET_CSR, (key, value)))
 
     def set_iir_csr(self, *args):
-        self.acq_process.send((AcquisitionProcessSignals.SET_IIR_CSR, args))
+        self.parent_conn.send((AcquisitionProcessSignals.SET_IIR_CSR, args))
 
     def pause_acquisition(self):
-        self.acq_process.send((AcquisitionProcessSignals.PAUSE_ACQUISIITON, True))
+        self.parent_conn.send((AcquisitionProcessSignals.PAUSE_ACQUISIITON, True))
 
     def continue_acquisition(self, uuid):
-        self.acq_process.send((AcquisitionProcessSignals.CONTINUE_ACQUISITION, uuid))
+        self.parent_conn.send((AcquisitionProcessSignals.CONTINUE_ACQUISITION, uuid))
 
     def set_raw_acquisition(self, enabled, decimation=None):
         if decimation is None:
             decimation = 0
-        self.acq_process.send(
+        self.parent_conn.send(
             (AcquisitionProcessSignals.SET_RAW_ACQUISITION, (enabled, decimation))
         )
 
     def set_dual_channel(self, enabled):
-        self.acq_process.send((AcquisitionProcessSignals.SET_DUAL_CHANNEL, enabled))
+        self.parent_conn.send((AcquisitionProcessSignals.SET_DUAL_CHANNEL, enabled))
diff --git a/linien-server/linien_server/acquisition_process.py b/linien-server/linien_server/acquisition_process.py
index 792741cc..7e08b277 100644
--- a/linien-server/linien_server/acquisition_process.py
+++ b/linien-server/linien_server/acquisition_process.py
@@ -40,8 +40,8 @@ def shutdown():
 
 class DataAcquisitionService(Service):
     def __init__(self):
-        self.r = RedPitaya()
-        self.csr = PythonCSR(self.r)
+        self.red_pitaya = RedPitaya()
+        self.csr = PythonCSR(self.red_pitaya)
         self.csr_queue = []
         self.csr_iir_queue = []
 
@@ -54,11 +54,11 @@ def __init__(self):
 
         self.locked = False
         self.exposed_set_sweep_speed(9)
-        # when self.locked is set to True, this doesn't mean that the lock is
-        # really on. It just means that the lock is requested and that the
-        # gateware waits until the sweep is at the correct position for the lock.
-        # Therefore, when self.locked is set, the acquisition process waits for
-        # confirmation from the gateware that the lock is actually running.
+        # when self.locked is set to True, this doesn't mean that the lock is really on.
+        # It just means that the lock is requested and that the gateware waits until the
+        # sweep is at the correct position for the lock. Therefore, when self.locked is
+        # set, the acquisition process waits for confirmation from the gateware that the
+        # lock is actually running.
         self.confirmed_that_in_lock = False
 
         self.fetch_additional_signals = True
@@ -97,7 +97,7 @@ def run_acquiry_loop():
 
                 # copied from https://github.com/RedPitaya/RedPitaya/blob/14cca62dd58f29826ee89f4b28901602f5cdb1d8/api/src/oscilloscope.c#L115  # noqa: E501
                 # check whether scope was triggered
-                not_triggered = (self.r.scope.read(0x1 << 2) & 0x4) > 0
+                not_triggered = (self.red_pitaya.scope.read(0x1 << 2) & 0x4) > 0
                 if not_triggered:
                     sleep(0.05)
                     continue
@@ -105,9 +105,9 @@ def run_acquiry_loop():
                 data, is_raw = self.read_data()
 
                 if self.acquisition_paused:
-                    # it may seem strange that we check this here a second time.
-                    # Reason: `read_data` takes some time and if in the mean time
-                    # acquisition was paused, we do not want to send the data
+                    # it may seem strange that we check this here a second time. Reason:
+                    # `read_data` takes some time and if in the mean time acquisition
+                    # was paused, we do not want to send the data
                     continue
 
                 if self.skip_next_data:
@@ -119,28 +119,28 @@ def run_acquiry_loop():
 
                 self.program_acquisition_and_rearm()
 
-        self.t = threading.Thread(target=run_acquiry_loop, args=())
-        self.t.daemon = True
-        self.t.start()
+        self.thread = threading.Thread(target=run_acquiry_loop, args=())
+        self.thread.daemon = True
+        self.thread.start()
 
     def program_acquisition_and_rearm(self, trigger_delay=16384):
         """Programs the acquisition settings and rearms acquisition."""
         if not self.locked:
             target_decimation = 2 ** (self.sweep_speed + int(np.log2(DECIMATION)))
 
-            self.r.scope.data_decimation = target_decimation
-            self.r.scope.trigger_delay = int(trigger_delay / DECIMATION) - 1
+            self.red_pitaya.scope.data_decimation = target_decimation
+            self.red_pitaya.scope.trigger_delay = int(trigger_delay / DECIMATION) - 1
 
         elif self.raw_acquisition_enabled:
-            self.r.scope.data_decimation = 2**self.raw_acquisition_decimation
-            self.r.scope.trigger_delay = trigger_delay
+            self.red_pitaya.scope.data_decimation = 2**self.raw_acquisition_decimation
+            self.red_pitaya.scope.trigger_delay = trigger_delay
 
         else:
-            self.r.scope.data_decimation = 1
-            self.r.scope.trigger_delay = int(trigger_delay / DECIMATION) - 1
+            self.red_pitaya.scope.data_decimation = 1
+            self.red_pitaya.scope.trigger_delay = int(trigger_delay / DECIMATION) - 1
 
         # trigger_source=6 means external trigger positive edge
-        self.r.scope.rearm(trigger_source=6)
+        self.red_pitaya.scope.rearm(trigger_source=6)
 
     def exposed_return_data(self, last_hash):
         no_data_available = self.data_hash is None
@@ -152,8 +152,8 @@ def exposed_return_data(self, last_hash):
 
     def exposed_set_sweep_speed(self, speed):
         self.sweep_speed = speed
-        # if a slow acqisition is currently running and we change the sweep speed
-        # we don't want to wait until it finishes
+        # if a slow acqisition is currently running and we change the sweep speed we
+        # don't want to wait until it finishes
         self.program_acquisition_and_rearm()
 
     def exposed_set_lock_status(self, locked):
@@ -184,18 +184,18 @@ def exposed_pause_acquisition(self):
     def exposed_continue_acquisition(self, uuid):
         self.program_acquisition_and_rearm()
         sleep(0.01)
-        # resetting data here is not strictly required but we want to be on the
-        # safe side
+        # resetting data here is not strictly required but we want to be on the safe
+        # side
         self.data_hash = None
         self.data = None
         self.acquisition_paused = False
         self.data_uuid = uuid
-        # if we are sweeping, we have to skip one data set because an incomplete
-        # sweep may have been recorded. When locked, this does not matter
+        # if we are sweeping, we have to skip one data set because an incomplete sweep
+        # may have been recorded. When locked, this does not matter
         self.skip_next_data = not self.confirmed_that_in_lock
 
     def read_data(self):
-        write_pointer = self.r.scope.write_pointer_trigger
+        write_pointer = self.red_pitaya.scope.write_pointer_trigger
 
         if self.raw_acquisition_enabled:
             return self.read_data_raw(0x10000, write_pointer, MAX_N_POINTS), True
@@ -212,7 +212,7 @@ def read_data(self):
                     channel_offset, write_pointer, N_POINTS
                 )
 
-                for sub_channel_idx in range(2):
+                for sub_channel_idx in (0, 1):
                     signals.append(channel_data[sub_channel_idx])
 
             signals_named = {}
@@ -239,7 +239,7 @@ def read_data(self):
 
             slow_out = self.csr.get("logic_slow_value")
             slow_out = slow_out if slow_out <= 8191 else slow_out - 16384
-            signals_named["slow"] = slow_out
+            signals_named["slow_control_signal"] = slow_out
 
             return signals_named, False
 
@@ -255,10 +255,10 @@ def read_data_raw(self, offset, addr, data_length):
         #   2'h0,adc_b_rd,2'h0,adc_a_rd
         #   i.e.: 2 zero bits, channel b (14 bit), 2 zero bits, channel a (14 bit)
         # .copy() is required, because np.frombuffer returns a readonly array
-        raw_data = self.r.scope.reads(offset + (4 * addr), data_length).copy()
+        raw_data = self.red_pitaya.scope.reads(offset + (4 * addr), data_length).copy()
 
-        # raw_data is an array of 32-bit ints
-        # we cast it to 16 bit --> each original int is split into two ints
+        # raw_data is an array of 32-bit ints. We cast it to 16 bit --> each original
+        # int is split into two ints
         raw_data.dtype = np.int16
 
         # sign bit is at position 14, but we have 16 bit ints
@@ -271,7 +271,7 @@ def read_data_raw(self, offset, addr, data_length):
             additional_raw_data = self.read_data_raw(offset, 0, to_read_later)
             signals = tuple(
                 np.append(signals[signal_idx], additional_raw_data[signal_idx])
-                for signal_idx in range(2)
+                for signal_idx in (0, 1)
             )
 
         return signals
diff --git a/linien-server/linien_server/autolock/autolock.py b/linien-server/linien_server/autolock/autolock.py
index d78419aa..bcc59427 100644
--- a/linien-server/linien_server/autolock/autolock.py
+++ b/linien-server/linien_server/autolock/autolock.py
@@ -199,7 +199,7 @@ def react_to_new_spectrum(self, plot_data):
                 control_signal = plot_data["control_signal"]
 
                 return self.after_lock(
-                    error_signal, control_signal, plot_data.get("slow")
+                    error_signal, control_signal, plot_data.get("slow_control_signal")
                 )
 
         except Exception:
diff --git a/linien-server/linien_server/csrmap.py b/linien-server/linien_server/csrmap.py
index bc45cea1..d28d24e5 100644
--- a/linien-server/linien_server/csrmap.py
+++ b/linien-server/linien_server/csrmap.py
@@ -160,131 +160,132 @@
     'gpio_p_outs': (31, 0x001, 8, True),
     'gpio_p_oes': (31, 0x002, 8, True),
     'logic_dual_channel': (8, 0x000, 1, True),
-    'logic_chain_a_factor': (8, 0x001, 9, True),
-    'logic_chain_b_factor': (8, 0x003, 9, True),
-    'logic_chain_a_offset': (8, 0x005, 14, True),
-    'logic_chain_b_offset': (8, 0x007, 14, True),
-    'logic_combined_offset': (8, 0x009, 14, True),
-    'logic_out_offset': (8, 0x00b, 14, True),
-    'logic_mod_channel': (8, 0x00d, 1, True),
-    'logic_control_channel': (8, 0x00e, 1, True),
-    'logic_sweep_channel': (8, 0x00f, 2, True),
-    'logic_fast_mode': (8, 0x010, 1, True),
-    'logic_slow_value': (8, 0x011, 14, False),
-    'logic_slow_decimation': (8, 0x013, 5, True),
-    'logic_analog_out_1': (8, 0x014, 15, True),
-    'logic_analog_out_2': (8, 0x016, 15, True),
-    'logic_analog_out_3': (8, 0x018, 15, True),
-    'logic_mod_amp': (8, 0x01a, 14, True),
-    'logic_mod_freq': (8, 0x01c, 32, True),
-    'logic_sweep_step': (8, 0x020, 30, True),
-    'logic_sweep_min': (8, 0x024, 14, True),
-    'logic_sweep_max': (8, 0x026, 14, True),
-    'logic_sweep_run': (8, 0x028, 1, True),
-    'logic_sweep_pause': (8, 0x029, 1, True),
-    'logic_limit_error_signal_min': (8, 0x02a, 25, True),
-    'logic_limit_error_signal_max': (8, 0x02e, 25, True),
-    'logic_limit_fast1_min': (8, 0x032, 14, True),
-    'logic_limit_fast1_max': (8, 0x034, 14, True),
-    'logic_limit_fast2_min': (8, 0x036, 14, True),
-    'logic_limit_fast2_max': (8, 0x038, 14, True),
-    'logic_pid_setpoint': (8, 0x03a, 25, True),
-    'logic_pid_kp': (8, 0x03e, 14, True),
-    'logic_pid_ki': (8, 0x040, 14, True),
-    'logic_pid_reset': (8, 0x042, 1, True),
-    'logic_pid_kd': (8, 0x043, 14, True),
-    'logic_autolock_robust_time_scale': (8, 0x045, 14, True),
-    'logic_autolock_robust_N_instructions': (8, 0x047, 5, True),
-    'logic_autolock_robust_final_wait_time': (8, 0x048, 14, True),
-    'logic_autolock_robust_peak_height_0': (8, 0x04a, 28, True),
-    'logic_autolock_robust_peak_height_1': (8, 0x04e, 28, True),
-    'logic_autolock_robust_peak_height_2': (8, 0x052, 28, True),
-    'logic_autolock_robust_peak_height_3': (8, 0x056, 28, True),
-    'logic_autolock_robust_peak_height_4': (8, 0x05a, 28, True),
-    'logic_autolock_robust_peak_height_5': (8, 0x05e, 28, True),
-    'logic_autolock_robust_peak_height_6': (8, 0x062, 28, True),
-    'logic_autolock_robust_peak_height_7': (8, 0x066, 28, True),
-    'logic_autolock_robust_peak_height_8': (8, 0x06a, 28, True),
-    'logic_autolock_robust_peak_height_9': (8, 0x06e, 28, True),
-    'logic_autolock_robust_peak_height_10': (8, 0x072, 28, True),
-    'logic_autolock_robust_peak_height_11': (8, 0x076, 28, True),
-    'logic_autolock_robust_peak_height_12': (8, 0x07a, 28, True),
-    'logic_autolock_robust_peak_height_13': (8, 0x07e, 28, True),
-    'logic_autolock_robust_peak_height_14': (8, 0x082, 28, True),
-    'logic_autolock_robust_peak_height_15': (8, 0x086, 28, True),
-    'logic_autolock_robust_peak_height_16': (8, 0x08a, 28, True),
-    'logic_autolock_robust_peak_height_17': (8, 0x08e, 28, True),
-    'logic_autolock_robust_peak_height_18': (8, 0x092, 28, True),
-    'logic_autolock_robust_peak_height_19': (8, 0x096, 28, True),
-    'logic_autolock_robust_peak_height_20': (8, 0x09a, 28, True),
-    'logic_autolock_robust_peak_height_21': (8, 0x09e, 28, True),
-    'logic_autolock_robust_peak_height_22': (8, 0x0a2, 28, True),
-    'logic_autolock_robust_peak_height_23': (8, 0x0a6, 28, True),
-    'logic_autolock_robust_peak_height_24': (8, 0x0aa, 28, True),
-    'logic_autolock_robust_peak_height_25': (8, 0x0ae, 28, True),
-    'logic_autolock_robust_peak_height_26': (8, 0x0b2, 28, True),
-    'logic_autolock_robust_peak_height_27': (8, 0x0b6, 28, True),
-    'logic_autolock_robust_peak_height_28': (8, 0x0ba, 28, True),
-    'logic_autolock_robust_peak_height_29': (8, 0x0be, 28, True),
-    'logic_autolock_robust_peak_height_30': (8, 0x0c2, 28, True),
-    'logic_autolock_robust_peak_height_31': (8, 0x0c6, 28, True),
-    'logic_autolock_robust_wait_for_0': (8, 0x0ca, 14, True),
-    'logic_autolock_robust_wait_for_1': (8, 0x0cc, 14, True),
-    'logic_autolock_robust_wait_for_2': (8, 0x0ce, 14, True),
-    'logic_autolock_robust_wait_for_3': (8, 0x0d0, 14, True),
-    'logic_autolock_robust_wait_for_4': (8, 0x0d2, 14, True),
-    'logic_autolock_robust_wait_for_5': (8, 0x0d4, 14, True),
-    'logic_autolock_robust_wait_for_6': (8, 0x0d6, 14, True),
-    'logic_autolock_robust_wait_for_7': (8, 0x0d8, 14, True),
-    'logic_autolock_robust_wait_for_8': (8, 0x0da, 14, True),
-    'logic_autolock_robust_wait_for_9': (8, 0x0dc, 14, True),
-    'logic_autolock_robust_wait_for_10': (8, 0x0de, 14, True),
-    'logic_autolock_robust_wait_for_11': (8, 0x0e0, 14, True),
-    'logic_autolock_robust_wait_for_12': (8, 0x0e2, 14, True),
-    'logic_autolock_robust_wait_for_13': (8, 0x0e4, 14, True),
-    'logic_autolock_robust_wait_for_14': (8, 0x0e6, 14, True),
-    'logic_autolock_robust_wait_for_15': (8, 0x0e8, 14, True),
-    'logic_autolock_robust_wait_for_16': (8, 0x0ea, 14, True),
-    'logic_autolock_robust_wait_for_17': (8, 0x0ec, 14, True),
-    'logic_autolock_robust_wait_for_18': (8, 0x0ee, 14, True),
-    'logic_autolock_robust_wait_for_19': (8, 0x0f0, 14, True),
-    'logic_autolock_robust_wait_for_20': (8, 0x0f2, 14, True),
-    'logic_autolock_robust_wait_for_21': (8, 0x0f4, 14, True),
-    'logic_autolock_robust_wait_for_22': (8, 0x0f6, 14, True),
-    'logic_autolock_robust_wait_for_23': (8, 0x0f8, 14, True),
-    'logic_autolock_robust_wait_for_24': (8, 0x0fa, 14, True),
-    'logic_autolock_robust_wait_for_25': (8, 0x0fc, 14, True),
-    'logic_autolock_robust_wait_for_26': (8, 0x0fe, 14, True),
-    'logic_autolock_robust_wait_for_27': (8, 0x100, 14, True),
-    'logic_autolock_robust_wait_for_28': (8, 0x102, 14, True),
-    'logic_autolock_robust_wait_for_29': (8, 0x104, 14, True),
-    'logic_autolock_robust_wait_for_30': (8, 0x106, 14, True),
-    'logic_autolock_robust_wait_for_31': (8, 0x108, 14, True),
-    'logic_autolock_fast_target_position': (8, 0x10a, 14, True),
-    'logic_autolock_request_lock': (8, 0x10c, 1, True),
-    'logic_autolock_autolock_mode': (8, 0x10d, 2, True),
-    'logic_autolock_lock_running': (8, 0x10e, 1, False),
-    'logic_raw_acquisition_iir_z0': (8, 0x10f, 27, True),
-    'logic_raw_acquisition_iir_a1': (8, 0x113, 25, True),
-    'logic_raw_acquisition_iir_a2': (8, 0x117, 25, True),
-    'logic_raw_acquisition_iir_a3': (8, 0x11b, 25, True),
-    'logic_raw_acquisition_iir_a4': (8, 0x11f, 25, True),
-    'logic_raw_acquisition_iir_a5': (8, 0x123, 25, True),
-    'logic_raw_acquisition_iir_b0': (8, 0x127, 25, True),
-    'logic_raw_acquisition_iir_b1': (8, 0x12b, 25, True),
-    'logic_raw_acquisition_iir_b2': (8, 0x12f, 25, True),
-    'logic_raw_acquisition_iir_b3': (8, 0x133, 25, True),
-    'logic_raw_acquisition_iir_b4': (8, 0x137, 25, True),
-    'logic_raw_acquisition_iir_b5': (8, 0x13b, 25, True),
-    'logic_control_signal_clr': (8, 0x13f, 1, True),
-    'logic_control_signal_max': (8, 0x140, 25, False),
-    'logic_control_signal_min': (8, 0x144, 25, False),
-    'logic_combined_error_signal_clr': (8, 0x148, 1, True),
-    'logic_combined_error_signal_max': (8, 0x149, 25, False),
-    'logic_combined_error_signal_min': (8, 0x14d, 25, False),
-    'logic_combined_error_signal_filtered_clr': (8, 0x151, 1, True),
-    'logic_combined_error_signal_filtered_max': (8, 0x152, 25, False),
-    'logic_combined_error_signal_filtered_min': (8, 0x156, 25, False),
+    'logic_mod_channel': (8, 0x001, 1, True),
+    'logic_control_channel': (8, 0x002, 1, True),
+    'logic_sweep_channel': (8, 0x003, 2, True),
+    'logic_control_slow_channel': (8, 0x004, 2, True),
+    'logic_fast_mode': (8, 0x005, 1, True),
+    'logic_chain_a_factor': (8, 0x006, 9, True),
+    'logic_chain_b_factor': (8, 0x008, 9, True),
+    'logic_chain_a_offset': (8, 0x00a, 14, True),
+    'logic_chain_b_offset': (8, 0x00c, 14, True),
+    'logic_combined_offset': (8, 0x00e, 14, True),
+    'logic_out_offset': (8, 0x010, 14, True),
+    'logic_slow_decimation': (8, 0x012, 5, True),
+    'logic_analog_out_1': (8, 0x013, 15, True),
+    'logic_analog_out_2': (8, 0x015, 15, True),
+    'logic_analog_out_3': (8, 0x017, 15, True),
+    'logic_slow_value': (8, 0x019, 14, False),
+    'logic_mod_amp': (8, 0x01b, 14, True),
+    'logic_mod_freq': (8, 0x01d, 32, True),
+    'logic_sweep_step': (8, 0x021, 30, True),
+    'logic_sweep_min': (8, 0x025, 14, True),
+    'logic_sweep_max': (8, 0x027, 14, True),
+    'logic_sweep_run': (8, 0x029, 1, True),
+    'logic_sweep_pause': (8, 0x02a, 1, True),
+    'logic_limit_error_signal_min': (8, 0x02b, 25, True),
+    'logic_limit_error_signal_max': (8, 0x02f, 25, True),
+    'logic_limit_fast1_min': (8, 0x033, 14, True),
+    'logic_limit_fast1_max': (8, 0x035, 14, True),
+    'logic_limit_fast2_min': (8, 0x037, 14, True),
+    'logic_limit_fast2_max': (8, 0x039, 14, True),
+    'logic_pid_setpoint': (8, 0x03b, 25, True),
+    'logic_pid_kp': (8, 0x03f, 14, True),
+    'logic_pid_ki': (8, 0x041, 14, True),
+    'logic_pid_reset': (8, 0x043, 1, True),
+    'logic_pid_kd': (8, 0x044, 14, True),
+    'logic_autolock_robust_time_scale': (8, 0x046, 14, True),
+    'logic_autolock_robust_N_instructions': (8, 0x048, 5, True),
+    'logic_autolock_robust_final_wait_time': (8, 0x049, 14, True),
+    'logic_autolock_robust_peak_height_0': (8, 0x04b, 28, True),
+    'logic_autolock_robust_peak_height_1': (8, 0x04f, 28, True),
+    'logic_autolock_robust_peak_height_2': (8, 0x053, 28, True),
+    'logic_autolock_robust_peak_height_3': (8, 0x057, 28, True),
+    'logic_autolock_robust_peak_height_4': (8, 0x05b, 28, True),
+    'logic_autolock_robust_peak_height_5': (8, 0x05f, 28, True),
+    'logic_autolock_robust_peak_height_6': (8, 0x063, 28, True),
+    'logic_autolock_robust_peak_height_7': (8, 0x067, 28, True),
+    'logic_autolock_robust_peak_height_8': (8, 0x06b, 28, True),
+    'logic_autolock_robust_peak_height_9': (8, 0x06f, 28, True),
+    'logic_autolock_robust_peak_height_10': (8, 0x073, 28, True),
+    'logic_autolock_robust_peak_height_11': (8, 0x077, 28, True),
+    'logic_autolock_robust_peak_height_12': (8, 0x07b, 28, True),
+    'logic_autolock_robust_peak_height_13': (8, 0x07f, 28, True),
+    'logic_autolock_robust_peak_height_14': (8, 0x083, 28, True),
+    'logic_autolock_robust_peak_height_15': (8, 0x087, 28, True),
+    'logic_autolock_robust_peak_height_16': (8, 0x08b, 28, True),
+    'logic_autolock_robust_peak_height_17': (8, 0x08f, 28, True),
+    'logic_autolock_robust_peak_height_18': (8, 0x093, 28, True),
+    'logic_autolock_robust_peak_height_19': (8, 0x097, 28, True),
+    'logic_autolock_robust_peak_height_20': (8, 0x09b, 28, True),
+    'logic_autolock_robust_peak_height_21': (8, 0x09f, 28, True),
+    'logic_autolock_robust_peak_height_22': (8, 0x0a3, 28, True),
+    'logic_autolock_robust_peak_height_23': (8, 0x0a7, 28, True),
+    'logic_autolock_robust_peak_height_24': (8, 0x0ab, 28, True),
+    'logic_autolock_robust_peak_height_25': (8, 0x0af, 28, True),
+    'logic_autolock_robust_peak_height_26': (8, 0x0b3, 28, True),
+    'logic_autolock_robust_peak_height_27': (8, 0x0b7, 28, True),
+    'logic_autolock_robust_peak_height_28': (8, 0x0bb, 28, True),
+    'logic_autolock_robust_peak_height_29': (8, 0x0bf, 28, True),
+    'logic_autolock_robust_peak_height_30': (8, 0x0c3, 28, True),
+    'logic_autolock_robust_peak_height_31': (8, 0x0c7, 28, True),
+    'logic_autolock_robust_wait_for_0': (8, 0x0cb, 14, True),
+    'logic_autolock_robust_wait_for_1': (8, 0x0cd, 14, True),
+    'logic_autolock_robust_wait_for_2': (8, 0x0cf, 14, True),
+    'logic_autolock_robust_wait_for_3': (8, 0x0d1, 14, True),
+    'logic_autolock_robust_wait_for_4': (8, 0x0d3, 14, True),
+    'logic_autolock_robust_wait_for_5': (8, 0x0d5, 14, True),
+    'logic_autolock_robust_wait_for_6': (8, 0x0d7, 14, True),
+    'logic_autolock_robust_wait_for_7': (8, 0x0d9, 14, True),
+    'logic_autolock_robust_wait_for_8': (8, 0x0db, 14, True),
+    'logic_autolock_robust_wait_for_9': (8, 0x0dd, 14, True),
+    'logic_autolock_robust_wait_for_10': (8, 0x0df, 14, True),
+    'logic_autolock_robust_wait_for_11': (8, 0x0e1, 14, True),
+    'logic_autolock_robust_wait_for_12': (8, 0x0e3, 14, True),
+    'logic_autolock_robust_wait_for_13': (8, 0x0e5, 14, True),
+    'logic_autolock_robust_wait_for_14': (8, 0x0e7, 14, True),
+    'logic_autolock_robust_wait_for_15': (8, 0x0e9, 14, True),
+    'logic_autolock_robust_wait_for_16': (8, 0x0eb, 14, True),
+    'logic_autolock_robust_wait_for_17': (8, 0x0ed, 14, True),
+    'logic_autolock_robust_wait_for_18': (8, 0x0ef, 14, True),
+    'logic_autolock_robust_wait_for_19': (8, 0x0f1, 14, True),
+    'logic_autolock_robust_wait_for_20': (8, 0x0f3, 14, True),
+    'logic_autolock_robust_wait_for_21': (8, 0x0f5, 14, True),
+    'logic_autolock_robust_wait_for_22': (8, 0x0f7, 14, True),
+    'logic_autolock_robust_wait_for_23': (8, 0x0f9, 14, True),
+    'logic_autolock_robust_wait_for_24': (8, 0x0fb, 14, True),
+    'logic_autolock_robust_wait_for_25': (8, 0x0fd, 14, True),
+    'logic_autolock_robust_wait_for_26': (8, 0x0ff, 14, True),
+    'logic_autolock_robust_wait_for_27': (8, 0x101, 14, True),
+    'logic_autolock_robust_wait_for_28': (8, 0x103, 14, True),
+    'logic_autolock_robust_wait_for_29': (8, 0x105, 14, True),
+    'logic_autolock_robust_wait_for_30': (8, 0x107, 14, True),
+    'logic_autolock_robust_wait_for_31': (8, 0x109, 14, True),
+    'logic_autolock_fast_target_position': (8, 0x10b, 14, True),
+    'logic_autolock_request_lock': (8, 0x10d, 1, True),
+    'logic_autolock_autolock_mode': (8, 0x10e, 2, True),
+    'logic_autolock_lock_running': (8, 0x10f, 1, False),
+    'logic_raw_acquisition_iir_z0': (8, 0x110, 27, True),
+    'logic_raw_acquisition_iir_a1': (8, 0x114, 25, True),
+    'logic_raw_acquisition_iir_a2': (8, 0x118, 25, True),
+    'logic_raw_acquisition_iir_a3': (8, 0x11c, 25, True),
+    'logic_raw_acquisition_iir_a4': (8, 0x120, 25, True),
+    'logic_raw_acquisition_iir_a5': (8, 0x124, 25, True),
+    'logic_raw_acquisition_iir_b0': (8, 0x128, 25, True),
+    'logic_raw_acquisition_iir_b1': (8, 0x12c, 25, True),
+    'logic_raw_acquisition_iir_b2': (8, 0x130, 25, True),
+    'logic_raw_acquisition_iir_b3': (8, 0x134, 25, True),
+    'logic_raw_acquisition_iir_b4': (8, 0x138, 25, True),
+    'logic_raw_acquisition_iir_b5': (8, 0x13c, 25, True),
+    'logic_control_signal_clr': (8, 0x140, 1, True),
+    'logic_control_signal_max': (8, 0x141, 25, False),
+    'logic_control_signal_min': (8, 0x145, 25, False),
+    'logic_combined_error_signal_clr': (8, 0x149, 1, True),
+    'logic_combined_error_signal_max': (8, 0x14a, 25, False),
+    'logic_combined_error_signal_min': (8, 0x14e, 25, False),
+    'logic_combined_error_signal_filtered_clr': (8, 0x152, 1, True),
+    'logic_combined_error_signal_filtered_max': (8, 0x153, 25, False),
+    'logic_combined_error_signal_filtered_min': (8, 0x157, 25, False),
     'scopegen_external_trigger': (6, 0x000, 1, True),
     'scopegen_dac_a_clr': (6, 0x001, 1, True),
     'scopegen_dac_a_max': (6, 0x002, 25, False),
@@ -296,16 +297,16 @@
     'scopegen_adc_b_sel': (6, 0x014, 4, True),
     'scopegen_adc_a_q_sel': (6, 0x015, 4, True),
     'scopegen_adc_b_q_sel': (6, 0x016, 4, True),
-    'slow_pid_setpoint': (2, 0x000, 14, True),
-    'slow_pid_kp': (2, 0x002, 14, True),
-    'slow_pid_ki': (2, 0x004, 14, True),
-    'slow_pid_reset': (2, 0x006, 1, True),
-    'slow_pid_kd': (2, 0x007, 14, True),
-    'slow_limit_min': (2, 0x009, 14, True),
-    'slow_limit_max': (2, 0x00b, 14, True),
-    'slow_out_clr': (2, 0x00d, 1, True),
-    'slow_out_max': (2, 0x00e, 25, False),
-    'slow_out_min': (2, 0x012, 25, False),
+    'slow_chain_pid_setpoint': (2, 0x000, 14, True),
+    'slow_chain_pid_kp': (2, 0x002, 14, True),
+    'slow_chain_pid_ki': (2, 0x004, 14, True),
+    'slow_chain_pid_reset': (2, 0x006, 1, True),
+    'slow_chain_pid_kd': (2, 0x007, 14, True),
+    'slow_chain_limit_min': (2, 0x009, 14, True),
+    'slow_chain_limit_max': (2, 0x00b, 14, True),
+    'slow_chain_out_clr': (2, 0x00d, 1, True),
+    'slow_chain_out_max': (2, 0x00e, 25, False),
+    'slow_chain_out_min': (2, 0x012, 25, False),
     'xadc_temp': (29, 0x000, 12, False),
     'xadc_v': (29, 0x002, 12, False),
     'xadc_a': (29, 0x004, 12, False),
@@ -313,5 +314,5 @@
     'xadc_c': (29, 0x008, 12, False),
     'xadc_d': (29, 0x00a, 12, False),
 }
-states = ['force', 'di0', 'di1', 'di2', 'di3', 'di4', 'di5', 'di6', 'di7', 'robust_watching', 'robust_turn_on_lock', 'robust_sign_equal', 'robust_over_threshold', 'robust_waited_long_enough']
-signals = ['zero', 'fast_a_x', 'fast_a_out_i', 'fast_a_out_q', 'fast_b_x', 'fast_b_out_i', 'fast_b_out_q', 'slow_out', 'scopegen_dac_a', 'scopegen_dac_b', 'logic_control_signal', 'logic_combined_error_signal', 'logic_combined_error_signal_filtered']
+states = ['force', 'di0', 'di1', 'di2', 'di3', 'di4', 'di5', 'di6', 'di7', 'robustwatching', 'robustturn_on_lock', 'robustsign_equal', 'robustover_threshold', 'robustwaited_long_enough']
+signals = ['zero', 'fast_a_x', 'fast_a_out_i', 'fast_a_out_q', 'fast_b_x', 'fast_b_out_i', 'fast_b_out_q', 'slow_chain_out', 'scopegen_dac_a', 'scopegen_dac_b', 'logic_control_signal', 'logic_combined_error_signal', 'logic_combined_error_signal_filtered']
diff --git a/linien-server/linien_server/linien.bin b/linien-server/linien_server/linien.bin
index 4215d4d3..2ea0fce8 100644
--- a/linien-server/linien_server/linien.bin
+++ b/linien-server/linien_server/linien.bin
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:2c4ff7212650d761391d5a52af4dc4b51a70d1832491a9d783df2b7c021f8528
+oid sha256:71f871bc3c53ad1a8349838c72d65c750b8047285e008009f2a16dee0f02709a
 size 2083740
diff --git a/linien-server/linien_server/parameters.py b/linien-server/linien_server/parameters.py
index 2089a23b..4f65a0e5 100644
--- a/linien-server/linien_server/parameters.py
+++ b/linien-server/linien_server/parameters.py
@@ -1,5 +1,6 @@
 # Copyright 2018-2022 Benjamin Wiegand <benjamin.wiegand@physik.hu-berlin.de>
 # Copyright 2021-2022 Bastian Leykauf <leykauf@physik.hu-berlin.de>
+# Copyright 2023 Christian Freier <christian.freier@nomadatomics.com>
 #
 # This file is part of Linien and based on redpid.
 #
@@ -94,6 +95,7 @@ def __init__(self):
             "mod_channel",
             "control_channel",
             "sweep_channel",
+            "control_slow_channel",
             "polarity_fast_out1",
             "polarity_fast_out2",
             "polarity_analog_out0",
diff --git a/linien-server/linien_server/registers.py b/linien-server/linien_server/registers.py
index a6a09a3b..c573c71a 100644
--- a/linien-server/linien_server/registers.py
+++ b/linien-server/linien_server/registers.py
@@ -1,5 +1,6 @@
 # Copyright 2018-2022 Benjamin Wiegand <benjamin.wiegand@physik.hu-berlin.de>
-# Copyright 2021-2022 Bastian Leykauf <leykauf@physik.hu-berlin.de>
+# Copyright 2021-2023 Bastian Leykauf <leykauf@physik.hu-berlin.de>
+# Copyright 2022 Christian Freier <christian.freier@nomadatomics.com>
 #
 # This file is part of Linien and based on redpid.
 #
@@ -18,7 +19,6 @@
 
 import numpy as np
 from linien_common.common import (
-    ANALOG_OUT0,
     HIGH_PASS_FILTER,
     LOW_PASS_FILTER,
     MHz,
@@ -33,7 +33,8 @@
 
 
 class Registers:
-    """This class provides low-level access to the FPGA registers.
+    """
+    This class provides low-level access to the FPGA registers.
 
     High-level applications should not access this class directly but instead
     communicate by manipulating `Parameters` / `RemoteParameters`.
@@ -79,11 +80,6 @@ def dual_channel_changed(dual_channel):
         use_ssh = self.host is not None and self.host not in ("localhost", "127.0.0.1")
         self.acquisition = AcquisitionMaster(use_ssh, self.host)
 
-    def run_data_acquisition(self, on_change):
-        """Starts a background process that continuously reads out error /
-        control signal of the FPGA. For every result, `on_change` is called."""
-        self.acquisition.run_data_acquisition(on_change)
-
     def write_registers(self):
         """Writes data from `parameters` to the FPGA."""
         params = dict(self.parameters)
@@ -131,7 +127,8 @@ def write_registers(self):
             logic_control_channel=params["control_channel"],
             logic_mod_channel=params["mod_channel"],
             logic_sweep_channel=params["sweep_channel"],
-            slow_pid_reset=not params["pid_on_slow_enabled"],
+            logic_control_slow_channel=params["control_slow_channel"],
+            slow_chain_pid_reset=not params["pid_on_slow_enabled"],
             logic_analog_out_1=params["analog_out_1"],
             logic_analog_out_2=params["analog_out_2"],
             logic_analog_out_3=params["analog_out_3"],
@@ -236,7 +233,7 @@ def write_registers(self):
             "logic_raw_acquisition_iir",
             *make_filter(
                 "LP", f=params["acquisition_raw_filter_frequency"] / fpga_base_freq, k=1
-            )
+            ),
         )
 
         for k, v in new.items():
@@ -253,9 +250,10 @@ def write_registers(self):
         ki = params["i"]
         kd = params["d"]
         slope = params["target_slope_rising"]
-        control_channel, sweep_channel = (
+        control_channel, sweep_channel, control_slow_channel = (
             params["control_channel"],
             params["sweep_channel"],
+            params["control_slow_channel"],
         )
 
         def channel_polarity(channel):
@@ -274,7 +272,8 @@ def channel_polarity(channel):
         )
         slow_slope = (
             1
-            if channel_polarity(ANALOG_OUT0) == channel_polarity(control_channel)
+            if channel_polarity(control_slow_channel)
+            == channel_polarity(control_channel)
             else -1
         )
 
@@ -321,14 +320,14 @@ def channel_polarity(channel):
                                 iir_name,
                                 *make_filter(
                                     "LP", f=filter_frequency / fpga_base_freq, k=1
-                                )
+                                ),
                             )
                         elif filter_type == HIGH_PASS_FILTER:
                             self.set_iir(
                                 iir_name,
                                 *make_filter(
                                     "HP", f=filter_frequency / fpga_base_freq, k=1
-                                )
+                                ),
                             )
                         else:
                             raise Exception(
@@ -363,10 +362,10 @@ def set_pid(self, p, i, d, slope, reset=None, request_lock=None):
 
     def set_slow_pid(self, strength, slope, reset=None):
         sign = slope
-        self.set("slow_pid_ki", strength * sign)
+        self.set("slow_chain_pid_ki", strength * sign)
 
         if reset is not None:
-            self.set("slow_pid_reset", reset)
+            self.set("slow_chain_pid_reset", reset)
 
     def set(self, key, value):
         self.acquisition.set_csr(key, value)
diff --git a/linien-server/linien_server/server.py b/linien-server/linien_server/server.py
index 11a75d4c..0868e8eb 100644
--- a/linien-server/linien_server/server.py
+++ b/linien-server/linien_server/server.py
@@ -1,5 +1,5 @@
 # Copyright 2018-2022 Benjamin Wiegand <benjamin.wiegand@physik.hu-berlin.de>
-# Copyright 2021-2022 Bastian Leykauf <leykauf@physik.hu-berlin.de>
+# Copyright 2021-2023 Bastian Leykauf <leykauf@physik.hu-berlin.de>
 #
 # This file is part of Linien and based on redpid.
 #
@@ -49,8 +49,10 @@
 
 
 class BaseService(rpyc.Service):
-    """A service that provides functionality for seamless integration of
-    parameter access on the client."""
+    """
+    A service that provides functionality for seamless integration of parameter access
+    on the client.
+    """
 
     def __init__(self):
         self.parameters = Parameters()
@@ -102,7 +104,7 @@ def run_acquiry_loop(self):
         """Starts a background process that keeps polling control and error
         signal. Every received value is pushed to `parameters.to_plot`."""
 
-        def data_received(is_raw, plot_data, data_uuid):
+        def on_new_data_received(is_raw, plot_data, data_uuid):
             # When a parameter is changed, `pause_acquisition` is set.
             # This means that the we should skip new data until we are sure that
             # it was recorded with the new settings.
@@ -138,14 +140,16 @@ def data_received(is_raw, plot_data, data_uuid):
                 else:
                     self.parameters.acquisition_raw_data.value = plot_data
 
-        self.registers.run_data_acquisition(data_received)
+        # each time new data is acquired, this function is called
+        self.registers.acquisition.on_new_data_received = on_new_data_received
         self.pause_acquisition()
         self.continue_acquisition()
 
     def run_periodic_timer(self):
-        """Starts a timer that increases the `ping` parameter once per second.
-        Its purpose is to allow for periodic tasks on the server:
-        just register an `on_change` listener for this parameter.
+        """
+        Start a timer that increases the `ping` parameter once per second. Its purpose
+        is to allow for periodic tasks on the server: just register an `on_change`
+        listener for this parameter.
         """
 
         def send_ping():
@@ -154,10 +158,10 @@ def send_ping():
                 print("ping", self.parameters.ping.value)
                 sleep(1)
 
-        t = threading.Thread(target=send_ping)
-        t.daemon = True
-        t.start()
-        self._periodic_timer = t
+        thread = threading.Thread(target=send_ping)
+        thread.daemon = True
+        thread.start()
+        self._periodic_timer = thread
 
     def _generate_signal_stats(self, to_plot):
         stats = {}
@@ -171,7 +175,7 @@ def _generate_signal_stats(self, to_plot):
         self.parameters.signal_stats.value = stats
 
     def exposed_write_registers(self):
-        """Syncs the parameters with the FPGA registers."""
+        """Sync the parameters with the FPGA registers."""
         self.registers.write_registers()
 
     def task_running(self):


Test patch:
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
index 8875a0c2..f27df02f 100644
--- a/.github/workflows/pytest.yml
+++ b/.github/workflows/pytest.yml
@@ -1,6 +1,7 @@
 name: Run PyTest
 
 on:
+  workflow_dispatch:
   push:
     paths:
       - gateware/**