diff --git a/Makefile.in b/Makefile.in
index cc16190b5ac7bb328d05dfcf0ded8d2bf623bc2a..749fa170cc860dbe2a5692e728dfbf7903021df9 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -83,10 +83,25 @@ start:
 	    "echo narf"
 	@tmux select-layout even-vertical
 
+	@# AHRS
+	@#
+	@tmux new-window -n ahrs -t ${SESSION_NAME}
+	@tmux split-window -v -t ${SESSION_NAME}:ahrs
+	@tmux send-keys -t ${SESSION_NAME}:ahrs.1  \
+	    "python scripts/ahrs_calibration.py -d ${DETECTOR_ID} -p ${MONITORING_LIGIER_PORT}" Enter
+	@tmux select-layout even-vertical
+
+	@# Log.io
+	@#
+	@tmux new-window -n logio -t ${SESSION_NAME}
+	@tmux split-window -v -t ${SESSION_NAME}:logio
+	@tmux send-keys -t ${SESSION_NAME}:logio.1  \
+	    "python scripts/logio.py -p ${MONITORING_LIGIER_PORT} -x ${LOGIO_IP} -q ${LOGIO_PORT}" Enter
+	@tmux select-layout even-vertical
 
 stop:
-	killall gunicorn
 	tmux kill-session -t ${SESSION_NAME}
+	killall gunicorn
 
 clean:
 	rm Makefile
diff --git a/README.md b/README.md
index 0efa47c142be59cbdb9448ce35439388823ed477..559eee5d9c830cec3a111956616cb7590184cab9 100644
--- a/README.md
+++ b/README.md
@@ -60,3 +60,21 @@ If you want to stop it:
     make stop
 
 easy.
+
+## Configuration file
+
+A file called `pipeline.toml` can be placed into the root folder of the
+monitoring software (usually `~/monitoring`) which can be used to set
+different kind of parameters, like plot attributes or ranges.
+Here is an example `pipeline.toml`:
+
+    [DOMRates]
+    lowest_rate = 200
+    highest_rate = 400
+
+    [PMTRates]
+    lowest_rate = 1000
+    highest_rate = 10000
+
+After a `make stop` and `make start`, the file is parsed and the default
+values are overwritten by those defined in the configuration file.
diff --git a/app/routes.py b/app/routes.py
index 1906dff473670dcbc373ccac283c1fd1c92ef473..3cf64f24ed70939d7d693d3125d4f8da52388e52 100644
--- a/app/routes.py
+++ b/app/routes.py
@@ -8,6 +8,9 @@ app.config['FREEZER_DESTINATION'] = '../km3web'
 PLOTS = [['dom_activity', 'dom_rates'], ['pmt_rates', 'pmt_hrv'],
          ['trigger_rates'], ['ztplot', 'triggermap']]
 
+AHRS_PLOTS = [['yaw_calib'], ['pitch_calib'], ['roll_calib']]
+TRIGGER_PLOTS = [['trigger_rates'], ['trigger_rates_lin']]
+
 
 @app.after_request
 def add_header(r):
@@ -27,6 +30,16 @@ def index():
     return render_template('plots.html', plots=PLOTS)
 
 
+@app.route('/ahrs.html')
+def ahrs():
+    return render_template('plots.html', plots=AHRS_PLOTS)
+
+
+@app.route('/trigger.html')
+def trigger():
+    return render_template('plots.html', plots=TRIGGER_PLOTS)
+
+
 @app.route('/plots/<path:filename>')
 def custom_static(filename):
     print(filename)
diff --git a/app/templates/base.html b/app/templates/base.html
index f4e2f213d123aa8b6d2d5e6875f2d63286abdb14..54273262e27596753923f77c631c9dcf86aacb54 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -29,11 +29,17 @@
           <ul class="nav navbar-nav">
             <li class="active"><a href="index.html">Overview</a></li>
           </ul>
+          <!-- <ul class="nav navbar&#45;nav"> -->
+          <!--   <li class="active"><a href="#">Intra DOM Calibration</a></li> -->
+          <!-- </ul> -->
+          <!-- <ul class="nav navbar&#45;nav"> -->
+          <!--   <li class="active"><a href="#">K40 Rate Dist</a></li> -->
+          <!-- </ul> -->
           <ul class="nav navbar-nav">
