diff --git a/cal_tools/cal_tools/agipdlib.py b/cal_tools/cal_tools/agipdlib.py
index 11d4438584b2db40bea2970558a1066269cc6e2c..25df1619293f0d6461b6061bde489d877a50671b 100644
--- a/cal_tools/cal_tools/agipdlib.py
+++ b/cal_tools/cal_tools/agipdlib.py
@@ -1,3 +1,6 @@
+from pathlib import Path
+from typing import Optional, Tuple
+
 import h5py
 import numpy as np
 import sharedmem
@@ -22,21 +25,65 @@ def get_num_cells(fname, loc, module):
         return options[np.argmin(dists)]
 
 
-def get_acq_rate(fname, loc, module):
-    with h5py.File(fname, "r") as f:
-        try:
-            pulses = \
-                np.squeeze(f[f"INSTRUMENT/{loc}/DET/{module}CH0:xtdf/image/pulseId"][:2])  # noqa
-            diff = pulses[1] - pulses[0]
-        except:
-            diff = 0
-        options = {8: 0.5, 4: 1.1, 2: 2.2, 1: 4.5}
-        return options.get(diff, None)
+def get_acq_rate(fast_paths: Tuple[str, str, int],
+                 slow_paths: Optional[Tuple[str, str]] = ('', '')
+                 ) -> Optional[float]:
+    """Get the acquisition rate from said detector module.
+
+    If the data is available from the middlelayer FPGA_COMP device, then it is
+    retrieved from there. If not, the rate is calculated from two different
+    pulses time.
+
+    The first entry is deliberatly not used, as the detector just began
+    operating, and it might have skipped a train.
 
+    :param slow_paths: in which file and h5 path to look for slow data.
+                       The first string is the filename with complete path,
+                       the second string is the key `karabo_id_control`
 
-def get_gain_setting(fname, h5path_ctrl):
+    :param fast_paths: in which module file and h5 path to look for pulses.
+                       The first string is the filename with complete path,
+                       the second string is the module device name `karabo_id`,
+                       the third parameter is the module number, used to
+                       navigate through the h5 file structure.
+
+    :return acq_rate: the acquisition rate.
+                      If not found in either files, return None.
+    """
+    # Attempt to look for acquisition rate in slow data
+    slow_data_file, karabo_id_control = slow_paths
+    slow_data_file = Path(slow_data_file)
+    if slow_data_file.is_file():
+        slow_data_path = f'CONTROL/{karabo_id_control}/MDL/FPGA_COMP/bunchStructure/repetitionRate/value'  # noqa
+        with h5py.File(slow_data_file, "r") as fin:
+            if slow_data_path in fin:
+                # The acquisition rate value is stored in a 1D array of type
+                # float. Use the 3rd value, arbitrarily chosen. It's okay to
+                # loose precision here because the usage is about defining the
+                # rate for meta-data.
+                return round(fin[slow_data_path][3], 1)
+
+    # Compute acquisition rate from fast data
+    fast_data_file, karabo_id, module = fast_paths
+    fast_data_file = Path(fast_data_file)
+    if fast_data_file.is_file():
+        fast_data_path = f'INSTRUMENT/{karabo_id}/DET/{module}CH0:xtdf/image/pulseId'  # noqa
+        with h5py.File(fast_data_file) as fin:
+            if fast_data_path in fin:
+                # pulses is of shape (NNNN, 1), of type uint8.
+                # Squeeze out the data, and subtract the 3rd entry from the 2nd
+                # to get a rate.
+                pulses = np.squeeze(fin[fast_data_path][1:3])
+                diff = pulses[1] - pulses[0]
+                options = {8: 0.5, 4: 1.1, 2: 2.2, 1: 4.5}
+                return options.get(diff, None)
+
+
+def get_gain_setting(fname: str, h5path_ctrl: str) -> int:
     """
-    Return gain setting base on setupr and patternTypeIndex
+    If the data is available from the middlelayer FPGA_COMP device, then it is
+    retrieved from there.
+    If not, the setting is calculated off `setupr` and `patternTypeIndex`
 
     gain-setting 1: setupr@dark=8, setupr@slopespc=40
     gain-setting 0: setupr@dark=0, setupr@slopespc=32
@@ -50,11 +97,18 @@ def get_gain_setting(fname, h5path_ctrl):
     :param h5path_ctrl: path to control information inside the file
     :return: gain setting
     """
