diff --git a/Ccs/tools/simulators/picontrtest.py b/Ccs/tools/simulators/picontrtest.py
new file mode 100644
index 0000000000000000000000000000000000000000..8cb584c11157f5f3ee06a55720dbe704f5ad7291
--- /dev/null
+++ b/Ccs/tools/simulators/picontrtest.py
@@ -0,0 +1,164 @@
+import matplotlib.pyplot as plt
+import numpy as np
+import sys
+
+sys.path.insert(0, '../..')
+import thermal_control as tmc
+import thermal_model as tmo
+
+T_REF = -116.
+PI_ALGO = 1
+
+OFFSET = 30
+COEFFP = 50
+COEFFI = 0.001
+EXEC_PER = 40
+
+MODSTEP = 5
+
+save = 0
+sufx = '_addheat'
+showpwr = 1
+
+mod = tmo.ThermalModel(-125, record=True, step=MODSTEP)
+mod.sigma_T = 0.0005
+mod.mass = 3.
+mod.rad_area = .25
+mod.T0 = -125
+mod.t_l = 60
+mod.t_d = 5
+
+mod.set_delay_func(tmo.sigmoid)
+# plt.plot(mod.heat_distr)
+
+tctrl = tmc.ThermalController(T_REF, COEFFP, COEFFI, OFFSET, EXEC_PER, model=mod, pi=PI_ALGO, deltaTmax=1)
+tctrl.MAXDELTAVOLTAGE = 0.2
+
+hours = 42
+NCTRLSTEPS = int(hours * 3600 / (EXEC_PER / 8))  # int(sys.argv[2])
+modstepsperctrl = int(EXEC_PER / 8 / MODSTEP)
+
+print('run thctrl for {} steps, with {} s period ({} s)'.format(NCTRLSTEPS, EXEC_PER / 8, NCTRLSTEPS * EXEC_PER / 8))
+print('{} model iterations per control step'.format(modstepsperctrl))
+
+simlog = []
+t = 0
+TINIT = mod.T0
+Toffset = 0
+glitches = ()#tmo.rng.choice(range(NCTRLSTEPS), 10)
+drift = 1
+
+for i in range(NCTRLSTEPS):
+    for j in range(modstepsperctrl):
+        mod.evolve(t)
+        t += MODSTEP
+
+    # insert T glitches at random positions
+    if i in glitches:
+        if i % 2:
+            tctrl._step(t, with_model=True, glitch=20)
+        else:
+            tctrl._step(t, with_model=True, glitch=-20)
+    else:
+        tctrl._step(t, with_model=True, glitch=Toffset)
+
+    # sudden T offset for some time
+    # if int(t) == 40000:
+    #     print(t)
+    #     mod.inst_heat = 2.5
+    # if int(t) == 80000:
+    #     print(t)
+    #     mod.inst_heat = 1
+    # if int(t) == 100000:
+    #     print(t)
+    #     mod.inst_heat = 0
+
+    # if int(t) == 50000:
+    #     print(t)
+    #     Toffset = 2
+    #
+    # if int(t) == 51800:
+    #     print(t)
+    #     Toffset = 0
+
+    if i % 1000 == 0:
+        print(i, 'steps of', NCTRLSTEPS)
+
+    # drift in equilibrium temperature
+    if drift:
+        mod.T0 = TINIT * (1 - 0.05 * np.cos(1 * i * 2 * np.pi / NCTRLSTEPS))
+
+    simlog.append((t, mod.T0, mod.T, mod.htr_pwr, mod.inst_heat))
+
+simlog = np.array(simlog).T
+print(mod.T0)
+
+r = np.array(tctrl.log).T
+mr = np.array(mod.log).T
+
+if not showpwr:
+    fig, ax = plt.subplots(2, 1, sharex=True, gridspec_kw={'height_ratios': [2, 1]}, figsize=(10, 6))
+    ax1, ax2 = ax
+
+    ax1.plot(r[0], r[2], color='tab:blue', label='$T$')
+    ax1.axhline(T_REF, c='grey', ls='--', label=r'$T_\mathrm{ref}$')
+    ax1.tick_params(bottom=False)
+    ax1.set_ylim((-118, -114))
+    ax1.grid()
+    # ax1.plot(simlog[0], simlog[2], color='tab:pink', ls='--', alpha=1, label=r'$T_\mathrm{act}$')
+    ax1.set_ylabel('temperature [°C]')
+    ax1.legend()
+    ax2.plot(r[0], r[3], color='tab:orange', label=r'$V_\mathrm{ctrl}$')
+    ax2.set_xlabel('time [s]')
+    ax2.set_ylabel(r'$U_\mathrm{ctrl}$ [V]')
+    ax2.set_ylim((0, 3))
+    ax2.grid()
+else:
+    fig, ax = plt.subplots(4, 1, sharex=True, gridspec_kw={'height_ratios': [2, 1, 1, 1]}, figsize=(12, 9))
+    ax1, ax2, ax3, ax4 = ax
+
+    ax1.plot(r[0], r[2], color='tab:blue', label='$T$')
+    ax1.plot(simlog[0], simlog[2], color='blue', alpha=.2, label=r'$T_\mathrm{mod}$')
+    ax1.axhline(T_REF, c='grey', ls='--', label=r'$T_\mathrm{ref}$')
+    ax1.tick_params(bottom=False)
+    ax1.set_ylim((-118, -114))
+    ax1.grid()
+    ax11 = ax1.twinx()
+    ax11.set_ylabel('$T_0$ [°C]', color='tab:green')
+    ax11.plot(simlog[0], simlog[1], color='tab:green', ls='--', alpha=0.5)
+    ax11.tick_params(axis='y', colors='tab:green')
+    ax1.set_ylabel('temperature [°C]')
+    ax1.legend()
+    ax2.plot(r[0], r[3], color='tab:orange', label=r'$V_\mathrm{ctrl}$')
+    ax2.set_ylabel(r'$U_\mathrm{ctrl}$ [V]')
+    ax2.set_ylim((0, 3))
+    ax2.tick_params(bottom=False)
+    ax2.grid()
+    ax3.plot(simlog[0], simlog[3], color='tab:red', ls='--', alpha=1, label='htr')
+    ax3.plot(simlog[0], simlog[4], color='tab:purple', ls='--', alpha=1, label='inst')
+    ax3.plot(mr[0], mr[3], color='tab:cyan', ls='--', alpha=1, label='cool')
+    ax3.set_ylabel('$P$ [W]')
+    ax3.set_ylim((-1, 10))
+    ax3.tick_params(bottom=False)
+    ax3.grid()
+    ax3.legend()
+    if PI_ALGO:
+        ax4.axhline(OFFSET, color='tab:brown', label='O', zorder=0)
+        ax4.plot(r[0], r[5], color='tab:pink', label='P', zorder=2)
+        ax4.plot(r[0], r[4], color='tab:olive', label='I', zorder=1)
+        ax4.legend()
+    ax4.set_ylabel('percentage')
+    ax4.set_xlabel('time [s]')
+    ax4.grid()
+
+plt.tight_layout()
+fig.subplots_adjust(hspace=0.075)
+
+if save:
+    plt.savefig('/home/marko/space/smile/OBSW/Documentation/htrctrlsim/picrtl_sim_{:.0f}s_p{:d}_i{:.5f}{}.pdf'.format(EXEC_PER / 8, COEFFP, COEFFI, sufx))
+    plt.savefig('/home/marko/space/smile/OBSW/Documentation/htrctrlsim/picrtl_sim_{:.0f}s_p{:d}_i{:.5f}{}.png'.format(EXEC_PER / 8, COEFFP, COEFFI, sufx), dpi=200)
+
+plt.show()
+
+# tctrl.save_log(fname)
+# print('END, data saved to {}'.format(fname), time.time())