-            <li class="active"><a href="#">Intra DOM Calibration</a></li>
+            <li class="active"><a href="trigger.html">Trigger</a></li>
           </ul>
           <ul class="nav navbar-nav">
-            <li class="active"><a href="#">K40 Rate Dist</a></li>
+            <li class="active"><a href="ahrs.html">AHRS</a></li>
           </ul>
           <ul class="nav navbar-nav">
             <li class="active"><a href="https://git.km3net.de/km3py/km3mon/issues">Issues</a></li>
diff --git a/configure b/configure
index 1d292ff406e227bdbdbe091abea336e29893d157..0d95310c0986a4ecc3e620d704ca5fd291131de3 100755
--- a/configure
+++ b/configure
@@ -5,6 +5,8 @@ daq_ligier_ip="192.168.0.110"
 daq_ligier_port=5553
 monitoring_ligier_ip="127.0.0.1"
 monitoring_ligier_port=5553
+logio_ip="127.0.0.1"
+logio_port=28777
 webserver_port=8080
 tmux_session_name="km3mon"
 
@@ -37,6 +39,14 @@ for arg in "$@"; do
         monitoring_ligier_port=`echo $arg | sed 's/--monitoring-ligier-port=//'`
         ;;
 
+    --logio-ip=*)
+        logio_ip=`echo $arg | sed 's/--logio-ip=//'`
+        ;;
+
+    --logio-port=*)
+        logio_port=`echo $arg | sed 's/--logio-port=//'`
+        ;;
+
     --tmux-session-name=*)
         tmux_session_name=`echo $arg | sed 's/--tmux-session-name=//'`
         ;;
@@ -54,6 +64,8 @@ for arg in "$@"; do
         echo "  --daq-ligier-ip           DAQ Ligier                     ${daq_ligier_ip}"
         echo "  --daq-ligier-port         Port of the DAQ Ligier         ${daq_ligier_port}"
         echo "  --monitoring-ligier-port  Port of the monitoring Ligier  ${monitoring_ligier_port}"
+        echo "  --logio-ip                Log.io IP                      ${logio_ip}"
+        echo "  --logio-port              Port of the Log.io server      ${logio_port}"
         echo "  --tmux-session-name       TMUX session name              ${tmux_session_name}"
         echo "  --webserver-port          Port of the web server         ${webserver_port}"
         echo
@@ -67,6 +79,8 @@ echo "DETECTOR_ID = ${detector_id}" > Makefile
 echo "DAQ_LIGIER_IP = ${daq_ligier_ip}" >> Makefile
 echo "DAQ_LIGIER_PORT = ${daq_ligier_port}" >> Makefile
 echo "MONITORING_LIGIER_PORT = ${monitoring_ligier_port}" >> Makefile
+echo "LOGIO_IP = ${logio_ip}" >> Makefile
+echo "LOGIO_PORT = ${logio_port}" >> Makefile
 echo "WEBSERVER_PORT = ${webserver_port}" >> Makefile
 echo "SESSION_NAME = ${tmux_session_name}" >> Makefile
 
@@ -76,6 +90,7 @@ show_km3mon_banner
 echo "Detector ID:        ${detector_id}"
 echo "DAQ Ligier:         ${daq_ligier_ip}:${daq_ligier_port}"
 echo "Monitoring Ligier:  ${monitoring_ligier_ip}:${monitoring_ligier_port}"
+echo "Log.io Server:      ${logio_ip}:${logio_port}"
 echo "Webserver:          0.0.0.0:${webserver_port}"
 echo "TMUX session:       ${tmux_session_name}"
 echo