-    with h5py.File(fname, "r") as f:
-        train_id = f["INDEX/trainId"][()]
+    gain_path = f'{h5path_ctrl}/gain/value'
+    with h5py.File(fname, "r") as fin:
+        if gain_path in fin:
+            return fin[gain_path][0]
+
+        # Get the index at which the train is not zero.
+        train_id = fin["INDEX/trainId"][()]
         idx = np.nonzero(train_id)[0][0]
-        setupr = f[f'{h5path_ctrl}/setupr/value'][idx]
-        pattern_type_idx = f[f'{h5path_ctrl}/patternTypeIndex/value'][idx]
+
+        setupr = fin[f'{h5path_ctrl}/setupr/value'][idx]
+        pattern_type_idx = fin[f'{h5path_ctrl}/patternTypeIndex/value'][idx]
+
         if (setupr == 0 and pattern_type_idx < 4) or (
                 setupr == 32 and pattern_type_idx == 4):
             return 0
@@ -65,6 +119,30 @@ def get_gain_setting(fname, h5path_ctrl):
             raise ValueError('Could not derive gain setting from setupr and patternTypeIndex')  # noqa
 
 
+def get_bias_voltage(fname: str, karabo_id_control: str,
+                     module: Optional[int] = 0) -> int:
+    """Read the voltage information from the FPGA device of module 0.
+
+    Different modules may operate at different voltages. In practice, they all
+    operate at the same voltage. As such, it is okay to read a single module's
+    value.
+
+    This value is read from slow data.
+
+    If the file cannot be accessed, an OSError will be raised.
+    If the hdf5 path cannot be accessed, None will be returned.
+
+    :param fname: path to slow data file with control information
+    :param karabo_id: The detector Karabo id, for creating the hdf5 path
+    :param module: defaults to module 0
+    :return: voltage, a uint16
+    """
+    voltage_path = f'/CONTROL/{karabo_id_control}/FPGA/M_{module}/highVoltage/actual/value'  # noqa
+    with h5py.File(fname, "r") as fin:
+        if voltage_path in fin:
+            return fin[voltage_path][0]
+
+
 class AgipdCorrections:
 
     def __init__(self, max_cells, max_pulses,
diff --git a/cal_tools/cal_tools/tools.py b/cal_tools/cal_tools/tools.py
index b8f695160e5ee6ff621158eb9a49d10e9e237e38..0f46f959ec4433b264c5591f08ed5c1bdeb572a4 100644
--- a/cal_tools/cal_tools/tools.py
+++ b/cal_tools/cal_tools/tools.py
@@ -7,6 +7,7 @@ from os.path import isfile, splitext
 from queue import Queue
 import re
 from time import sleep
+from typing import Optional
 from urllib.parse import urljoin
 
 import dateutil.parser
@@ -227,7 +228,9 @@ def get_run_info(proposal, run):
     return resp.json()
 
 
-def get_dir_creation_date(directory, run, tsdir=False, verbosity=0):
+def get_dir_creation_date(directory: str, run: int,
+                          tsdir: Optional[bool] = False,
+                          verbosity: Optional[int] = 0):
     """
     Return run starting time from the MDC.
     If not succeeded, return modification time of oldest file.h5
diff --git a/notebooks/AGIPD/AGIPD_Correct_and_Verify.ipynb b/notebooks/AGIPD/AGIPD_Correct_and_Verify.ipynb
index d7803821cf31e4baf5ecc115a3851ea8ff2b361d..e906d9921aa33d3f733bc70e47e11b0cb61f442e 100644
--- a/notebooks/AGIPD/AGIPD_Correct_and_Verify.ipynb
+++ b/notebooks/AGIPD/AGIPD_Correct_and_Verify.ipynb
@@ -313,7 +313,7 @@
     "\n",
     "# Evaluate aquisition rate\n",
     "if acq_rate == 0:\n",
-    "    acq_rate = get_acq_rate(filename, karabo_id, channel)\n",
+    "    acq_rate = get_acq_rate((filename, karabo_id, channel))\n",
     "else:\n",
     "    acq_rate = None\n",
     "\n",
diff --git a/notebooks/AGIPD/AGIPD_Retrieve_Constants_Precorrection.ipynb b/notebooks/AGIPD/AGIPD_Retrieve_Constants_Precorrection.ipynb
index 8f3df25d24065fb556f678d4e27dc4618fa06532..8a2ded9840b0484a95478c069917efc61489167d 100644
--- a/notebooks/AGIPD/AGIPD_Retrieve_Constants_Precorrection.ipynb
+++ b/notebooks/AGIPD/AGIPD_Retrieve_Constants_Precorrection.ipynb
@@ -271,7 +271,7 @@
     "                break\n",
     "\n",
     "    if acq_rate == 0.:\n",
-    "        acq_rate = get_acq_rate(f, karabo_id, idx)\n",
+    "        acq_rate = get_acq_rate((f, karabo_id, idx))\n",
     "    else:\n",
     "        acq_rate = None\n",
     "\n",
diff --git a/notebooks/AGIPD/Characterize_AGIPD_Gain_Darks_NBC.ipynb b/notebooks/AGIPD/Characterize_AGIPD_Gain_Darks_NBC.ipynb
index 2b39c0bc11e203dc0ed78bb8f4b8afcd2b37614b..5dc3a6c48990ee9e1ef68ad05dfc3f0bd9014668 100644
--- a/notebooks/AGIPD/Characterize_AGIPD_Gain_Darks_NBC.ipynb
+++ b/notebooks/AGIPD/Characterize_AGIPD_Gain_Darks_NBC.ipynb
@@ -50,7 +50,7 @@
     "db_output = False # output constants to database\n",
     "\n",
     "mem_cells = 0 # number of memory cells used, set to 0 to automatically infer\n",
-    "bias_voltage = 300 # detector bias voltage\n",
+    "bias_voltage = 0 # detector bias voltage\n",
     "gain_setting = 0.1 # the gain setting, use 0.1 to try to auto-determine\n",
     "acq_rate = 0. # the detector acquisition rate, use 0 to try to auto-determine\n",
     "interlaced = False # assume interlaced data format, for data prior to Dec. 2017\n",
@@ -91,6 +91,7 @@
     "warnings.filterwarnings('ignore')\n",
     "from collections import OrderedDict\n",
     "import os\n",
+    "from typing import Tuple, List\n",
     "import h5py\n",
     "import numpy as np\n",
     "import matplotlib\n",
@@ -111,7 +112,7 @@
     "from cal_tools.plotting import (create_constant_overview,\n",
     "                                plot_badpix_3d, show_processed_modules,\n",
     "                                show_overview)\n",
-    "from cal_tools.agipdlib import get_gain_setting\n",
+    "from cal_tools.agipdlib import get_bias_voltage, get_gain_setting\n",
     "\n",
     "# make sure a cluster is running with ipcluster start --n=32, give it a while to start\n",
     "from ipyparallel import Client\n",
@@ -155,6 +156,9 @@
     "    dinstance = \"AGIPD500K\"\n",
     "    nmods = 8\n",
     "\n",
+    "control_names = [f'{in_folder}/r{r:04d}/RAW-R{r:04d}-{karabo_da_control}-S00000.h5'\n",
+    "                 for r in (run_high, run_med, run_low)] \n",
+    "\n",
     "print(f\"Detector in use is {karabo_id}\")\n",
     "print(f\"Instrument {instrument}\")\n",
     "print(f\"Detector instance {dinstance}\")\n",
@@ -218,6 +222,12 @@
     "h5path = h5path.format(karabo_id, receiver_id)\n",
     "h5path_idx = h5path_idx.format(karabo_id, receiver_id)\n",
     "\n",
+    "if bias_voltage == 0: \n",
+    "    # Read the bias voltage from files, if recorded.\n",
+    "    # If not available, make use of the historical voltage the detector is running at\n",
+    "    bias_voltage = get_bias_voltage(control_names[0], karabo_id_control)\n",
+    "    bias_voltage = bias_voltage if bias_voltage is not None else 300\n",
+    "\n",
     "print(\"Parameters are:\")\n",
     "print(f\"Proposal: {prop}\")\n",
     "print(f\"Memory cells: {mem_cells}/{max_cells}\")\n",
@@ -273,55 +283,64 @@
      "end_time": "2019-02-20T10:50:55.839958Z",
      "start_time": "2019-02-20T10:50:55.468134Z"
     },
-    "scrolled": false
+    "scrolled": true
    },
    "outputs": [],
    "source": [
     "import copy\n",
     "from functools import partial\n",
-    "def characterize_module(il_mode, cells, bp_thresh, rawversion, loc, acq_rate,\n",
-    "                        h5path, h5path_idx, inp):\n",
+    "def characterize_module(il_mode: bool,\n",
+    "                        cells: int,\n",
+    "                        bp_thresh: Tuple[List[int], float, List[int], float], \n",
+    "                        rawversion: int,\n",
+    "                        loc: str, \n",
+    "                        acq_rate: float,\n",
+    "                        h5path: str,\n",
+    "                        h5path_idx: str,\n",
+    "                        control_names: List[str],\n",
+    "                        karabo_id_control: str,\n",
+    "                        inp: Tuple[str, int, int]) -> Tuple[np.array, np.array, np.array, np.array, int, np.array, int, float]:\n",
     "    import numpy as np\n",
     "    import copy\n",
     "    import h5py\n",
     "    from cal_tools.enums import BadPixels\n",
     "    from cal_tools.agipdlib import get_num_cells, get_acq_rate\n",
     "\n",
-    "    filename, channel, gg = inp\n",
+    "    fast_data_filename, channel, gg = inp\n",
     "    \n",
     "    if cells == 0:\n",
-    "        cells = get_num_cells(filename, loc, channel)\n",
+    "        cells = get_num_cells(fast_data_filename, loc, channel)\n",
     "\n",
     "    print(f\"Using {cells} memory cells\")\n",
     "    \n",
     "    if acq_rate == 0.:\n",
-    "        acq_rate = get_acq_rate(filename, loc, channel)\n",
+    "        slow_paths = control_names[gg], karabo_id_control\n",
+    "        fast_paths = fast_data_filename, loc, channel\n",
+    "        acq_rate = get_acq_rate(fast_paths, slow_paths)\n",
     "\n",
     "    thresholds_offset, thresholds_offset_sigma, thresholds_noise, thresholds_noise_sigma = bp_thresh \n",
     "    thresholds_offset_hard = thresholds_offset[gg]\n",
     "    thresholds_noise_hard = thresholds_noise[gg]\n",
-    "    infile = h5py.File(filename, \"r\", driver=\"core\")\n",
     "    \n",
     "    h5path = h5path.format(channel)\n",
     "    h5path_idx = h5path_idx.format(channel)\n",
     "    \n",
-    "    if rawversion == 2:\n",
-    "        count = np.squeeze(infile[f\"{h5path_idx}/count\"])\n",
-    "        first = np.squeeze(infile[f\"{h5path_idx}/first\"])\n",
-    "        last_index = int(first[count != 0][-1]+count[count != 0][-1])\n",
-    "        first_index = int(first[count != 0][0])\n",
-    "    else:\n",
-    "        status = np.squeeze(infile[f\"{h5path_idx}/status\"])\n",
-    "        if np.count_nonzero(status != 0) == 0:\n",
-    "            return\n",
-    "        last = np.squeeze(infile[f\"{h5path_idx}/last\"])\n",
-    "        first = np.squeeze(infile[f\"{h5path_idx}/first\"])\n",
-    "        last_index = int(last[status != 0][-1]) + 1\n",
-    "        first_index = int(first[status != 0][0])\n",
-    "    im = np.array(infile[f\"{h5path}/data\"][first_index:last_index,...])    \n",
-    "    cellIds = np.squeeze(infile[f\"{h5path}/cellId\"][first_index:last_index,...]) \n",
-    "    \n",
-    "    infile.close()\n",
+    "    with h5py.File(fast_data_filename, \"r\", driver=\"core\") as infile:\n",
+    "        if rawversion == 2:\n",
+    "            count = np.squeeze(infile[f\"{h5path_idx}/count\"])\n",
+    "            first = np.squeeze(infile[f\"{h5path_idx}/first\"])\n",
+    "            last_index = int(first[count != 0][-1]+count[count != 0][-1])\n",
+    "            first_index = int(first[count != 0][0])\n",
+    "        else:\n",
+    "            status = np.squeeze(infile[f\"{h5path_idx}/status\"])\n",
+    "            if np.count_nonzero(status != 0) == 0:\n",
+    "                return\n",
+    "            last = np.squeeze(infile[f\"{h5path_idx}/last\"])\n",
+    "            first = np.squeeze(infile[f\"{h5path_idx}/first\"])\n",
+    "            last_index = int(last[status != 0][-1]) + 1\n",
+    "            first_index = int(first[status != 0][0])\n",
+    "        im = np.array(infile[f\"{h5path}/data\"][first_index:last_index,...])    \n",
+    "        cellIds = np.squeeze(infile[f\"{h5path}/cellId\"][first_index:last_index,...]) \n",
     "\n",
     "    if il_mode:\n",
     "        ga = im[1::2, 0, ...]\n",
@@ -412,7 +431,8 @@
     "p = partial(characterize_module, IL_MODE, max_cells,\n",
     "           (thresholds_offset_hard, thresholds_offset_sigma,\n",
     "            thresholds_noise_hard, thresholds_noise_sigma),\n",
-    "            rawversion, karabo_id, acq_rate, h5path, h5path_idx)\n",
+    "            rawversion, karabo_id, acq_rate, h5path, h5path_idx,\n",
+    "           control_names, karabo_id_control)\n",
     "\n",
     "# Don't remove. Used for Debugging.\n",
     "#results = list(map(p, inp))\n",
diff --git a/notebooks/AGIPD/Characterize_AGIPD_Gain_FlatFields_NBC.ipynb b/notebooks/AGIPD/Characterize_AGIPD_Gain_FlatFields_NBC.ipynb
index 9f4a3351b56b1857bd6f79465aa94b5249a03bd5..6c7a36b8f44b6dfabe902d4f38a5ac7504249481 100644
--- a/notebooks/AGIPD/Characterize_AGIPD_Gain_FlatFields_NBC.ipynb
+++ b/notebooks/AGIPD/Characterize_AGIPD_Gain_FlatFields_NBC.ipynb
@@ -189,7 +189,7 @@
     "channel = 0\n",
     "fname = fbase.format(runs[0], runs[0].upper(), channel, sequences[0])\n",
     "if acqrate == 0.:\n",
-    "    acqrate = get_acq_rate(fname, loc, channel)\n",
+    "    acqrate = get_acq_rate((fname, loc, channel))\n",
     "    \n",
     "\n",
     "if mem_cells == 0:\n",
diff --git a/notebooks/AGIPD/Chracterize_AGIPD_Gain_PC_NBC.ipynb b/notebooks/AGIPD/Chracterize_AGIPD_Gain_PC_NBC.ipynb
index bd919cef03f613c3466e04f9060a13c4b4674b31..59c499d2a2dc8bd005b57780d42a8dad1911aadc 100644
--- a/notebooks/AGIPD/Chracterize_AGIPD_Gain_PC_NBC.ipynb
+++ b/notebooks/AGIPD/Chracterize_AGIPD_Gain_PC_NBC.ipynb
@@ -160,7 +160,7 @@
     "    print('Reading ',fname)\n",
     "    \n",
     "    if acq_rate == 0.:\n",
-    "        acq_rate = get_acq_rate(fname, loc, channel)\n",
+    "        acq_rate = get_acq_rate((fname, loc, channel))\n",
     "        print(\"Acquisition rate set from file: {} MHz\".format(acq_rate))\n",
     "\n",
     "    if mem_cells == 0:\n",
diff --git a/notebooks/AGIPD/playground/AGIPD_SingleM_test_Dark.ipynb b/notebooks/AGIPD/playground/AGIPD_SingleM_test_Dark.ipynb
index 551f5f98a568931bcc44e659bcf9d141b406e3a0..f727a1a94440240014378ad353bb276210798570 100644
--- a/notebooks/AGIPD/playground/AGIPD_SingleM_test_Dark.ipynb
+++ b/notebooks/AGIPD/playground/AGIPD_SingleM_test_Dark.ipynb
@@ -260,7 +260,7 @@
     "        cells = get_num_cells(filename, loc, channel)\n",
     "    \n",
     "    if acq_rate == 0.:\n",
-    "        acq_rate = get_acq_rate(filename, loc, channel)\n",
+    "        acq_rate = get_acq_rate((filename, loc, channel))\n",
     "    \n",
     "    thresholds_offset, thresholds_offset_sigma, thresholds_noise, thresholds_noise_sigma = bp_thresh \n",
     "    thresholds_offset_hard = thresholds_offset[gg]\n",
diff --git a/notebooks/generic/overallmodules_Darks_Summary_NBC.ipynb b/notebooks/generic/overallmodules_Darks_Summary_NBC.ipynb
index d0f9c7d44522dd8baee23f26877bdedf4b2540cb..5747928d6c6e309d653ed911767b1d142dc3f773 100644
--- a/notebooks/generic/overallmodules_Darks_Summary_NBC.ipynb
+++ b/notebooks/generic/overallmodules_Darks_Summary_NBC.ipynb
@@ -61,7 +61,7 @@
     "    elif \"MID\" in karabo_id:\n",
     "        dinstance = \"AGIPD1M2\"\n",
     "        nmods = 16\n",
-    "    elif \"HED\" in karabo_id or \"DETLAB\" in karabo_id :\n",
+    "    elif \"HED\" in karabo_id or \"DETLAB\" in karabo_id:\n",
     "        dinstance = \"AGIPD500K\"\n",
     "        nmods = 8\n",
     "    display(Markdown(\"\"\"\n",
diff --git a/requirements.txt b/requirements.txt
index 76a713f32e287b96e697f3b4a5022792ac01ed60..451df43f035744e740dbff94ea08ca2ad438c9bd 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-git+file:///gpfs/exfel/sw/calsoft/git/cal_db_interactive@1.5.3
+git+file:///gpfs/exfel/sw/calsoft/git/cal_db_interactive@1.6.1
 git+file:///gpfs/exfel/sw/calsoft/git/nbparameterise@0.3
 git+file:///gpfs/exfel/sw/calsoft/git/pyDetLib@2.5.3-2.7.0#subdirectory=lib
 astcheck == 0.2.5