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

Issue:
Add ability to output slow PID on fast DACs 
Based changes of original PR #284 on develop branch

Remaining issues:
* [x] when disabling slow_PID, the GUI element flips to "ANALOG_OUT0" (slow_PID output is off, though)
* [x] slow history is only shown for ANALOG_OUT0


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/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index eb2be176..589c5da8 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -1,4 +1,4 @@
-name: PublishPackages
+name: Publish Packages
 
 on:
   workflow_dispatch:
diff --git a/.github/workflows/pyinstaller-windows.yml b/.github/workflows/pyinstaller-windows.yml
index 1aabe0f2..7fc3438e 100644
--- a/.github/workflows/pyinstaller-windows.yml
+++ b/.github/workflows/pyinstaller-windows.yml
@@ -2,6 +2,11 @@ name: Pyinstaller Windows
 
 on:
   workflow_dispatch:
+  push:
+    paths:
+      - linien-common/**
+      - linien-client/**
+      - linien-gui/**
 
 jobs:
   build:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index af8e7c59..e780d054 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,7 +3,7 @@ repos:
     rev: 23.1.0
     hooks:
       - id: black
-        exclude: ^(gateware/logic/|gateware/lowlevel/|linien/server/csrmap.py)
+        exclude: ^(gateware/logic/|gateware/lowlevel/|gateware/linien_module.py|linien-server/linien_server/csrmap.py)
 
   - repo: https://github.com/pycqa/isort
     rev: 5.12.0
diff --git a/CITATION.cff b/CITATION.cff
new file mode 100644
index 00000000..cdb057a3
--- /dev/null
+++ b/CITATION.cff
@@ -0,0 +1,54 @@
+cff-version: 1.2.0
+message: If you are using Linien as part of a scientific publication, please cite our article.
+authors:
+  - given-names: Benjamin
+    family-names: Wiegand
+    email: bwiegand@physik.hu-berlin.de
+    affiliation: Institut für Physik, Humboldt-Universität zu Berlin
+    orcid: "https://orcid.org/0000-0001-6549-2307"
+  - family-names: Leykauf
+    given-names: Bastian
+    email: leykauf@physik.hu-berlin.de
+    affiliation: Institut für Physik, Humboldt-Universität zu Berlin
+    orcid: "https://orcid.org/0000-0002-4630-0602"
+  - family-names: Jördens
+    given-names: Robert
+    affiliation: '"QUARTIQ GmbH'
+    orcid: "https://orcid.org/0000-0001-5333-0562"
+preferred-citation:
+  type: article
+  title: >-
+    Linien: A versatile, user-friendly, open-source FPGA-based tool for frequency
+    stabilization and spectroscopy parameter optimization
+  authors:
+    - given-names: Benjamin
+      family-names: Wiegand
+      email: bwiegand@physik.hu-berlin.de
+      affiliation: >-
+        Institut für Physik, Humboldt-Universität zu
+        Berlin
+      orcid: "https://orcid.org/0000-0001-6549-2307"
+    - family-names: Leykauf
+      given-names: Bastian
+      email: leykauf@physik.hu-berlin.de
+      affiliation: >-
+        Institut für Physik, Humboldt-Universität zu
+        Berlin
+      orcid: "https://orcid.org/0000-0002-4630-0602"
+    - family-names: Jördens
+      given-names: Robert
+      affiliation: '"QUARTIQ GmbH'
+      orcid: "https://orcid.org/0000-0001-5333-0562"
+    - family-names: Krutzik
+      given-names: Markus
+      affiliation: >-
+        Institut für Physik, Humboldt-Universität zu
+        Berlin & Ferdinand-Braun-Institut gGmbH,
+        Leibniz-Institut für Höchstfrequenztechnik
+  doi: "10.1063/5.0090384"
+  url: "https://doi.org/10.1063/5.0090384"
+  journal: "Review of Scientific Instruments"
+  volume: 93
+  issue: 6
+  start: "063001"
+  year: 2022
diff --git a/README.md b/README.md
index 2fba62aa..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)
 
@@ -350,7 +350,7 @@ except:
 Updating Linien
 ---------------
 
-Before installing a new version of Linien, open the previously installed client and click the "Shutdown server" button. Don't worry, your settings and parameters will be saved. Then you may install the latest client on your local PC as described in the [getting started](#getting-started) section above. The next time you connect to RedPitaya, Linien will install the matching server version.
+Before installing a new version of Linien, open the previously installed client and click the "Shutdown server" button. Don't worry, your settings and parameters will be saved. Then you may install the latest client on your local PC as described in the [getting started](#getting-started-install-linien) section above. The next time you connect to RedPitaya, Linien will install the matching server version.
 
 
 Development
@@ -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/build_fpga_image.sh b/gateware/build_fpga_image.sh
index e8486544..7371162d 100644
--- a/gateware/build_fpga_image.sh
+++ b/gateware/build_fpga_image.sh
@@ -15,4 +15,5 @@ fi
 export PATH=$VIVADOPATH:$PATH
 
 rm linien-server/linien_server/linien.bin -f
-python3 gateware/fpga_image_helper.py
\ No newline at end of file
+# run with -m option to avoid errors related to relative imports without breaking pytest
+python3 -m gateware.fpga_image_helper
\ No newline at end of file
diff --git a/gateware/fpga_image_helper.py b/gateware/fpga_image_helper.py
index b50b1559..686922a3 100644
--- a/gateware/fpga_image_helper.py
+++ b/gateware/fpga_image_helper.py
@@ -22,9 +22,9 @@
 
 REPO_ROOT_DIR = Path(__file__).resolve().parents[1]
 
-from bit2bin import bit2bin
-from hw_platform import Platform
-from linien_module import RootModule
+from .bit2bin import bit2bin
+from .hw_platform import Platform
+from .linien_module import RootModule
 
 
 def py_csrconstants(map, fil):
diff --git a/gateware/linien_module.py b/gateware/linien_module.py
index 290b819b..1abacd5c 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.slow_control_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.slow_control_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.slow_control_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/__init__.py b/gateware/logic/__init__.py
new file mode 100644
index 00000000..e69de29b
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/gateware/lowlevel/__init__.py b/gateware/lowlevel/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/linien-client/setup.py b/linien-client/setup.py
index 4f01d11b..d2b0174f 100644
--- a/linien-client/setup.py
+++ b/linien-client/setup.py
@@ -18,7 +18,7 @@
 
 from setuptools import find_packages, setup
 
-version = "0.6.0"
+version = "0.7.0"
 
 setup(
     name="linien-client",
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-common/setup.py b/linien-common/setup.py
index da92a79e..ecba2cde 100644
--- a/linien-common/setup.py
+++ b/linien-common/setup.py
@@ -19,7 +19,7 @@
 
 setup(
     name="linien-common",
-    version="0.6.0",
+    version="0.7.0",
     author="Benjamin Wiegand",
     author_email="highwaychile@posteo.de",
     maintainer="Bastian Leykauf",
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..9b569cc2 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.slow_control_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.slow_control_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.slow_control_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.slow_control_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-gui/setup.py b/linien-gui/setup.py
index 4fb93d5f..4178bd75 100644
--- a/linien-gui/setup.py
+++ b/linien-gui/setup.py
@@ -18,8 +18,7 @@
 
 from setuptools import find_packages, setup
 
-version = "0.6.0"
-
+version = "0.7.0"
 
 setup(
     name="linien-gui",
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 efd3b64e..01d5fe20 100644
--- a/linien-server/linien_server/csrmap.py
+++ b/linien-server/linien_server/csrmap.py
@@ -1,346 +1,318 @@
 csr_constants = {
-    "fast_a_iir_c_1_shift": 23,
-    "fast_a_iir_c_1_width": 25,
-    "fast_a_iir_c_1_interval": 1,
-    "fast_a_iir_c_1_latency": 2,
-    "fast_a_iir_c_1_order": 1,
-    "fast_a_iir_c_1_iterative": 0,
-    "fast_a_iir_d_1_shift": 23,
-    "fast_a_iir_d_1_width": 25,
-    "fast_a_iir_d_1_interval": 1,
-    "fast_a_iir_d_1_latency": 3,
-    "fast_a_iir_d_1_order": 2,
-    "fast_a_iir_d_1_iterative": 0,
-    "fast_a_iir_c_2_shift": 23,
-    "fast_a_iir_c_2_width": 25,
-    "fast_a_iir_c_2_interval": 1,
-    "fast_a_iir_c_2_latency": 2,
-    "fast_a_iir_c_2_order": 1,
-    "fast_a_iir_c_2_iterative": 0,
-    "fast_a_iir_d_2_shift": 23,
-    "fast_a_iir_d_2_width": 25,
-    "fast_a_iir_d_2_interval": 1,
-    "fast_a_iir_d_2_latency": 3,
-    "fast_a_iir_d_2_order": 2,
-    "fast_a_iir_d_2_iterative": 0,
-    "fast_b_iir_c_1_shift": 23,
-    "fast_b_iir_c_1_width": 25,
-    "fast_b_iir_c_1_interval": 1,
-    "fast_b_iir_c_1_latency": 2,
-    "fast_b_iir_c_1_order": 1,
-    "fast_b_iir_c_1_iterative": 0,
-    "fast_b_iir_d_1_shift": 23,
-    "fast_b_iir_d_1_width": 25,
-    "fast_b_iir_d_1_interval": 1,
-    "fast_b_iir_d_1_latency": 3,
-    "fast_b_iir_d_1_order": 2,
-    "fast_b_iir_d_1_iterative": 0,
-    "fast_b_iir_c_2_shift": 23,
-    "fast_b_iir_c_2_width": 25,
-    "fast_b_iir_c_2_interval": 1,
-    "fast_b_iir_c_2_latency": 2,
-    "fast_b_iir_c_2_order": 1,
-    "fast_b_iir_c_2_iterative": 0,
-    "fast_b_iir_d_2_shift": 23,
-    "fast_b_iir_d_2_width": 25,
-    "fast_b_iir_d_2_interval": 1,
-    "fast_b_iir_d_2_latency": 3,
-    "fast_b_iir_d_2_order": 2,
-    "fast_b_iir_d_2_iterative": 0,
-    "logic_raw_acquisition_iir_shift": 23,
-    "logic_raw_acquisition_iir_width": 25,
-    "logic_raw_acquisition_iir_interval": 1,
-    "logic_raw_acquisition_iir_latency": 6,
-    "logic_raw_acquisition_iir_order": 5,
-    "logic_raw_acquisition_iir_iterative": 0,
+    'fast_a_iir_c_1_shift': 23,
+    'fast_a_iir_c_1_width': 25,
+    'fast_a_iir_c_1_interval': 1,
+    'fast_a_iir_c_1_latency': 2,
+    'fast_a_iir_c_1_order': 1,
+    'fast_a_iir_c_1_iterative': 0,
+    'fast_a_iir_d_1_shift': 23,
+    'fast_a_iir_d_1_width': 25,
+    'fast_a_iir_d_1_interval': 1,
+    'fast_a_iir_d_1_latency': 3,
+    'fast_a_iir_d_1_order': 2,
+    'fast_a_iir_d_1_iterative': 0,
+    'fast_a_iir_c_2_shift': 23,
+    'fast_a_iir_c_2_width': 25,
+    'fast_a_iir_c_2_interval': 1,
+    'fast_a_iir_c_2_latency': 2,
+    'fast_a_iir_c_2_order': 1,
+    'fast_a_iir_c_2_iterative': 0,
+    'fast_a_iir_d_2_shift': 23,
+    'fast_a_iir_d_2_width': 25,
+    'fast_a_iir_d_2_interval': 1,
+    'fast_a_iir_d_2_latency': 3,
+    'fast_a_iir_d_2_order': 2,
+    'fast_a_iir_d_2_iterative': 0,
+    'fast_b_iir_c_1_shift': 23,
+    'fast_b_iir_c_1_width': 25,
+    'fast_b_iir_c_1_interval': 1,
+    'fast_b_iir_c_1_latency': 2,
+    'fast_b_iir_c_1_order': 1,
+    'fast_b_iir_c_1_iterative': 0,
+    'fast_b_iir_d_1_shift': 23,
+    'fast_b_iir_d_1_width': 25,
+    'fast_b_iir_d_1_interval': 1,
+    'fast_b_iir_d_1_latency': 3,
+    'fast_b_iir_d_1_order': 2,
+    'fast_b_iir_d_1_iterative': 0,
+    'fast_b_iir_c_2_shift': 23,
+    'fast_b_iir_c_2_width': 25,
+    'fast_b_iir_c_2_interval': 1,
+    'fast_b_iir_c_2_latency': 2,
+    'fast_b_iir_c_2_order': 1,
+    'fast_b_iir_c_2_iterative': 0,
+    'fast_b_iir_d_2_shift': 23,
+    'fast_b_iir_d_2_width': 25,
+    'fast_b_iir_d_2_interval': 1,
+    'fast_b_iir_d_2_latency': 3,
+    'fast_b_iir_d_2_order': 2,
+    'fast_b_iir_d_2_iterative': 0,
+    'logic_raw_acquisition_iir_shift': 23,
+    'logic_raw_acquisition_iir_width': 25,
+    'logic_raw_acquisition_iir_interval': 1,
+    'logic_raw_acquisition_iir_latency': 6,
+    'logic_raw_acquisition_iir_order': 5,
+    'logic_raw_acquisition_iir_iterative': 0,
 }
 
 csr = {
-    "dna_dna": (28, 0x000, 64, False),
-    "fast_a_y_tap": (0, 0x000, 2, True),
-    "fast_a_invert": (0, 0x001, 1, True),
-    "fast_a_demod_delay": (0, 0x002, 32, True),
-    "fast_a_demod_multiplier": (0, 0x006, 4, True),
-    "fast_a_x_limit_1_min": (0, 0x007, 25, True),
-    "fast_a_x_limit_1_max": (0, 0x00B, 25, True),
-    "fast_a_iir_c_1_z0": (0, 0x00F, 27, True),
-    "fast_a_iir_c_1_a1": (0, 0x013, 25, True),
-    "fast_a_iir_c_1_b0": (0, 0x017, 25, True),
-    "fast_a_iir_c_1_b1": (0, 0x01B, 25, True),
-    "fast_a_iir_d_1_z0": (0, 0x01F, 27, True),
-    "fast_a_iir_d_1_a1": (0, 0x023, 25, True),
-    "fast_a_iir_d_1_a2": (0, 0x027, 25, True),
-    "fast_a_iir_d_1_b0": (0, 0x02B, 25, True),
-    "fast_a_iir_d_1_b1": (0, 0x02F, 25, True),
-    "fast_a_iir_d_1_b2": (0, 0x033, 25, True),
-    "fast_a_y_limit_1_min": (0, 0x037, 25, True),
-    "fast_a_y_limit_1_max": (0, 0x03B, 25, True),
-    "fast_a_x_limit_2_min": (0, 0x03F, 25, True),
-    "fast_a_x_limit_2_max": (0, 0x043, 25, True),
-    "fast_a_iir_c_2_z0": (0, 0x047, 27, True),
-    "fast_a_iir_c_2_a1": (0, 0x04B, 25, True),
-    "fast_a_iir_c_2_b0": (0, 0x04F, 25, True),
-    "fast_a_iir_c_2_b1": (0, 0x053, 25, True),
-    "fast_a_iir_d_2_z0": (0, 0x057, 27, True),
-    "fast_a_iir_d_2_a1": (0, 0x05B, 25, True),
-    "fast_a_iir_d_2_a2": (0, 0x05F, 25, True),
-    "fast_a_iir_d_2_b0": (0, 0x063, 25, True),
-    "fast_a_iir_d_2_b1": (0, 0x067, 25, True),
-    "fast_a_iir_d_2_b2": (0, 0x06B, 25, True),
-    "fast_a_y_limit_2_min": (0, 0x06F, 25, True),
-    "fast_a_y_limit_2_max": (0, 0x073, 25, True),
-    "fast_a_x_clr": (0, 0x077, 1, True),
-    "fast_a_x_max": (0, 0x078, 25, False),
-    "fast_a_x_min": (0, 0x07C, 25, False),
-    "fast_a_out_i_clr": (0, 0x080, 1, True),
-    "fast_a_out_i_max": (0, 0x081, 25, False),
-    "fast_a_out_i_min": (0, 0x085, 25, False),
-    "fast_a_out_q_clr": (0, 0x089, 1, True),
-    "fast_a_out_q_max": (0, 0x08A, 25, False),
-    "fast_a_out_q_min": (0, 0x08E, 25, False),
-    "fast_a_dx_sel": (0, 0x092, 4, True),
-    "fast_a_dy_sel": (0, 0x093, 4, True),
-    "fast_b_y_tap": (1, 0x000, 2, True),
-    "fast_b_invert": (1, 0x001, 1, True),
-    "fast_b_demod_delay": (1, 0x002, 32, True),
-    "fast_b_demod_multiplier": (1, 0x006, 4, True),
-    "fast_b_x_limit_1_min": (1, 0x007, 25, True),
-    "fast_b_x_limit_1_max": (1, 0x00B, 25, True),
-    "fast_b_iir_c_1_z0": (1, 0x00F, 27, True),
-    "fast_b_iir_c_1_a1": (1, 0x013, 25, True),
-    "fast_b_iir_c_1_b0": (1, 0x017, 25, True),
-    "fast_b_iir_c_1_b1": (1, 0x01B, 25, True),
-    "fast_b_iir_d_1_z0": (1, 0x01F, 27, True),
-    "fast_b_iir_d_1_a1": (1, 0x023, 25, True),
-    "fast_b_iir_d_1_a2": (1, 0x027, 25, True),
-    "fast_b_iir_d_1_b0": (1, 0x02B, 25, True),
-    "fast_b_iir_d_1_b1": (1, 0x02F, 25, True),
-    "fast_b_iir_d_1_b2": (1, 0x033, 25, True),
-    "fast_b_y_limit_1_min": (1, 0x037, 25, True),
-    "fast_b_y_limit_1_max": (1, 0x03B, 25, True),
-    "fast_b_x_limit_2_min": (1, 0x03F, 25, True),
-    "fast_b_x_limit_2_max": (1, 0x043, 25, True),
-    "fast_b_iir_c_2_z0": (1, 0x047, 27, True),
-    "fast_b_iir_c_2_a1": (1, 0x04B, 25, True),
-    "fast_b_iir_c_2_b0": (1, 0x04F, 25, True),
-    "fast_b_iir_c_2_b1": (1, 0x053, 25, True),
-    "fast_b_iir_d_2_z0": (1, 0x057, 27, True),
-    "fast_b_iir_d_2_a1": (1, 0x05B, 25, True),
-    "fast_b_iir_d_2_a2": (1, 0x05F, 25, True),
-    "fast_b_iir_d_2_b0": (1, 0x063, 25, True),
-    "fast_b_iir_d_2_b1": (1, 0x067, 25, True),
-    "fast_b_iir_d_2_b2": (1, 0x06B, 25, True),
-    "fast_b_y_limit_2_min": (1, 0x06F, 25, True),
-    "fast_b_y_limit_2_max": (1, 0x073, 25, True),
-    "fast_b_x_clr": (1, 0x077, 1, True),
-    "fast_b_x_max": (1, 0x078, 25, False),
-    "fast_b_x_min": (1, 0x07C, 25, False),
-    "fast_b_out_i_clr": (1, 0x080, 1, True),
-    "fast_b_out_i_max": (1, 0x081, 25, False),
-    "fast_b_out_i_min": (1, 0x085, 25, False),
-    "fast_b_out_q_clr": (1, 0x089, 1, True),
-    "fast_b_out_q_max": (1, 0x08A, 25, False),
-    "fast_b_out_q_min": (1, 0x08E, 25, False),
-    "fast_b_dx_sel": (1, 0x092, 4, True),
-    "fast_b_dy_sel": (1, 0x093, 4, True),
-    "gpio_n_ins": (30, 0x000, 8, False),
-    "gpio_n_outs": (30, 0x001, 8, True),
-    "gpio_n_oes": (30, 0x002, 8, True),
-    "gpio_n_state": (30, 0x003, 14, False),
-    "gpio_n_state_clr": (30, 0x005, 1, True),
-    "gpio_n_do0_en": (30, 0x006, 14, True),
-    "gpio_n_do1_en": (30, 0x008, 14, True),
-    "gpio_n_do2_en": (30, 0x00A, 14, True),
-    "gpio_n_do3_en": (30, 0x00C, 14, True),
-    "gpio_n_do4_en": (30, 0x00E, 14, True),
-    "gpio_n_do5_en": (30, 0x010, 14, True),
-    "gpio_n_do6_en": (30, 0x012, 14, True),
-    "gpio_n_do7_en": (30, 0x014, 14, True),
-    "gpio_p_ins": (31, 0x000, 8, False),
-    "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),
-    "scopegen_external_trigger": (6, 0x000, 1, True),
-    "scopegen_dac_a_clr": (6, 0x001, 1, True),
-    "scopegen_dac_a_max": (6, 0x002, 25, False),
-    "scopegen_dac_a_min": (6, 0x006, 25, False),
-    "scopegen_dac_b_clr": (6, 0x00A, 1, True),
-    "scopegen_dac_b_max": (6, 0x00B, 25, False),
-    "scopegen_dac_b_min": (6, 0x00F, 25, False),
-    "scopegen_adc_a_sel": (6, 0x013, 4, True),
-    "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),
-    "xadc_temp": (29, 0x000, 12, False),
-    "xadc_v": (29, 0x002, 12, False),
-    "xadc_a": (29, 0x004, 12, False),
-    "xadc_b": (29, 0x006, 12, False),
-    "xadc_c": (29, 0x008, 12, False),
-    "xadc_d": (29, 0x00A, 12, False),
+    'dna_dna': (28, 0x000, 64, False),
+    'fast_a_y_tap': (0, 0x000, 2, True),
+    'fast_a_invert': (0, 0x001, 1, True),
+    'fast_a_demod_delay': (0, 0x002, 32, True),
+    'fast_a_demod_multiplier': (0, 0x006, 4, True),
+    'fast_a_x_limit_1_min': (0, 0x007, 25, True),
+    'fast_a_x_limit_1_max': (0, 0x00b, 25, True),
+    'fast_a_iir_c_1_z0': (0, 0x00f, 27, True),
+    'fast_a_iir_c_1_a1': (0, 0x013, 25, True),
+    'fast_a_iir_c_1_b0': (0, 0x017, 25, True),
+    'fast_a_iir_c_1_b1': (0, 0x01b, 25, True),
+    'fast_a_iir_d_1_z0': (0, 0x01f, 27, True),
+    'fast_a_iir_d_1_a1': (0, 0x023, 25, True),
+    'fast_a_iir_d_1_a2': (0, 0x027, 25, True),
+    'fast_a_iir_d_1_b0': (0, 0x02b, 25, True),
+    'fast_a_iir_d_1_b1': (0, 0x02f, 25, True),
+    'fast_a_iir_d_1_b2': (0, 0x033, 25, True),
+    'fast_a_y_limit_1_min': (0, 0x037, 25, True),
+    'fast_a_y_limit_1_max': (0, 0x03b, 25, True),
+    'fast_a_x_limit_2_min': (0, 0x03f, 25, True),
+    'fast_a_x_limit_2_max': (0, 0x043, 25, True),
+    'fast_a_iir_c_2_z0': (0, 0x047, 27, True),
+    'fast_a_iir_c_2_a1': (0, 0x04b, 25, True),
+    'fast_a_iir_c_2_b0': (0, 0x04f, 25, True),
+    'fast_a_iir_c_2_b1': (0, 0x053, 25, True),
+    'fast_a_iir_d_2_z0': (0, 0x057, 27, True),
+    'fast_a_iir_d_2_a1': (0, 0x05b, 25, True),
+    'fast_a_iir_d_2_a2': (0, 0x05f, 25, True),
+    'fast_a_iir_d_2_b0': (0, 0x063, 25, True),
+    'fast_a_iir_d_2_b1': (0, 0x067, 25, True),
+    'fast_a_iir_d_2_b2': (0, 0x06b, 25, True),
+    'fast_a_y_limit_2_min': (0, 0x06f, 25, True),
+    'fast_a_y_limit_2_max': (0, 0x073, 25, True),
+    'fast_a_x_clr': (0, 0x077, 1, True),
+    'fast_a_x_max': (0, 0x078, 25, False),
+    'fast_a_x_min': (0, 0x07c, 25, False),
+    'fast_a_out_i_clr': (0, 0x080, 1, True),
+    'fast_a_out_i_max': (0, 0x081, 25, False),
+    'fast_a_out_i_min': (0, 0x085, 25, False),
+    'fast_a_out_q_clr': (0, 0x089, 1, True),
+    'fast_a_out_q_max': (0, 0x08a, 25, False),
+    'fast_a_out_q_min': (0, 0x08e, 25, False),
+    'fast_a_dx_sel': (0, 0x092, 4, True),
+    'fast_a_dy_sel': (0, 0x093, 4, True),
+    'fast_b_y_tap': (1, 0x000, 2, True),
+    'fast_b_invert': (1, 0x001, 1, True),
+    'fast_b_demod_delay': (1, 0x002, 32, True),
+    'fast_b_demod_multiplier': (1, 0x006, 4, True),
+    'fast_b_x_limit_1_min': (1, 0x007, 25, True),
+    'fast_b_x_limit_1_max': (1, 0x00b, 25, True),
+    'fast_b_iir_c_1_z0': (1, 0x00f, 27, True),
+    'fast_b_iir_c_1_a1': (1, 0x013, 25, True),
+    'fast_b_iir_c_1_b0': (1, 0x017, 25, True),
+    'fast_b_iir_c_1_b1': (1, 0x01b, 25, True),
+    'fast_b_iir_d_1_z0': (1, 0x01f, 27, True),
+    'fast_b_iir_d_1_a1': (1, 0x023, 25, True),
+    'fast_b_iir_d_1_a2': (1, 0x027, 25, True),
+    'fast_b_iir_d_1_b0': (1, 0x02b, 25, True),
+    'fast_b_iir_d_1_b1': (1, 0x02f, 25, True),
+    'fast_b_iir_d_1_b2': (1, 0x033, 25, True),
+    'fast_b_y_limit_1_min': (1, 0x037, 25, True),
+    'fast_b_y_limit_1_max': (1, 0x03b, 25, True),
+    'fast_b_x_limit_2_min': (1, 0x03f, 25, True),
+    'fast_b_x_limit_2_max': (1, 0x043, 25, True),
+    'fast_b_iir_c_2_z0': (1, 0x047, 27, True),
+    'fast_b_iir_c_2_a1': (1, 0x04b, 25, True),
+    'fast_b_iir_c_2_b0': (1, 0x04f, 25, True),
+    'fast_b_iir_c_2_b1': (1, 0x053, 25, True),
+    'fast_b_iir_d_2_z0': (1, 0x057, 27, True),
+    'fast_b_iir_d_2_a1': (1, 0x05b, 25, True),
+    'fast_b_iir_d_2_a2': (1, 0x05f, 25, True),
+    'fast_b_iir_d_2_b0': (1, 0x063, 25, True),
+    'fast_b_iir_d_2_b1': (1, 0x067, 25, True),
+    'fast_b_iir_d_2_b2': (1, 0x06b, 25, True),
+    'fast_b_y_limit_2_min': (1, 0x06f, 25, True),
+    'fast_b_y_limit_2_max': (1, 0x073, 25, True),
+    'fast_b_x_clr': (1, 0x077, 1, True),
+    'fast_b_x_max': (1, 0x078, 25, False),
+    'fast_b_x_min': (1, 0x07c, 25, False),
+    'fast_b_out_i_clr': (1, 0x080, 1, True),
+    'fast_b_out_i_max': (1, 0x081, 25, False),
+    'fast_b_out_i_min': (1, 0x085, 25, False),
+    'fast_b_out_q_clr': (1, 0x089, 1, True),
+    'fast_b_out_q_max': (1, 0x08a, 25, False),
+    'fast_b_out_q_min': (1, 0x08e, 25, False),
+    'fast_b_dx_sel': (1, 0x092, 4, True),
+    'fast_b_dy_sel': (1, 0x093, 4, True),
+    'gpio_n_ins': (30, 0x000, 8, False),
+    'gpio_n_outs': (30, 0x001, 8, True),
+    'gpio_n_oes': (30, 0x002, 8, True),
+    'gpio_n_state': (30, 0x003, 14, False),
+    'gpio_n_state_clr': (30, 0x005, 1, True),
+    'gpio_n_do0_en': (30, 0x006, 14, True),
+    'gpio_n_do1_en': (30, 0x008, 14, True),
+    'gpio_n_do2_en': (30, 0x00a, 14, True),
+    'gpio_n_do3_en': (30, 0x00c, 14, True),
+    'gpio_n_do4_en': (30, 0x00e, 14, True),
+    'gpio_n_do5_en': (30, 0x010, 14, True),
+    'gpio_n_do6_en': (30, 0x012, 14, True),
+    'gpio_n_do7_en': (30, 0x014, 14, True),
+    'gpio_p_ins': (31, 0x000, 8, False),
+    'gpio_p_outs': (31, 0x001, 8, True),
+    'gpio_p_oes': (31, 0x002, 8, True),
+    'logic_dual_channel': (8, 0x000, 1, True),
+    'logic_mod_channel': (8, 0x001, 1, True),
+    'logic_control_channel': (8, 0x002, 1, True),
+    'logic_sweep_channel': (8, 0x003, 2, True),
+    'logic_slow_control_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),
+    'scopegen_dac_a_min': (6, 0x006, 25, False),
+    'scopegen_dac_b_clr': (6, 0x00a, 1, True),
+    'scopegen_dac_b_max': (6, 0x00b, 25, False),
+    'scopegen_dac_b_min': (6, 0x00f, 25, False),
+    'scopegen_adc_a_sel': (6, 0x013, 4, True),
+    '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_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),
+    'xadc_b': (29, 0x006, 12, False),
+    '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..8e28e978 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:e9de83d288e611a27e57f547702cb8d580e2e27a16141eeb41eab53f90daa79c
 size 2083740
diff --git a/linien-server/linien_server/linien_install_requirements.sh b/linien-server/linien_server/linien_install_requirements.sh
index c25f4b74..2ab5abd7 100644
--- a/linien-server/linien_server/linien_install_requirements.sh
+++ b/linien-server/linien_server/linien_install_requirements.sh
@@ -14,7 +14,7 @@ cd /tmp
 echo 'building ethernet blinking fix'
 git clone https://github.com/linien-org/mdio-tool.git
 cd mdio-tool
-git checkout 72bd5a915ff046a59ce4303c8de672e77622a86c
+git checkout v1.0.0
 cmake .
 make
 mv -f mdio-tool /usr/bin
\ No newline at end of file
diff --git a/linien-server/linien_server/parameters.py b/linien-server/linien_server/parameters.py
index e6e99796..89449f91 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.
 #
@@ -28,8 +29,9 @@
 
 
 class Parameters(BaseParameters):
-    """This class defines the parameters of the Linien server. They represent
-    the public interface and can be used to control the behavior of the server.
+    """
+    This class defines the parameters of the Linien server. They represent the public
+    interface and can be used to control the behavior of the server.
 
     Access on the server takes place like this:
 
@@ -52,8 +54,8 @@ class Parameters(BaseParameters):
     def __init__(self):
         super().__init__()
 
-        # parameters whose values are saved by the client and restored if the
-        # client connects to the RedPitaya with no server running.
+        # parameters whose values are saved by the client and restored if the client
+        # connects to the RedPitaya with no server running:
         self._restorable_parameters = (
             "fast_mode",
             "modulation_amplitude",
@@ -93,6 +95,7 @@ def __init__(self):
             "mod_channel",
             "control_channel",
             "sweep_channel",
+            "slow_control_channel",
             "polarity_fast_out1",
             "polarity_fast_out2",
             "polarity_analog_out0",
@@ -112,236 +115,337 @@ def __init__(self):
             "psd_acquisition_max_decimation",
         )
 
-        # the `to_plot` parameter is a pickled dictionary that contains signals
-        # that may be plotted. Depending on the locking state, it may contain these
-        # signals:
-        # Unlocked state:
-        #   - `error_signal_1` and `error_signal_1_quadrature`:
-        #       IQ-demodulated and low-pass-filtered error signals from
-        #       ANALOG IN 0
-        #   - `error_signal_2` and `error_signal_2_quadrature`:
-        #       IQ-demodulated and low-pass-filtered error signals from
-        #       ANALOG IN 1. These signals are only available if in dual-channel
-        #       spectroscopy mode is enabled. Otherwise the un-demodulated
-        #       monitor_signal` is shown.
-        #   - `monitor_signal`:
-        #       Signal recorded from ANALOG IN 1 without demodulation. Only
-        #       available if dual channel mode is not enabled.
-        #
-        # Locked state:
-        #   - `error_signal`:
-        #       Error signal that is fed into the PID controller.
-        #   - `control_signal`:
-        #       Output of the PID controller.
-        #   - `slow`:
-        #       Output of slow additional integrator on slow analog output.
-        #       Note that this value is not an array but a single point.
         self.to_plot = Parameter(sync=False)
-        # A dictionary that contains mean, standard deviation, minimum value and
-        # maximum value for all the signals contained in `to_plot`.
-        # Exemplary dictionary keys are `error_signal_mean`,
-        # `control_signal_std`, `monitor_signal_min` or `error_signal_2_max`.
+        """
+        The `to_plot` parameter is a pickled dictionary that contains signalsvthat may
+        be plotted. Depending on the locking state, it may contain thesevsignals:
+        Unlocked state:
+          - `error_signal_1` and `error_signal_1_quadrature`:
+              IQ-demodulated and low-pass-filtered error signals from ANALOG IN 0
+          - `error_signal_2` and `error_signal_2_quadrature`:
+              IQ-demodulated and low-pass-filtered error signals from ANALOG IN 1. These
+              signals are only available if in dual-channel spectroscopy mode is
+              enabled. Otherwise the un-demodulated monitor_signal` is shown.
+          - `monitor_signal`:
+              Signal recorded from ANALOG IN 1 without demodulation. Only available if
+              dual channel mode is not enabled.
+        Locked state:
+          - `error_signal`:
+              Error signal that is fed into the PID controller.
+          - `control_signal`:
+              Output of the PID controller.
+          - `slow`:
+              Output of slow additional integrator on slow analog output. Note that this
+              value is not an array but a single point.
+        """
+
         self.signal_stats = Parameter(sync=False)
+        """
+        A dictionary that contains mean, standard deviation, minimum value and maximum
+        value for all the signals contained in `to_plot`. Exemplary dictionary keys are
+        `error_signal_mean`, `control_signal_std`, `monitor_signal_min` or
+        `error_signal_2_max`.
+        """
 
         #           --------- GENERAL PARAMETERS ---------
-        # configures the output of the modulation frequency. A value of 0 means
-        # FAST OUT 1 and a value of 1 corresponds to FAST OUT 2
+
         self.mod_channel = Parameter(start=0, min_=0, max_=1)
-        # configures the output of the scan sweep:
-        #       0 --> FAST OUT 1
-        #       1 --> FAST OUT 2
-        #       2 --> ANALOG OUT 0 (slow channel)
+        """
+        Configures the output of the modulation frequency. A value of 0 means FAST OUT 1
+        and a value of 1 corresponds to FAST OUT 2
+        """
+
         self.sweep_channel = Parameter(start=1, min_=0, max_=2)
-        # configures the output of the lock signal. A value of 0 means
-        # FAST OUT 1 and a value of 1 corresponds to FAST OUT 2
+        """
+        Configures the output of the scan sweep:
+            0 --> FAST OUT 1
+            1 --> FAST OUT 2
+            2 --> ANALOG OUT 0 (slow channel)
+        """
+
         self.control_channel = Parameter(start=1, min_=0, max_=1)
+        """
+        Configures the output of the lock signal. A value of 0 means FAST OUT 1 and a 
+        value of 1 corresponds to FAST OUT 2
+        """
+
+        self.slow_control_channel = Parameter(start=2, min_=0, max_=2)
+        """
+        Configures the output of the slow PID control:
+            0 --> FAST OUT 1
+            1 --> FAST OUT 2
+            2 --> ANALOG OUT 0 (slow channel)        
+        """
 
-        # set the output of GPIO pins. Each bit corresponds to one pin, i.e.:
-        #       parameters.gpio_p_out.value = 0b11110000
-        # turns on the first 4 pins and turns off the other ones.
         self.gpio_p_out = Parameter(start=0, min_=0, max_=0b11111111)
+        """
+        set the output of GPIO pins. Each bit corresponds to one pin, i.e.
+        `parameters.gpio_p_out.value = 0b11110000` turns on the first 4 pins and turns
+        off the other ones.
+        """
         self.gpio_n_out = Parameter(start=0, min_=0, max_=0b11111111)
+        """
+        set the output of GPIO pins. Each bit corresponds to one pin, i.e.
+        `parameters.gpio_p_out.value = 0b11110000` turns on the first 4 pins and turns
+        off the other ones.
+        """
+
+        # ANALOG_OUT0 is used for slow PID --> it can't be controlled manually
+        self.analog_out_1 = Parameter(start=0, min_=0, max_=(2**15) - 1)
+        """
+        parameters for setting ANALOG_OUT 1 voltage.
+        Usage: `parameters.analog_out_1.value = 1.2 * ANALOG_OUT_V`
+        Minimum value is 0 and maximum 1.8 * ANALOG_OUT_V
+        """
+        self.analog_out_2 = Parameter(start=0, min_=0, max_=(2**15) - 1)
+        """
+        parameters for setting ANALOG_OUT 1 voltage.
+        Usage: `parameters.analog_out_2.value = 1.2 * ANALOG_OUT_V`
+        Minimum value is 0 and maximum 1.8 * ANALOG_OUT_V
+        """
+        self.analog_out_3 = Parameter(start=0, min_=0, max_=(2**15) - 1)
+        """
+        parameters for setting ANALOG_OUT 1 voltage.
+        Usage: `parameters.analog_out_3.value = 1.2 * ANALOG_OUT_V`
+        Minimum value is 0 and maximum 1.8 * ANALOG_OUT_V
+        """
 
-        # parameters for setting ANALOG_OUT voltage
-        # usage:
-        #       parameters.analog_out_1.value = 1.2 * ANALOG_OUT_V
-        # Minimum value is 0 and maximum 1.8 * ANALOG_OUT_V
-        # note that ANALOG_OUT_0 ís used for the slow PID and thus can't be
-        for i in range(4):
-            if i == 0:
-                # ANALOG_OUT0 is used for slow PID --> it can't be controlled
-                # manually
-                continue
-
-            setattr(
-                self,
-                "analog_out_%d" % i,
-                Parameter(start=0, min_=0, max_=(2**15) - 1),
-            )
-
-        # If `True`, this parameter turns off the sweep and starts the PID
         self.lock = Parameter(start=False)
+        """If `True`, this parameter turns off the sweep and starts the PID"""
 
-        # for both fast outputs and the analog out, define whether tuning the
-        # voltage up correspond to tuning the laser frequency up or down. Setting
-        # these values correctly is only required when using both, a fast out and
-        # a the slow analog output for PID
         self.polarity_fast_out1 = Parameter(start=False)
+        """
+        Defines whether tuning the voltage up correspond to tuning the laser frequency
+        up or down. Setting these values correctly is only required when using both, a
+        fast out and a the slow output for PID
+        """
+
         self.polarity_fast_out2 = Parameter(start=False)
+        """
+        Defines whether tuning the voltage up correspond to tuning the laser frequency
+        up or down. Setting these values correctly is only required when using both, a
+        fast out and a the slow output for PID
+        """
+
         self.polarity_analog_out0 = Parameter(start=False)
+        """
+        Defines whether tuning the voltage up correspond to tuning the laser frequency
+        up or down. Setting these values correctly is only required when using both, a
+        fast out and a the slow output for PID
+        """
 
-        # record of control signal should be kept for how long?
         self.control_signal_history_length = Parameter(start=600)
+        """Record of control signal should be kept for how long?"""
+
         self.control_signal_history = Parameter(
             start={"times": [], "values": []}, sync=False
         )
+
         self.monitor_signal_history = Parameter(
             start={"times": [], "values": []}, sync=False
         )
-        # if this boolean is `True`, no new spectroscopy data is sent to the
-        # clients. This parameter is used when writing data to FPGA that would
-        # result in cropped / distorted signals being displayed.
+
         self.pause_acquisition = Parameter(start=False)
+        """
+        If this boolean is `True`, no new spectroscopy data is sent to the clients. This
+        parameter is used when writing data to FPGA that would result in cropped or
+        distorted signals being displayed.
+        """
 
-        # this parameter is not exposed to GUI. It is used by the autolock or
-        # normal lock to fetch less data if they are not needed.
         self.fetch_additional_signals = Parameter(start=True)
+        """
+        This parameter is not exposed to GUI. It is used by the autolock or normal lock
+        to fetch less data if they are not needed.
+        """
 
-        # this is just a counter that is automatically increased every second.
-        # Its purpose is to allow for periodic tasks on the server:
-        # just register an `on_change` listener for this parameter.
         self.ping = Parameter(start=0)
+        """
+        This is just a counter that is automatically increased every second. Its purpose
+        is to allow for periodic tasks on the server: just register an `on_change`
+        listener for this parameter.
+        """
 
         #           --------- SWEEP PARAMETERS ---------
 
-        # Amplitude of the sweep in units of 0.5 * Vpp of the output (2 V for fast
-        # outputs (range +/- 1 V) and 0.9 V for slow outputs (range 0 V to 1.8 V). That
-        # means an amplitude of 1.0 corresponds to the full sweep range in both cases.
         self.sweep_amplitude = Parameter(min_=0.001, max_=1, start=1)
-        # The center position of the sweep. If a fast output is used for the sweep this
-        #  is the sweep center position in volts. If the slow output is used the
-        # interval [-1, +1] of this parameter is mapped to the interval [0V, +1.8V].
+        """
+        Amplitude of the sweep in units of 0.5 * Vpp of the output (2 V for fast outputs
+        (range +/- 1 V) and 0.9 V for slow outputs (range 0 V to 1.8 V). That means an
+        amplitude of 1.0 corresponds to the full sweep range in both cases.
+        """
+
         self.sweep_center = Parameter(min_=-1, max_=1, start=0)
-        # The sweep speed in internal units. The actual speed is given by
-        #       f_real = 3.8 kHz / (2 ** sweep_speed)
-        # Allowed values are [0, ..., 16]
+        """
+        The center position of the sweep. If a fast output is used for the sweep this is
+        the sweep center position in volts. If the slow output is used the interval
+        [-1, +1] of this parameter is mapped to the interval [0V, +1.8V].
+        """
+
         self.sweep_speed = Parameter(min_=0, max_=32, start=8)
-        # If set to `True`, this parameter pauses the sweep at the `sweep_center`.
+        """
+        The sweep speed in internal units. The actual speed is given by
+        f_real = 3.8 kHz / (2 ** sweep_speed)
+        Allowed values are [0, ..., 16]
+        """
+
         self.sweep_pause = Parameter(start=False)
+        """If set to `True`, this parameter pauses the sweep at the `sweep_center`."""
 
         #           --------- MODULATION PARAMETERS ---------
 
-        # The amplitude of the modulation in internal units. Use Vpp for
-        # conversion to volts peak-peak, e.g:
-        #       parameters.modulation_amplitude.value = 0.5 * Vpp
-        # Values between 0 and 2 * Vpp are allowed.
         self.modulation_amplitude = Parameter(min_=0, max_=(1 << 14) - 1, start=1 * Vpp)
+        """
+        The amplitude of the modulation in internal units. Use Vpp for conversion to
+        volts peak-peak, e.g: `parameters.modulation_amplitude.value = 0.5 * Vpp`
+        Values between 0 and 2 * Vpp are allowed.
+        """
 
-        # Frequency of the modulation in internal units. Use MHz for conversion
-        # to human-readable frequency, e.g:
-        #       parameters.modulation_frequency.value = 6.6 * MHz
-        # By design, values up to 128 * MHz = 0xffffffff are allowed although in
-        # practice values of more than 50 MHz don't make sense due to the limited
-        # sampling rate of the DAC.
         self.modulation_frequency = Parameter(min_=0, max_=0xFFFFFFFF, start=15 * MHz)
+        """
+        Frequency of the modulation in internal units. Use MHz for conversion to
+        human-readable frequency, e.g:
+            `parameters.modulation_frequency.value = 6.6 * MHz`
+        By design, values up to 128 * MHz = 0xffffffff are allowed although in practice
+        values of more than 50 MHz don't make sense due to the limited sampling rate of
+        the DAC.
+        """
 
         #           --------- DEMODULATION AND FILTER PARAMETERS ---------
-        # fast mode allows to bypass demodulation and IIR filtering of the fast
-        # channels.
         self.fast_mode = Parameter(start=False)
-        # Linien allows for two simulataneous demodulation channels. By default,
-        # only one is enabled. This is controlled by `dual_channel`.
+        """
+        Fast mode allows to bypass demodulation and IIR filtering of the fast channels.
+        """
+
         self.dual_channel = Parameter(start=False)
-        # If in dual channel mode, what is the mixing ratio between them?
-        # A value of 0 corresponds to equal ratio
-        #            -128             only channel A being active
-        #            128              only channel B being active
-        # Integer values [-128, ..., 128] are allowed.
+        """
+        Linien allows for two simulataneous demodulation channels. By default, only one
+        is enabled. This is controlled by `dual_channel`.
+        """
+
         self.channel_mixing = Parameter(start=0)
+        """
+        If in dual channel mode, what is the mixing ratio between them? A value of 0
+        corresponds to equal ratio
+                   -128             only channel A being active
+                   128              only channel B being active
+        Integer values [-128, ..., 128] are allowed.        
+        """
 
         # The following parameters exist twice, i.e. once per channel
         for channel in ("a", "b"):
-            # The demodulation phase in degree (0-360)
             setattr(
                 self,
                 "demodulation_phase_%s" % channel,
                 Parameter(min_=0, max_=360, start=0x0, wrap=True),
             )
-            # This parameter allows for multi-f (e.g. 3f or 5f) demodulation.
-            # Default value is 1, indicating that 1f demodulation is used.
+            """The demodulation phase in degree (0-360)"""
+
             setattr(
                 self,
                 "demodulation_multiplier_%s" % channel,
                 Parameter(min_=0, max_=15, start=1),
             )
-            # The vertical offset for a channel. A value of -8191 shifts the data
-            # down by 1V, a value of +8191 moves it up.
+            """
+            This parameter allows for multi-f (e.g. 3f or 5f) demodulation. Default
+            value is 1, indicating that 1f demodulation is used.
+            """
+
             setattr(
                 self, "offset_%s" % channel, Parameter(min_=-8191, max_=8191, start=0)
             )
-            # A boolean indicating whether the channel data should be inverted.
+            """
+            The vertical offset for a channel. A value of -8191 shifts the data down by
+            1V, a value of +8191 moves it up.
+            """
+
             setattr(self, "invert_%s" % channel, Parameter(start=False))
+            """A boolean indicating whether the channel data should be inverted."""
 
             # - -----   FILTER PARAMETERS   -----
-            # after demodulation of the signal, Linien may apply up to two IIR
-            # filters.
-            # `filter_automatic` is a boolean indicating whether Linien should
-            # automatically determine suitable filter for a given modulation
-            # frequency or whether the user may configure the filters himself.
-            # If automatic mode is enabled, two low pass filters are installed
-            # with a frequency of half the modulation frequency.
-            setattr(self, "filter_automatic_%s" % channel, Parameter(start=True))
 
-            for filter_i in [1, 2]:
-                # should this filter be enabled? Note that disabling a filter
-                # does not bypass it as this would change the propagation time
-                # of the signal through the FPGA which is unfavorable as it leads
-                # to a mismatch of the demodulation phase. Instead, a filter
-                # with unity gain is installed.
+            setattr(self, "filter_automatic_%s" % channel, Parameter(start=True))
+            """
+            After demodulation of the signal, Linien may apply up to two IIR filters.
+            `filter_automatic` is a boolean indicating whether Linien should
+            automatically determine suitable filter for a given modulation frequency or
+            whether the user may configure the filters himself. If automatic mode is
+            enabled, two low pass filters are installed with a frequency of half the
+            modulation frequency.            
+            """
+
+            for filter_i in (1, 2):
                 setattr(
                     self,
                     "filter_%d_enabled_%s" % (filter_i, channel),
                     Parameter(start=False),
                 )
-                # Either `LOW_PASS_FILTER` or `HIGH_PASS_FILTER` from
-                # `linien_common.common` module.
+                """
+                Should this filter be enabled? Note that disabling a filter does not
+                bypass it as this would change the propagation time of the signal
+                through the FPGA which is unfavorable as it leads to a mismatch of the
+                demodulation phase. Instead, a filter with unity gain is installed.
+                """
+
                 setattr(
                     self, "filter_%d_type_%s" % (filter_i, channel), Parameter(start=0)
                 )
-                # The filter frequency in units of Hz
+                """
+                Either `LOW_PASS_FILTER` or `HIGH_PASS_FILTER` from
+                linien_common.common` module."""
+
                 setattr(
                     self,
                     "filter_%d_frequency_%s" % (filter_i, channel),
                     Parameter(start=10000),
                 )
+                """The filter frequency in units of Hz"""
 
         #           --------- LOCK AND PID PARAMETERS ---------
-        # after combining channels A and B and before passing the result to the
-        # PID, `combined_offset` is added. It uses the same units as the channel
-        # offsets, i.e. a value of -8191 shifts the data down by 1V, a value
-        # of +8191 moves it up.
+
         self.combined_offset = Parameter(min_=-8191, max_=8191, start=0)
-        # PID parameters. Range is [0, 8191]. In order to change sign of PID
-        # parameters, use `target_slope_rising`
+        """
+        After combining channels A and B and before passing the result to the PID,
+        `combined_offset` is added. It uses the same units as the channel offsets, i.e.
+        a value of -8191 shifts the data down by 1V, a value of +8191 moves it up.       
+        """
+
         self.p = Parameter(start=50, max_=8191)
+        """
+        Proportional part of PID parameters. Range is [0, 8191]. In order to change sign
+        of PID parameters, use `target_slope_rising`
+        """
+
         self.i = Parameter(start=5, max_=8191)
+        """
+        Integral part of PID parameters. Range is [0, 8191]. In order to change sign
+        of PID parameters, use `target_slope_rising`
+        """
+
         self.d = Parameter(start=0, max_=8191)
-        # A boolean that inverts the sign of the PID parameters
+        """
+        Derivate part of PID parameters. Range is [0, 8191]. In order to change sign
+        of PID parameters, use `target_slope_rising`
+        """
+
         self.target_slope_rising = Parameter(start=True)
+        """A boolean that inverts the sign of the PID parameters"""
 
-        # Whether the PID on ANALOG_OUT 0 is enabled
         self.pid_on_slow_enabled = Parameter(start=False)
-        # Strength of the (slow) PID on ANALOG_OUT 0. This strength corresponds
-        # to the strength of the integrator. Maximum value is 8191.
+        """Whether the PID on ANALOG_OUT 0 is enabled."""
+
         self.pid_on_slow_strength = Parameter(start=0)
+        """
+        Strength of the (slow) PID on ANALOG_OUT 0. This strength corresponds to the
+        strength of the integrator. Maximum value is 8191.
+        """
 
         self.check_lock = Parameter(start=True)
         self.watch_lock = Parameter(start=True)
         self.watch_lock_threshold = Parameter(start=0.01)
 
         #           --------- AUTOLOCK PARAMETERS ---------
-        # these are used internally by the autolock and usually should not be
-        # manipulated
+        # These parameters are used internally by the optimization algorithm and usually
+        # should not be manipulated
         self.task = Parameter(start=None, sync=False)
         self.automatic_mode = Parameter(start=True)
         self.autolock_target_position = Parameter(start=0)
@@ -362,7 +466,7 @@ def __init__(self):
         self.autolock_initial_sweep_amplitude = Parameter(start=1)
 
         #           --------- OPTIMIZATION PARAMETERS ---------
-        # these are used internally by the optimization algorithm and usually
+        # These parameters are used internally by the optimization algorithm and usually
         # should not be manipulated
         self.optimization_selection = Parameter(start=False)
         self.optimization_running = Parameter(start=False)
@@ -379,16 +483,20 @@ def __init__(self):
         self.optimization_failed = Parameter(start=False)
 
         #           --------- PID OPTIMIZATION PARAMETERS ---------
-        # these are used internally by the PID optimization algorithm and usually
+        # These parameters are used internally by the optimization algorithm and usually
         # should not be manipulated
         self.acquisition_raw_enabled = Parameter(start=False)
         self.acquisition_raw_decimation = Parameter(start=1)
         self.acquisition_raw_data = Parameter()
-        # raw acquisition has an additiona iir filter that can be used as low
-        # pass for preventing alias effects
         self.acquisition_raw_filter_enabled = Parameter(start=False)
-        # The filter frequency in units of Hz
+        """
+        Raw acquisition has an additiona iir filter that can be used as low pass for
+        preventing alias effects
+        """
+
         self.acquisition_raw_filter_frequency = Parameter(start=0)
+        """The filter frequency in units of Hz"""
+
         self.psd_data_partial = Parameter(start=None)
         self.psd_data_complete = Parameter(start=None)
         self.psd_algorithm = Parameter(start=PSD_ALGORITHM_LPSD)
diff --git a/linien-server/linien_server/registers.py b/linien-server/linien_server/registers.py
index a6a09a3b..0e3ea7fb 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_slow_control_channel=params["slow_control_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, slow_control_channel = (
             params["control_channel"],
             params["sweep_channel"],
+            params["slow_control_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(slow_control_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):
diff --git a/linien-server/setup.py b/linien-server/setup.py
index f9312f1e..960314bf 100644
--- a/linien-server/setup.py
+++ b/linien-server/setup.py
@@ -18,7 +18,7 @@
 
 from setuptools import find_packages, setup
 
-version = "0.6.0"
+version = "0.7.0"
 
 setup(
     name="linien-server",
diff --git a/pyproject.toml b/pyproject.toml
index 71900e94..bdadc30a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,8 @@ exclude = '''
   /(
     gateware/logic
    | gateware/lowlevel
-   | linien/server/csrmap.py
+   | gateware/linien_module.py
+   | linien-server/linien_server/csrmap.py
    )/
 )
 '''
@@ -18,4 +19,4 @@ plt_dirname = "tests/plots"
 # don't complain about plt_dirname
 filterwarnings = "ignore::pytest.PytestConfigWarning"
 
-markers = """slow : marks tests as slow (deselect with '-m "not slow"')"""
\ No newline at end of file
+markers = """slow : marks tests as slow (deselect with '-m "not slow"')"""
diff --git a/version-info.json b/version-info.json
index cc12d961..b4fec861 100644
--- a/version-info.json
+++ b/version-info.json
@@ -1,3 +1,3 @@
 {
-    "latest": "0.6.0"
+    "latest": "0.7.0"
 }
\ No newline at end of file


Test patch:
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
index 5cf9b819..f27df02f 100644
--- a/.github/workflows/pytest.yml
+++ b/.github/workflows/pytest.yml
@@ -2,6 +2,12 @@ name: Run PyTest
 
 on:
   workflow_dispatch:
+  push:
+    paths:
+      - gateware/**
+      - linien-common/**
+      - linien-server/**
+      - tests/**
 
 permissions:
   contents: read
diff --git a/tests/test_approacher.py b/tests/test_approacher.py
index 6389ca6a..ede173c8 100644
--- a/tests/test_approacher.py
+++ b/tests/test_approacher.py
@@ -102,9 +102,9 @@ def _get_signal(shift):
             )
 
             found = False
-
-            for i in range(100):
-                shift = target_shift * (1 + (0.025 * np.random.randn()))
+            rng = np.random.default_rng(seed=0)
+            for _ in range(100):
+                shift = target_shift * (1 + (0.025 * rng.standard_normal()))
                 error_signal = _get_signal(shift)[:]
                 approacher.approach_line(error_signal)
 
diff --git a/tests/test_common.py b/tests/test_common.py
index 5dcf0918..a7e4e1ce 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -24,6 +24,7 @@
 from pytest import raises
 
 Y_SHIFT = 0
+RNG = np.random.default_rng(seed=0)
 
 
 def peak(x):
@@ -44,7 +45,7 @@ def get_signal(sweep_amplitude, center, shift):
 
 
 def add_noise(spectrum, level):
-    return spectrum + (np.random.randn(len(spectrum)) * level)
+    return spectrum + (RNG.standard_normal(len(spectrum)) * level)
 
 
 def test_determine_shift_by_correlation():
diff --git a/tests/test_optimizer_utils.py b/tests/test_optimizer_utils.py
index 8dfe41b4..591910c8 100644
--- a/tests/test_optimizer_utils.py
+++ b/tests/test_optimizer_utils.py
@@ -16,12 +16,12 @@
 # You should have received a copy of the GNU General Public License
 # along with Linien.  If not, see <http://www.gnu.org/licenses/>.
 
-from random import randint, random
-
 import numpy as np
 from linien_server.optimization.utils import get_max_slope, optimize_phase_from_iq
 from scipy.optimize import minimize_scalar
 
+RNG = np.random.default_rng(seed=0)
+
 
 def test_get_max_slope():
     def generate_slope(N=1024, slope=1):
@@ -62,65 +62,54 @@ def get_sin(phase=0):
 
     def generate_fake_data(spectrum, phase=0):
         data = np.array([])
-
         sin = get_sin(phase=phase)
-
         for point in spectrum:
             data = np.append(data, point * sin)
-
         return data
 
     def demod(data, phase=0):
         sin = get_sin(phase=phase)
-
         block_size = len(sin)
-        N_points = round(len(data) / block_size)
-
+        n_points = round(len(data) / block_size)
         demodulated_data = []
-
-        for N in range(N_points):
-            data_slice = data[N * block_size : (N + 1) * block_size]
+        for n in range(n_points):
+            data_slice = data[n * block_size : (n + 1) * block_size]
             demodulated = np.mean(sin * data_slice)
             demodulated_data.append(demodulated)
-
         return demodulated_data
 
     final_zoom_factor = 10
-
     sweep_amplitude = 1.0
     max_val = np.pi * 5 * sweep_amplitude
     x = np.linspace(-1 * max_val, 1 * max_val, 100)
 
-    for iteration in range(1):
-        spectrum = spectrum_for_testing(x) * 2
-        data = generate_fake_data(spectrum, phase=30)  # phase=randint(0, 360))
+    spectrum = spectrum_for_testing(x) * 2
+    data = generate_fake_data(spectrum, phase=30)  # phase=randint(0, 360))
 
-        spectrum2 = spectrum_for_testing(x + random() * 3)
-        data2 = generate_fake_data(spectrum2, phase=randint(0, 360))  # noqa: F841
+    spectrum2 = spectrum_for_testing(x + RNG.random() * 3)
+    data2 = generate_fake_data(spectrum2, phase=RNG.integers(0, 360))  # noqa: F841
 
-        spectrum3 = spectrum_for_testing(x + random() * 3)
-        data3 = generate_fake_data(spectrum3, phase=randint(0, 360))  # noqa: F841
+    spectrum3 = spectrum_for_testing(x + RNG.random() * 3)
+    data3 = generate_fake_data(spectrum3, phase=RNG.integers(0, 360))  # noqa: F841
 
-        combined = data  # + data2 + data3
+    combined = data + data2 + data3
 
-        def get_slope(signal):
-            return get_max_slope(signal, final_zoom_factor)
+    def get_slope(signal):
+        return get_max_slope(signal, final_zoom_factor)
 
-        min_result = minimize_scalar(
-            lambda phase: -1 * get_slope(demod(combined, phase=phase)),
-            method="Bounded",
-            bounds=(0, 360),
-        )
+    min_result = minimize_scalar(
+        lambda phase: -1 * get_slope(demod(combined, phase=phase)),
+        method="Bounded",
+        bounds=(0, 360),
+    )
 
-        i = demod(combined)
-        q = demod(combined, phase=90)
+    i = demod(combined)
+    q = demod(combined, phase=90)
 
-        optimized_phase, optimized_slope = optimize_phase_from_iq(
-            i, q, final_zoom_factor
-        )
+    optimized_phase, optimized_slope = optimize_phase_from_iq(i, q, final_zoom_factor)
 
-        assert abs(min_result.x - optimized_phase) <= 0.1
-        assert abs(abs(min_result.fun) - optimized_slope) / optimized_slope <= 0.001
+    assert abs(min_result.x - optimized_phase) <= 0.1
+    assert abs(abs(min_result.fun) - optimized_slope) / optimized_slope <= 0.001
 
 
 if __name__ == "__main__":
diff --git a/tests/test_pid_transfer.py b/tests/test_pid_transfer.py
index bf640fff..48f18711 100644
--- a/tests/test_pid_transfer.py
+++ b/tests/test_pid_transfer.py
@@ -30,11 +30,11 @@
 @pytest.mark.slow
 def test_pid_transfer(plt):
     def pid_testbench(pid):
-        np.random.seed(299792458)
+        rng = np.random.default_rng(seed=299792458)
         amplitude = 0.01
         samples = 1 << 12
 
-        x = np.random.uniform(-amplitude, amplitude, samples)
+        x = rng.uniform(-amplitude, amplitude, samples)
         scale = 2 ** (len(pid.input) - 1) - 1
         x = (scale * np.array(x)).astype(int)
         y = np.array([0] * len(x))
diff --git a/tests/test_robust_autolock.py b/tests/test_robust_autolock.py
index 269e15c0..5d003242 100644
--- a/tests/test_robust_autolock.py
+++ b/tests/test_robust_autolock.py
@@ -39,6 +39,8 @@
 VCD_DIR = Path(__file__).parent / "vcd"
 FPGA_DELAY_SUMDIFF_CALCULATOR = 2
 
+RNG = np.random.default_rng(seed=0)
+
 
 def peak(x):
     return np.exp(-np.abs(x)) * np.sin(x)
@@ -55,7 +57,9 @@ def atomic_spectrum(noise_level):
     smaller_peaks = (peak(x - 10) * 1024) - (peak(x + 10) * 1024)
     target_idxs = (328, 350)
     return (
-        as_int(central_peak + smaller_peaks + (np.random.randn(len(x)) * noise_level)),
+        as_int(
+            central_peak + smaller_peaks + (RNG.standard_normal(len(x)) * noise_level)
+        ),
         target_idxs,
     )
 
@@ -66,17 +70,17 @@ def pfd_spectrum(noise_level):
     y[y > 3000] = 3000
     y[y < -3000] = -3000
     target_idxs = (220, 300)
-    return as_int(y + (np.random.randn(len(x)) * noise_level)), target_idxs
+    return as_int(y + (RNG.standard_normal(len(x)) * noise_level)), target_idxs
 
 
 def add_noise(spectrum, level):
-    return as_int(spectrum + (np.random.randn(len(spectrum)) * level))
+    return as_int(spectrum + (RNG.standard_normal(len(spectrum)) * level))
 
 
 def add_jitter(spectrum, level=None, exact_value=None):
     assert (level is not None) or (exact_value is not None)
     if exact_value is None:
-        exact_value = int(round(np.random.randn() * level))
+        exact_value = int(round(RNG.standard_normal() * level))
 
     shift = exact_value
     return np.roll(spectrum, shift)
@@ -87,8 +91,10 @@ def test_get_description(plt, debug=True):
     def get_lock_position_from_autolock_instructions_by_simulating_fpga(
         spectrum, description, time_scale, initial_spectrum, final_wait_time
     ):
-        """This function simulated the behavior of `RobustAutolock` on FPGA
-        and allows to find out whether FPGA would lock to the correct point."""
+        """
+        This function simulated the behavior of `RobustAutolock` on FPGA and allows to
+        find out whether FPGA would lock to the correct point.
+        """
         result = {}
 
         def tb(dut):
@@ -141,7 +147,8 @@ def tb(dut):
                 plt.plot(spectrum)
 
             jitters = [
-                0 if i == 0 else int(round(np.random.randn() * 50)) for i in range(10)
+                0 if i == 0 else int(round(RNG.standard_normal() * 50))
+                for i in range(10)
             ]
 
             spectra_with_jitter = [