diff --git a/frontail.json b/frontail.json
new file mode 100644
index 0000000000000000000000000000000000000000..24a32b74b587fb02636e7dc1fb382e56c539ce9e
--- /dev/null
+++ b/frontail.json
@@ -0,0 +1,12 @@
+{
+    "words": {
+        "ERROR": "color: #CC0C28;",
+        "STATUS": "color: #2F76CC;",
+        "DataFilter": "background-color: #138799; color: #ffffff",
+        "DataWriter": "background-color: #FFBE05; color: #000000",
+        "DataQueue": "background-color: #674591; color=: #ffffff"
+    },
+    "lines": {
+        "ERROR": "font-weight: bold;"
+    }
+}
diff --git a/scripts/ahrs_calibration.py b/scripts/ahrs_calibration.py
new file mode 100755
index 0000000000000000000000000000000000000000..db5faa365a8c6f1b53cf9686bce19d99ca71f17d
--- /dev/null
+++ b/scripts/ahrs_calibration.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+# coding=utf-8
+# vim: ts=4 sw=4 et
+"""
+Runs the AHRS calibration online.
+
+Usage:
+    ahrs_calibration.py [options]
+    ahrs_calibration.py (-h | --help)
+
+Options:
+    -l LIGIER_IP    The IP of the ligier [default: 127.0.0.1].
+    -p LIGIER_PORT  The port of the ligier [default: 5553].
+    -d DET_ID       Detector ID [default: 29].
+    -o PLOT_DIR     The directory to save the plot [default: plots].
+    -h --help       Show this screen.
+
+"""
+from __future__ import division
+
+from datetime import datetime
+from collections import deque, defaultdict
+from functools import partial
+import io
+import os
+import threading
+
+import numpy as np
+import matplotlib
+matplotlib.use('Agg')
+import matplotlib.pyplot as plt
+import matplotlib.dates as md
+import seaborn as sns
+
+import km3pipe as kp
+from km3pipe.io.daq import TMCHData
+from km3modules.ahrs import fit_ahrs, get_latest_ahrs_calibration
+import km3pipe.style
+km3pipe.style.use('km3pipe')
+
+
+class CalibrateAHRS(kp.Module):
+    def configure(self):
+        self.plots_path = self.require('plots_path')
+        det_id = self.require('det_id')
+        self.detector = kp.hardware.Detector(det_id=det_id)
+        self.du = self.get('du', default=1)
+
+        self.clbmap = kp.db.CLBMap(det_oid=det_id)
+
+        self.cuckoo = kp.time.Cuckoo(60, self.create_plot)
+        self.cuckoo_log = kp.time.Cuckoo(10, print)
+        self.data = {}
+        queue_size = 50000
+        for ahrs_param in ('yaw', 'pitch', 'roll'):
+            self.data[ahrs_param] = defaultdict(
+                partial(deque, maxlen=queue_size))
+        self.times = defaultdict(partial(deque, maxlen=queue_size))
+        self.lock = threading.Lock()
+        self.index = 0
+
+    def process(self, blob):
+        self.index += 1
+        if self.index % 29 != 0:
+            return blob
+        now = datetime.utcnow()
+        tmch_data = TMCHData(io.BytesIO(blob['CHData']))
+        dom_id = tmch_data.dom_id
+        clb = self.clbmap.dom_id[dom_id]
+        if clb.floor == 0:
+            self.log.info("Skipping base CLB")
+            return blob
+
+        if clb.du != self.du:
+            return blob
+
+        yaw = tmch_data.yaw
+        calib = get_latest_ahrs_calibration(clb.upi, max_version=4)
+
+        if calib is None:
+            return blob
+
+        cyaw, cpitch, croll = fit_ahrs(tmch_data.A, tmch_data.H, *calib)
+        self.cuckoo_log("DU{}-DOM{} (random pick): calibrated yaw={}".format(
+            clb.du, clb.floor, cyaw))
+        with self.lock:
+            self.data['yaw'][clb.floor].append(cyaw)
+            self.data['pitch'][clb.floor].append(cpitch)
+            self.data['roll'][clb.floor].append(croll)
+            self.times[clb.floor].append(now)
+
+        self.cuckoo.msg()
+        return blob
+
+    def create_plot(self):
+        print(self.__class__.__name__ + ": updating plot.")
+        # xfmt = md.DateFormatter('%Y-%m-%d %H:%M')
+        xfmt = md.DateFormatter('%H:%M')
+        for ahrs_param in self.data.keys():
+            fig, ax = plt.subplots(figsize=(16, 6))
+            sns.set_palette("husl", 18)
+            ax.set_title("AHRS {} Calibration on DU{}\n{}".format(
+                ahrs_param, self.du, datetime.utcnow()))
+            ax.set_xlabel("UTC time")
+            ax.xaxis.set_major_formatter(xfmt)
+            ax.set_ylabel(ahrs_param)
+            with self.lock:
+                for floor in sorted(self.data[ahrs_param].keys()):
+                    ax.plot(
+                        self.times[floor],
+                        self.data[ahrs_param][floor],
+                        marker='.',
+                        linestyle='none',
+                        label="Floor {}".format(floor))
+            lgd = plt.legend(
+                bbox_to_anchor=(1.005, 1), loc=2, borderaxespad=0.)
+            fig.tight_layout()
+            plt.savefig(
+                os.path.join(self.plots_path, ahrs_param + '_calib.png'),
+                bbox_extra_artists=(lgd, ),
+                bbox_inches='tight')
+            plt.close('all')
+
+
+def main():
+    from docopt import docopt
+    args = docopt(__doc__)
+
+    det_id = int(args['-d'])
+    plots_path = args['-o']
+    ligier_ip = args['-l']
+    ligier_port = int(args['-p'])
+
+    pipe = kp.Pipeline()
+    pipe.attach(
+        kp.io.ch.CHPump,
+        host=ligier_ip,
+        port=ligier_port,
+        tags='IO_MONIT',
+        timeout=60 * 60 * 24 * 7,
+        max_queue=2000)
+    pipe.attach(kp.io.daq.DAQProcessor)
+    pipe.attach(CalibrateAHRS, det_id=det_id, plots_path=plots_path)
+    pipe.drain()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/scripts/dom_activity.py b/scripts/dom_activity.py
index 899ec61d9392876ef0dc5ba8586f0e1419017fc9..29e3c2e36e3b117779e819d53cd8f7f59ae2037f 100755
--- a/scripts/dom_activity.py
+++ b/scripts/dom_activity.py
@@ -91,7 +91,8 @@ class DOMActivityPlotter(kp.Module):
             self.detector,
             filename,
             'last activity [s]',
-            "DOM Activity - via Summary Slices",
+            "DOM Activity for DetID-{} - via Summary Slices".format(
+                self.detector.det_id),
             vmin=0.0,
             vmax=15 * 60)
 
diff --git a/scripts/dom_rates.py b/scripts/dom_rates.py
index 8d2267f511871f3d4aa4ebae563c3087bfa76a59..91d1974a0a1fa502130fa4cdc8c7217c8b6a12ae 100755
--- a/scripts/dom_rates.py
+++ b/scripts/dom_rates.py
@@ -80,7 +80,7 @@ class DOMRates(kp.Module):
             self.detector,
             filename,
             'rate [kHz]',
-            "DOM Rates",
+            "DOM Rates for DetID-{}".format(self.detector.det_id),
             vmin=self.lowest_rate,
             vmax=self.highest_rate,
             cmap='coolwarm',
diff --git a/scripts/live_triggermap.py b/scripts/live_triggermap.py
index 1fd198320e663636158cf048192d9ef9ffba4f0e..d50e9b463d226ae76c2d22a5854e7ec96322db35 100755
--- a/scripts/live_triggermap.py
+++ b/scripts/live_triggermap.py
@@ -125,9 +125,10 @@ class DOMHits(Module):
         ax.tick_params(labelbottom=False)
         ax.tick_params(labeltop=False)
         ax.set_xlabel("event (latest on the right)")
-        ax.set_title("{0} - via the last {1} Events\n{2} UTC".format(
-            title, self.max_events,
-            datetime.utcnow().strftime("%c")))
+        ax.set_title(
+            "{0} for DetID-{1} - via the last {2} Events\n{3} UTC".format(
+                title, self.det.det_id, self.max_events,
+                datetime.utcnow().strftime("%c")))
         cb = fig.colorbar(im, pad=0.05)
         cb.set_label("number of hits")
 
@@ -180,7 +181,7 @@ def main():
         kp.io.ch.CHPump,
         host=ligier_ip,
         port=ligier_port,
-        tags='IO_EVT, IO_SUM',
+        tags='IO_EVT',
         timeout=60 * 60 * 24 * 7,
         max_queue=2000)
     pipe.attach(kp.io.daq.DAQProcessor)
diff --git a/scripts/logio.py b/scripts/logio.py
new file mode 100755
index 0000000000000000000000000000000000000000..779214f8474c61fa7bebc7b6a8f342a4335847d8
--- /dev/null
+++ b/scripts/logio.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+# coding=utf-8
+# Filename: logio.py
+# Author: Tamas Gal <tgal@km3net.de>
+# vim: ts=4 sw=4 et
+"""
+Sends MSG data from Ligier to a log.io server to be displayed in real-time.
+
+Usage:
+    logio.py [options]
+    logio.py (-h | --help)
+
+Options:
+    -l LIGIER_IP    The IP of the ligier [default: 127.0.0.1].
+    -p LIGIER_PORT  The port of the ligier [default: 5553].
+    -x LOGIO_IP    The IP of the ligier [default: 127.0.0.1].
+    -q LOGIO_PORT  The port of the ligier [default: 28777].
+    -h --help       Show this screen.
+
+"""
+import socket
+import time
+
+from km3pipe import Pipeline, Module
+from km3pipe.io import CHPump
+
+
+class LogIO(Module):
+    def configure(self):
+        url = self.get('logio_ip', default='127.0.0.1')
+        port = self.get('logio_port', default=28777)
+
+        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.sock.connect((url, port))
+
+    def process(self, blob):
+        data = blob['CHData']
+        log_level = 'info'
+        if "ERROR" in data:
+            log_level = 'error'
+        if "WARNING" in data:
+            log_level = 'warning'
+        source = "Other"
+        if " F0" in data:
+            source = "DataFilter"
+        if " Q0" in data:
+            source = "DataQueue"
+        if " W0" in data:
+            source = "DataWriter"
+        self.sock.send("+log|{0}|Portopalo DAQ|{1}|{2}\r\n".format(
+            source, log_level, data))
+        return blob
+
+    def finish(self):
+        self.sock.close()
+
+
+def main():
+    from docopt import docopt
+    args = docopt(__doc__)
+
+    ligier_ip = args['-l']
+    ligier_port = int(args['-p'])
+    logio_ip = args['-x']
+    logio_port = int(args['-q'])
+
+    pipe = Pipeline()
+    pipe.attach(
+        CHPump,
+        host=ligier_ip,
+        port=ligier_port,
+        tags='MSG',
+        timeout=7 * 60 * 60 * 24,
+        max_queue=500)
+    pipe.attach(LogIO, logio_ip=logio_ip, logio_port=logio_port)
+    pipe.drain()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/scripts/msg_dumper.py b/scripts/msg_dumper.py
new file mode 100755
index 0000000000000000000000000000000000000000..66e9d2d0c8d6c1e6f5b9c62d63fc21b4e1f1e61a
--- /dev/null
+++ b/scripts/msg_dumper.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# coding=utf-8
+# Filename: msg_dumper.py
+# Author: Tamas Gal <tgal@km3net.de>
+# vim: ts=4 sw=4 et
+"""
+Dumps MSG data from Ligier to a file.
+
+Usage:
+    msg_dumper.py [options]
+    msg_dumper.py (-h | --help)
+
+Options:
+    -l LIGIER_IP    The IP of the ligier [default: 127.0.0.1].
+    -p LIGIER_PORT  The port of the ligier [default: 5553].
+    -f LOG_FILE     Log file to dump the messages [default: MSG.log].
+    -h --help       Show this screen.
+
+"""
+import os
+import time
+
+from km3pipe import Pipeline, Module
+from km3pipe.io import CHPump
+
+
+class MSGDumper(Module):
+    def configure(self):
+        self.filename = self.get('filename', default='MSG.log')
+        self.fobj = open(os.path.abspath(self.filename), 'a')
+
+    def process(self, blob):
+        data = blob['CHData'].decode()
+        source = "Other"
+        if " F0" in data:
+            source = "DataFilter"
+        if " Q0" in data:
+            source = "DataQueue"
+        if " W0" in data:
+            source = "DataWriter"
+
+        entry = "{} [{}]: {}\n".format(
+            os.path.basename(self.filename), source, data)
+        self.fobj.write(entry)
+        return blob
+
+    def finish(self):
+        self.fobj.close()
+
+
+def main():
+    from docopt import docopt
+    args = docopt(__doc__)
+
+    ligier_ip = args['-l']
+    ligier_port = int(args['-p'])
+    filename = args['-f']
+
+    pipe = Pipeline()
+    pipe.attach(
+        CHPump,
+        host=ligier_ip,
+        port=ligier_port,
+        tags='MSG',
+        timeout=7 * 60 * 60 * 24,
+        max_queue=500)
+    pipe.attach(MSGDumper, filename=filename)
+    pipe.drain()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/scripts/trigger_rates.py b/scripts/trigger_rates.py
index bc7a207dbd3ceb6e982ca1a3f13b57f053f40b88..b16505151632099ed53f379d1209951144c7c273 100755
--- a/scripts/trigger_rates.py
+++ b/scripts/trigger_rates.py
@@ -21,6 +21,7 @@ from __future__ import division, print_function
 
 from datetime import datetime
 from collections import defaultdict, deque, OrderedDict
+from itertools import chain
 import sys
 from io import BytesIO
 from os.path import join
@@ -35,11 +36,10 @@ import matplotlib.dates as md
 
 import km3pipe as kp
 from km3pipe.config import Config
-from km3pipe.io.daq import (DAQPreamble, DAQEvent,
-                            is_3dshower, is_3dmuon, is_mxshower)
+from km3pipe.io.daq import (DAQPreamble, DAQEvent, is_3dshower, is_3dmuon,
+                            is_mxshower)
 import km3pipe.style
 
-
 VERSION = "1.0"
 km3pipe.style.use('km3pipe')
 
@@ -47,8 +47,8 @@ km3pipe.style.use('km3pipe')
 class TriggerRate(kp.Module):
     def configure(self):
         self.plots_path = self.require('plots_path')
-        self.interval = self.get("interval",
-                                 default=self.trigger_rate_sampling_period())
+        self.interval = self.get(
+            "interval", default=self.trigger_rate_sampling_period())
         self.filename = self.get("filename", default="trigger_rates")
         self.with_minor_ticks = self.get("with_minor_ticks", default=False)
         print("Update interval: {}s".format(self.interval))
@@ -56,18 +56,25 @@ class TriggerRate(kp.Module):
         self.trigger_rates = OrderedDict()
 
         self.styles = {
-            "xfmt": md.DateFormatter('%Y-%m-%d %H:%M'),
-            "general": dict(markersize=6,  linestyle='None'),
-            "Overall": dict(marker='D',
-                            markerfacecolor='None',
-                            markeredgecolor='tomato',
-                            markeredgewidth=1),
-            "3DMuon": dict(marker='X', markerfacecolor='dodgerblue'),
-            "MXShower": dict(marker='v', markerfacecolor='orange'),
-            "3DShower": dict(marker='^', markerfacecolor='olivedrab'),
+            "xfmt":
+            md.DateFormatter('%Y-%m-%d %H:%M'),
+            "general":
+            dict(markersize=6, linestyle='None'),
+            "Overall":
+            dict(
+                marker='D',
+                markerfacecolor='None',
+                markeredgecolor='tomato',
+                markeredgewidth=1),
+            "3DMuon":
+            dict(marker='X', markerfacecolor='dodgerblue'),
+            "MXShower":
+            dict(marker='v', markerfacecolor='orange'),
+            "3DShower":
+            dict(marker='^', markerfacecolor='olivedrab'),
         }
 
-        queue_len = int(60*24/(self.interval/60))
+        queue_len = int(60 * 24 / (self.interval / 60))
         for trigger in ["Overall", "3DMuon", "MXShower", "3DShower"]:
             self.trigger_rates[trigger] = deque(maxlen=queue_len)
 
@@ -75,6 +82,10 @@ class TriggerRate(kp.Module):
         self.thread = threading.Thread(target=self.plot).start()
         self.lock = threading.Lock()
 
+        self.run_changes = []
+        self.current_run_id = 0
+        self.det_id = 0
+
     def process(self, blob):
         if not str(blob['CHPrefix'].tag) == 'IO_EVT':
             return blob
@@ -85,6 +96,10 @@ class TriggerRate(kp.Module):
         data_io = BytesIO(data)
         preamble = DAQPreamble(file_obj=data_io)  # noqa
         event = DAQEvent(file_obj=data_io)
+        self.det_id = event.header.det_id
+        if event.header.run > self.current_run_id:
+            self.current_run_id = event.header.run
+            self._log_run_change()
         tm = event.trigger_mask
         with self.lock:
             self.trigger_counts["Overall"] += 1
@@ -96,6 +111,27 @@ class TriggerRate(kp.Module):
 
         return blob
 
+    def _log_run_change(self):
+        self.print("New run: %s" % self.current_run_id)
+        now = datetime.utcnow()
+        self.run_changes.append((now, self.current_run_id))
+
+    def _get_run_changes_to_plot(self):
+        self.print("Checking run changes out of range")
+        overall_rates = self.trigger_rates['Overall']
+        if not overall_rates:
+            self.print("No trigger rates logged  yet, nothing to remove.")
+            return
+        self.print("  all:     {}".format(self.run_changes))
+        run_changes_to_plot = []
+        min_timestamp = min(overall_rates)[0]
+        self.print("  earliest timestamp to plot: {}".format(min_timestamp))
+        for timestamp, run in self.run_changes:
+            if timestamp > min_timestamp:
+                run_changes_to_plot.append((timestamp, run))
+        self.print("  to plot: {}".format(run_changes_to_plot))
+        return run_changes_to_plot
+
     def plot(self):
         while self.run:
             time.sleep(self.interval)
@@ -119,12 +155,35 @@ class TriggerRate(kp.Module):
                 self.log.warning("Empty rates, skipping...")
                 continue
             timestamps, trigger_rates = zip(*rates)
-            ax.plot(timestamps, trigger_rates,
-                    **self.styles[trigger],
-                    **self.styles['general'],
-                    label=trigger)
-        ax.set_title("Trigger Rates\n{0} UTC"
-                     .format(datetime.utcnow().strftime("%c")))
+            ax.plot(
+                timestamps,
+                trigger_rates,
+                **self.styles[trigger],
+                **self.styles['general'],
+                label=trigger)
+
+        run_changes_to_plot = self._get_run_changes_to_plot()
+        self.print("Recorded run changes: {}".format(run_changes_to_plot))
+        all_rates = [r for d, r in chain(*self.trigger_rates.values())]
+        if not all_rates:
+            self.log.warning("Empty rates, skipping...")
+            return
+        min_trigger_rate = min(all_rates)
+        max_trigger_rate = max(all_rates)
+        for run_start, run in run_changes_to_plot:
+            plt.text(
+                run_start, (min_trigger_rate + max_trigger_rate) / 2,
+                "\nRUN %s  " % run,
+                rotation=60,
+                verticalalignment='top',
+                fontsize=8,
+                color='gray')
+            ax.axvline(
+                run_start, color='#ff0f5b', linestyle='--', alpha=0.8)  # added
+
+        ax.set_title("Trigger Rates for DetID-{0}\n{1} UTC".format(
+            self.det_id,
+            datetime.utcnow().strftime("%c")))
         ax.set_xlabel("time")
         ax.set_ylabel("trigger rate [Hz]")
         ax.xaxis.set_major_formatter(self.styles["xfmt"])
@@ -175,11 +234,13 @@ def main():
     ligier_port = int(args['-p'])
 
     pipe = kp.Pipeline()
-    pipe.attach(kp.io.ch.CHPump, host=ligier_ip,
-                port=ligier_port,
-                tags='IO_EVT',
-                timeout=60*60*24*7,
-                max_queue=200000)
+    pipe.attach(
+        kp.io.ch.CHPump,
+        host=ligier_ip,
+        port=ligier_port,
+        tags='IO_EVT',
+        timeout=60 * 60 * 24 * 7,
+        max_queue=200000)
     pipe.attach(TriggerRate, interval=300, plots_path=plots_path)
     pipe.drain()
 
diff --git a/scripts/ztplot.py b/scripts/ztplot.py
index f3aab3c22781be716ddb0915ce85b3419729894c..82b8b0cd01fb2050dc029a56adb3920f601513bd 100755
--- a/scripts/ztplot.py
+++ b/scripts/ztplot.py
@@ -135,13 +135,17 @@ class ZTPlot(Module):
                 label.set_rotation(45)
 
             if idx % n_cols == 0:
-                ax.set_ylabel('time [ns]', fontsize=fontsize)
+                ax.set_ylabel('z [m]', fontsize=fontsize)
             if idx >= len(axes) - n_cols:
-                ax.set_xlabel('z [m]', fontsize=fontsize)
+                ax.set_xlabel('time [ns]', fontsize=fontsize)
 
+        print
         plt.suptitle(
-            "FrameIndex {0}, TriggerCounter {1}\n{2} UTC".format(
-                event_info.frame_index, event_info.trigger_counter,
+            "z-t-Plot for DetID-{0}, Run {1}, FrameIndex {2}, "
+            "TriggerCounter {3}, Overlays {4}\n{5} UTC".format(
+                event_info.det_id[0], event_info.run_id[0],
+                event_info.frame_index[0], event_info.trigger_counter[0],
+                event_info.overlays[0],
                 datetime.utcfromtimestamp(event_info.utc_seconds)),
             fontsize=fontsize,
             y=1.05)