diff --git a/app/gui/oven_control/autocook.cpp b/app/gui/oven_control/autocook.cpp
index b3d716d..43aa13f 100644
--- a/app/gui/oven_control/autocook.cpp
+++ b/app/gui/oven_control/autocook.cpp
@@ -1,5 +1,6 @@
 #include "autocook.h"
 #include "soundplayer.h"
+#include "haccp.h"
 
 AutoCook::AutoCook() : currentStepIndex(0), done_(false), doorOpened(false), checkingCoreTemp(false)
 {
@@ -12,7 +13,6 @@ AutoCook::AutoCook(Cook cook) : AutoCook()
     for (int idx = 0; idx < this->cook.steps.size(); idx++)
         this->cook.steps[idx].time *= 1000;
 
-
     startStep();
 }
 
@@ -71,6 +71,19 @@ void AutoCook::startStep()
         break;
     }
 
+    HACCP::autoStart(cook.root);
+
+    for (int i = 0; i < 5; i++)
+    {
+        Define::CookConfigType type = cook.configs[i].type;
+        if (type == Define::ConfigNotUsed)
+            continue;
+
+        HACCP::setConfig(type, cook.configs[i].current);
+    }
+
+    HACCP::setStep(currentStep.type);
+
     advance();
 }
 
@@ -81,6 +94,8 @@ void AutoCook::nextStep()
     CookStep &currentStep = cook.steps[currentStepIndex];
     CookStep &nextStep = cook.steps[currentStepIndex + 1];
 
+    HACCP::setStep(nextStep.type);
+
     Define::StepClass currentClass = Define::classify(currentStep.type);
     Define::StepClass nextClass = Define::classify(nextStep.type);
 
@@ -315,6 +330,7 @@ bool AutoCook::advance()
             {
                 done_ = true;
 
+                HACCP::done();
                 SoundPlayer::playStop();
 
                 if (currentStep.dehumidification)
@@ -360,6 +376,7 @@ bool AutoCook::advance()
             {
                 done_ = true;
 
+                HACCP::done();
                 SoundPlayer::playStop();
 
                 if (currentStep.dehumidification)
diff --git a/app/gui/oven_control/autocookwindow.cpp b/app/gui/oven_control/autocookwindow.cpp
index 069406c..cfd60ca 100644
--- a/app/gui/oven_control/autocookwindow.cpp
+++ b/app/gui/oven_control/autocookwindow.cpp
@@ -17,6 +17,7 @@
 #include "autocookselectionpopup.h"
 #include "autocookcheckconfigwindow.h"
 #include "manualviewerdlg.h"
+#include "haccp.h"
 
 AutoCookWindow::AutoCookWindow(QWidget *parent, Cook cook) :
     QMainWindow(parent),
@@ -110,6 +111,7 @@ AutoCookWindow::AutoCookWindow(QWidget *parent, Cook cook) :
 AutoCookWindow::~AutoCookWindow()
 {
     Oven::getInstance()->stop();
+    HACCP::done();
 
     delete ui;
 }
@@ -749,6 +751,9 @@ void AutoCookWindow::startProcess(int process)
 
         SoundPlayer::playStart();
 
+        HACCP::autoStart(cook.root);
+        HACCP::setProcess(Define::MakeCrisper);
+
         break;
     }
     case Define::KeepWarm:
@@ -756,6 +761,9 @@ void AutoCookWindow::startProcess(int process)
         processSelected = false;
         selectedProcess = (Define::Process) process;
 
+        HACCP::autoStart(cook.root);
+        HACCP::setProcess(Define::KeepWarm);
+
         KeepWarmPopup *p = new KeepWarmPopup(this);
         p->showFullScreen();
         p->raise();
@@ -778,6 +786,7 @@ void AutoCookWindow::checkProcess()
         Oven *oven = Oven::getInstance();
         if (oven->currentTemp() >= oven->temp())
         {
+            HACCP::done();
             SoundPlayer::playStop();
             oven->stopCooking();
             checkProcessTimer.stop();
diff --git a/app/gui/oven_control/define.cpp b/app/gui/oven_control/define.cpp
index 42edc7e..13de466 100644
--- a/app/gui/oven_control/define.cpp
+++ b/app/gui/oven_control/define.cpp
@@ -566,3 +566,36 @@ QString Define::name(Define::Mode mode)
         return "";
     }
 }
+
+QString Define::name(Define::CookConfigType type)
+{
+    switch (type)
+    {
+    case Brightness:
+        return "brightness";
+    case BurnDegree:
+        return "cookinglevel";
+    case SoftBoilDegree:
+        return "boiledlevel";
+    case PieceSize:
+        return "size";
+    case CrispyDegree:
+        return "crispy1";
+    case MoistDegree:
+        return "moisten1";
+    case Thickness:
+        return "thickness";
+    case Humidity:
+        return "humidity";
+    case Temperature:
+        return "temperature";
+    case Time:
+        return "cookingtime";
+    case CoreTemperature:
+        return "coretemperature";
+    case Thermometer:
+        return "coretemperaturesensor";
+    default:
+        return "unknownconfig";
+    }
+}
diff --git a/app/gui/oven_control/define.h b/app/gui/oven_control/define.h
index c9799bc..a8f9992 100644
--- a/app/gui/oven_control/define.h
+++ b/app/gui/oven_control/define.h
@@ -9,7 +9,7 @@
 
 // 0 for normal
 // 1 for premium
-#define MODEL_GRADE 0
+#define MODEL_GRADE 1
 
 namespace Define
 {
@@ -52,6 +52,7 @@ namespace Define
     QString iconActiveted(CookConfigType type);
     QString minimum(CookConfigType type);
     QString maximum(CookConfigType type);
+    QString name(CookConfigType type);
 
     enum StepClass
     {
diff --git a/app/gui/oven_control/engineermenuwindow.cpp b/app/gui/oven_control/engineermenuwindow.cpp
index dfe2629..5c11de0 100644
--- a/app/gui/oven_control/engineermenuwindow.cpp
+++ b/app/gui/oven_control/engineermenuwindow.cpp
@@ -11,6 +11,7 @@
 #include "fileprocessor.h"
 #include "fileprocessdlg.h"
 #include "usbcheckpopupdlg.h"
+#include "haccp.h"
 
 #include <QKeyEvent>
 
@@ -28,10 +29,14 @@ EngineerMenuWindow::EngineerMenuWindow(QWidget *parent) :
 
     foreach (QPushButton *button, findChildren<QPushButton *>())
         connect(button, &QPushButton::pressed, SoundPlayer::playClick);
+
+    HACCP::engineeringStart();
 }
 
 EngineerMenuWindow::~EngineerMenuWindow()
 {
+    HACCP::done();
+
     delete ui;
 }
 
diff --git a/app/gui/oven_control/haccp.cpp b/app/gui/oven_control/haccp.cpp
new file mode 100644
index 0000000..ec5bd41
--- /dev/null
+++ b/app/gui/oven_control/haccp.cpp
@@ -0,0 +1,651 @@
+#include "haccp.h"
+
+#include <QDateTime>
+#include <QDir>
+#include <QTextStream>
+#include <QTimer>
+
+#include "unistd.h"
+
+#include "stringer.h"
+#include "oven.h"
+
+namespace
+{
+
+enum Type { Invalid, Manual, Auto, Multi, Wash, Engineering };
+
+struct Stamp
+{
+    QDateTime time;
+    int temp;
+    int targetCoreTemp;
+    int curCoreTemp;
+    bool door;
+    QString caption;
+};
+
+struct Config
+{
+    Define::CookConfigType type;
+    int level;
+};
+
+struct Data
+{
+    QDateTime startedAt;
+    Type type;
+    QString autoRoot;
+    QList<Config> autoConfig;
+    int washType;
+    QList<Stamp> records;
+} data;
+
+QDateTime initializedTime;
+int processCount;
+QTimer checkTimer;
+QTime lastStampedTime;
+
+Define::Mode lastMode;
+int lastFan;
+bool lastDoor;
+bool lastDamper;
+bool lastSideNozzle;
+bool lastCoreTempEnabled;
+
+int figureProcessCount();
+void initData(Type type);
+void start();
+void stamp(QString caption = "");
+void stampStart();
+void stampMode(Define::Mode mode);
+void stampDamper();
+void stampSideNozzle();
+void stampEnd();
+void stampError(QString error);
+void saveData();
+void check();
+}
+
+namespace HACCP
+{
+
+void init()
+{
+    // make the directory if it doesn't exist
+    QDir().mkpath("/prime/haccp");
+
+    initializedTime = QDateTime::currentDateTime();
+    processCount = figureProcessCount() + 1;
+
+    checkTimer.setInterval(100);
+    QObject::connect(&checkTimer, &QTimer::timeout, check);
+
+    Oven *oven = Oven::getInstance();
+    QObject::connect(oven, &Oven::changed, check);
+}
+
+void autoStart(const QString &path)
+{
+    initData(Auto);
+    data.autoRoot = path;
+
+    start();
+}
+
+void manualStart()
+{
+    qDebug() << __FUNCTION__;
+
+    initData(Manual);
+    start();
+}
+
+void multiStart()
+{
+    initData(Multi);
+    start();
+}
+
+void washStart(int type)
+{
+    initData(Wash);
+    data.washType = type;
+}
+
+void engineeringStart()
+{
+    initData(Engineering);
+}
+
+void done()
+{
+    qDebug() << __FUNCTION__;
+    if (data.type == Invalid)
+        return;
+
+    stampEnd();
+    saveData();
+}
+
+void error(QString code)
+{
+    if (data.type == Invalid)
+        return;
+
+    stampError(code);
+    saveData();
+}
+
+void setConfig(Define::CookConfigType type, int level)
+{
+    data.autoConfig.append(Config{type, level});
+}
+
+void setStep(Define::StepType type)
+{
+    switch (type)
+    {
+    case Define::Preheat:
+        stamp("preheat");
+        break;
+    case Define::Load:
+        stamp("load");
+        break;
+    case Define::PutThermometer:
+        stamp("therometer");
+        break;
+    case Define::Cut:
+        stamp("cut");
+        break;
+    case Define::Pour:
+        stamp("pour");
+        break;
+    case Define::Bake:
+        stamp("bake");
+        break;
+    case Define::Dry:
+        stamp("dry");
+        break;
+    case Define::Ferment:
+        stamp("ferment");
+        break;
+    case Define::BlowSteam:
+        stamp("steaming");
+        break;
+    case Define::CoolDown:
+        stamp("cooldown");
+        break;
+    case Define::Steam:
+        stamp("steam");
+        break;
+    case Define::Roast:
+        stamp("roasting");
+        break;
+    case Define::Boil:
+        stamp("boil");
+        break;
+    case Define::Thicken:
+        stamp("thicken");
+        break;
+    case Define::WarmUp:
+        stamp("warmup");
+        break;
+    case Define::MakeCrispy:
+        stamp("crispy2");
+        break;
+    case Define::Finish:
+        stamp("finish");
+        break;
+    case Define::Damp:
+        stamp("damp");
+        break;
+    case Define::Defer:
+        stamp("defer");
+        break;
+    case Define::Grill:
+        stamp("grill");
+        break;
+    case Define::End:
+        stamp("end");
+        break;
+    case Define::Burn:
+        stamp("burn");
+        break;
+    case Define::Fry:
+        stamp("fry");
+        break;
+    case Define::HeatUp:
+        stamp("heatup");
+        break;
+    case Define::Ripen:
+        stamp("ripen");
+        break;
+    case Define::RipenKeep:
+        stamp("ripenkeep");
+        break;
+    case Define::BoilSteadily:
+        stamp("boilsteadily");
+        break;
+    case Define::CookGratin:
+        stamp("cookgratin");
+        break;
+    case Define::Brown:
+        stamp("brown");
+        break;
+    case Define::Simmer:
+        stamp("simmer");
+        break;
+    case Define::Moisten:
+        stamp("moisten2");
+        break;
+    }
+}
+
+void setProcess(Define::Process type)
+{
+    switch (type)
+    {
+    case Define::CookAgain:
+        stamp("again");
+        break;
+    case Define::MakeCrisper:
+        stamp("crispy3");
+        break;
+    case Define::KeepWarm:
+        stamp("heatreserve");
+        break;
+    }
+}
+
+}
+
+
+namespace
+{
+
+QString path()
+{
+    return "/prime/haccp";
+}
+
+QString path(int year)
+{
+    return QString("/prime/haccp/%1").arg(year, 4, 10, QLatin1Char('0'));
+}
+
+QString path(int year, int month)
+{
+    return QString("/prime/haccp/%1/%2")
+            .arg(year, 4, 10, QLatin1Char('0'))
+            .arg(month, 2, 10, QLatin1Char('0'));
+}
+
+QString path(int year, int month, int day)
+{
+    return QString("/prime/haccp/%1/%2/%3")
+            .arg(year, 4, 10, QLatin1Char('0'))
+            .arg(month, 2, 10, QLatin1Char('0'))
+            .arg(day, 2, 10, QLatin1Char('0'));
+}
+
+QString path(int year, int month, int day, int proc)
+{
+    return QString("/prime/haccp/%1/%2/%3/%4")
+            .arg(year, 4, 10, QLatin1Char('0'))
+            .arg(month, 2, 10, QLatin1Char('0'))
+            .arg(day, 2, 10, QLatin1Char('0'))
+            .arg(proc);
+}
+
+int figureLatest(const QString &path, QDir::Filter filter)
+{
+    QDir root(path);
+    if (!root.exists())
+        return -1;
+
+    QStringList list = root.entryList(filter);
+    if (list.empty())
+        return -1;
+
+    int latest = -1;
+    foreach (QString e, list)
+    {
+        bool ok;
+        int num = e.toInt(&ok);
+        if (!ok)
+            continue;
+
+        if (num > latest)
+            latest = num;
+    }
+
+    return latest;
+}
+
+int figureLatestDir(const QString &path)
+{
+    return figureLatest(path, QDir::AllDirs);
+}
+
+int figureLatestFile(const QString &path)
+{
+    return figureLatest(path, QDir::Files);
+}
+
+int figureProcessCount()
+{
+    int year = figureLatestDir(path());
+    if (year < 0)
+        return 0;
+
+    int month = figureLatestDir(path(year));
+    if (month < 0)
+        return 0;
+
+    int day = figureLatestDir(path(year, month));
+    if (day < 0)
+        return 0;
+
+    int proc = figureLatestFile(path(year, month, day));
+    if (proc < 0)
+        return 0;
+
+    return proc;
+}
+
+void initData(Type type)
+{
+    data.type = type;
+    data.autoRoot = QString();
+    data.autoConfig.clear();
+    data.washType = 0;
+    data.records.clear();
+
+    if (initializedTime.isValid())
+    {
+        data.startedAt = initializedTime;
+        stampStart();
+    }
+    else
+        data.startedAt = QDateTime::currentDateTime();
+
+}
+
+void start()
+{
+    qDebug() << __FUNCTION__;
+
+    lastMode = Define::InvalidMode;
+    lastFan = -1;
+    lastDoor = false;
+    lastDamper = false;
+    lastSideNozzle = false;
+    lastCoreTempEnabled = false;
+
+    Oven *oven = Oven::getInstance();
+    stampMode(oven->mode());
+
+    checkTimer.start();
+}
+
+void stamp(QString caption)
+{
+    qDebug() << __FUNCTION__;
+
+    if (data.type == Invalid)
+        return;
+
+    Oven *oven = Oven::getInstance();
+
+    Stamp s;
+    s.time = QDateTime::currentDateTime();
+    s.temp = oven->currentTemp();
+
+    if (oven->interTempEnabled())
+    {
+        s.targetCoreTemp = oven->interTemp();
+        s.curCoreTemp = oven->currentInterTemp();
+    }
+    else
+    {
+        s.targetCoreTemp = 0;
+        s.curCoreTemp = 0;
+    }
+
+    s.door = oven->door();
+    s.caption = caption;
+
+    data.records.append(s);
+
+    lastDoor = oven->door();
+    lastStampedTime.start();
+}
+
+void stampStart()
+{
+    qDebug() << __FUNCTION__;
+
+    stamp("START (POWER ON)");
+}
+
+void stampMode(Define::Mode mode)
+{
+    qDebug() << __FUNCTION__;
+
+    switch (mode)
+    {
+    case Define::SteamMode:
+        stamp("Mode STEAM");
+        break;
+    case Define::CombiMode:
+        stamp("Mode COMBI");
+        break;
+    case Define::DryMode:
+        stamp("Mode DRY");
+        break;
+    }
+
+    lastMode = mode;
+}
+
+void stampFan(int fan)
+{
+    qDebug() << __FUNCTION__;
+
+    lastFan = fan;
+    stamp(QString("FAN = %1").arg(fan));
+}
+
+void stampDamper()
+{
+    qDebug() << __FUNCTION__;
+
+    stamp("DAMPER");
+}
+
+void stampSideNozzle()
+{
+    qDebug() << __FUNCTION__;
+
+    stamp("SIDE NOZZLE");
+}
+
+void stampEnd()
+{
+    qDebug() << __FUNCTION__;
+
+    stamp("END");
+}
+
+void stampError(QString error)
+{
+    stamp(QString("END (%1)").arg(error));
+}
+
+void saveData()
+{
+    qDebug() << __FUNCTION__;
+
+    if (data.records.isEmpty())
+    {
+        data.type = Invalid;
+        return;
+    }
+
+    QDate date = data.startedAt.date();
+
+    QDir().mkpath(path(date.year(), date.month(), date.day()));
+
+    QString p = path(date.year(), date.month(), date.day(), processCount);
+
+    QFile file(p);
+    if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly))
+    {
+        qDebug() << "Failed to open";
+        return;
+    }
+
+    QTextStream stream(&file);
+    stream.setCodec("UTF-8");
+
+    // TODO: fill them
+    stream << "No.," << processCount << "\n";
+    stream << "Typ," << "\n";
+    stream << "Serial No.," << "\n";
+    stream << "Version," << "\n";
+    stream << "Time," << data.startedAt.toString("yyyy.MM.dd HH:mm:ss") << "\n";
+
+    stream << "Progr,";
+    switch (data.type)
+    {
+    case Manual:
+        stream << 1;
+        break;
+    case Auto:
+        stream << data.autoRoot;
+        break;
+    case Multi:
+        stream << 4;
+        break;
+    case Wash:
+        stream << 6 << "-" << data.washType;
+        break;
+    case Engineering:
+        stream << 999;
+        break;
+    }
+    stream << "\n";
+
+    stream << "\n"
+           << "#1,Time\n"
+           << "#2,Inner Tank Temp\n"
+           << "#3,Food Temp (Target)\n"
+           << "#4,Food Temp\n"
+           << "#5,Door Open / Closed\n";
+
+    stream << "\n";
+
+    stream << "#1,#2,#3,#4,#5\n";
+    for (int i = 0; i < data.records.size(); i++)
+    {
+        const Stamp &s = data.records.at(i);
+        if (!s.caption.isEmpty() && i+1 < data.records.size())
+        {
+            Stamp &n = data.records[i+1];
+
+            int secs = data.startedAt.secsTo(s.time);
+            int nextSecs = data.startedAt.secsTo(n.time);
+            if (secs == nextSecs
+                    && s.temp == n.temp
+                    && s.targetCoreTemp == n.targetCoreTemp
+                    && s.curCoreTemp == n.curCoreTemp
+                    && s.door == n.door)
+            {
+                if (n.caption.isEmpty())
+                    n.caption = s.caption;
+                else
+                    n.caption = QString("%1\n%2").arg(s.caption).arg(n.caption);
+
+                continue;
+            }
+        }
+
+        if (!s.caption.isEmpty())
+            stream << s.caption << "\n";
+
+        stream << Stringer::timeSecs(data.startedAt.secsTo(s.time)) << ","
+               << s.temp << ","
+               << s.targetCoreTemp << ","
+               << s.curCoreTemp << ","
+               << (s.door ? "O\n" : "C\n");
+    }
+
+    if (!data.autoConfig.isEmpty())
+    {
+        foreach (Config c, data.autoConfig)
+            stream << Define::name(c.type)
+                   << " = "
+                   << c.level << "\n";
+    }
+
+    stream.flush();
+    file.close();
+    sync();
+
+    initializedTime = QDateTime();
+    processCount++;
+
+    data.type = Invalid;
+
+    checkTimer.stop();
+}
+
+void check()
+{
+    switch (data.type)
+    {
+    case Invalid:
+    case Wash:
+    case Engineering:
+        return;
+    default:
+        break;
+    }
+
+    Oven *oven = Oven::getInstance();
+
+    if (oven->mode() != lastMode)
+        stampMode(oven->mode());
+
+    if (oven->fan() != lastFan)
+        stampFan(oven->fan());
+
+    if (oven->damper() != lastDamper)
+    {
+        lastDamper = oven->damper();
+        if (lastDamper)
+            stampDamper();
+    }
+
+    if (oven->humidification() != lastSideNozzle)
+    {
+        lastSideNozzle = oven->humidification();
+        if (lastSideNozzle)
+            stampSideNozzle();
+    }
+
+    if (oven->interTempEnabled() != lastCoreTempEnabled)
+    {
+        lastCoreTempEnabled = oven->interTempEnabled();
+        stamp();
+    }
+
+    if (oven->door() != lastDoor)
+        stamp();
+
+    if (lastStampedTime.elapsed() > 60 * 1000 - 100)
+        stamp();
+}
+
+}
diff --git a/app/gui/oven_control/haccp.h b/app/gui/oven_control/haccp.h
new file mode 100644
index 0000000..1ac22b3
--- /dev/null
+++ b/app/gui/oven_control/haccp.h
@@ -0,0 +1,29 @@
+#ifndef HACCP_H
+#define HACCP_H
+
+#include <QString>
+
+#include "define.h"
+
+namespace HACCP
+{
+
+void init();
+
+void autoStart(const QString &path);
+void manualStart();
+void multiStart();
+void washStart(int type);
+void engineeringStart();
+
+void done();
+void error(QString code);
+
+// For auto cook
+void setConfig(Define::CookConfigType type, int level);
+void setStep(Define::StepType type);
+void setProcess(Define::Process type);
+
+}
+
+#endif // HACCP_H
diff --git a/app/gui/oven_control/keepwarmpopup.cpp b/app/gui/oven_control/keepwarmpopup.cpp
index 2ec477c..96a433f 100644
--- a/app/gui/oven_control/keepwarmpopup.cpp
+++ b/app/gui/oven_control/keepwarmpopup.cpp
@@ -3,6 +3,8 @@
 
 #include <QKeyEvent>
 
+#include "haccp.h"
+
 KeepWarmPopup::KeepWarmPopup(QWidget *parent) :
     QWidget(parent),
     ui(new Ui::KeepWarmPopup)
@@ -19,6 +21,8 @@ KeepWarmPopup::KeepWarmPopup(QWidget *parent) :
 
 KeepWarmPopup::~KeepWarmPopup()
 {
+    HACCP::done();
+
     delete ui;
 }
 
diff --git a/app/gui/oven_control/main.cpp b/app/gui/oven_control/main.cpp
index ff1e423..cd95bff 100644
--- a/app/gui/oven_control/main.cpp
+++ b/app/gui/oven_control/main.cpp
@@ -5,6 +5,8 @@
 #include "ovenstatics.h"
 #include "config.h"
 #include "inputoverwatcher.h"
+#include "haccp.h"
+
 #include <QApplication>
 
 int main(int argc, char *argv[])
@@ -32,6 +34,8 @@ int main(int argc, char *argv[])
     pal.setColor(QPalette::Disabled, QPalette::Light, QColor(0, 0, 0, 0));
     QApplication::setPalette(pal);
 
+    HACCP::init();
+
     MainWindow w;
     w.showFullScreen();
 
diff --git a/app/gui/oven_control/manualcookwindow.cpp b/app/gui/oven_control/manualcookwindow.cpp
index df20645..a498174 100644
--- a/app/gui/oven_control/manualcookwindow.cpp
+++ b/app/gui/oven_control/manualcookwindow.cpp
@@ -22,6 +22,7 @@
 #include "washwindow.h"
 #include "errorpopupdlg.h"
 #include "manualviewerdlg.h"
+#include "haccp.h"
 
 #include <QTime>
 
@@ -559,6 +560,8 @@ void ManualCookWindow::onOvenUpdated(Oven *oven)
             lastCheckedCooking = oven->cooking();
             cookDone = true;
 
+            HACCP::done();
+
             emit done();
         }
     }
@@ -619,6 +622,7 @@ void ManualCookWindow::start()
         s.coreTemp = oven->interTemp();
 
         CookHistory::record(s);
+        HACCP::manualStart();
 
         if (monitorLevel > 0 && oven->door())
         {
@@ -637,6 +641,7 @@ void ManualCookWindow::stop()
 
     oven->stop();
     startCookingTimer.stop();
+    HACCP::done();
 }
 
 void ManualCookWindow::addFavorite()
diff --git a/app/gui/oven_control/multicookcontroller.cpp b/app/gui/oven_control/multicookcontroller.cpp
index ff38fa7..ba05583 100644
--- a/app/gui/oven_control/multicookcontroller.cpp
+++ b/app/gui/oven_control/multicookcontroller.cpp
@@ -1,6 +1,7 @@
 #include "multicookcontroller.h"
 
 #include "oven.h"
+#include "haccp.h"
 
 MultiCookController::MultiCookController(QObject *parent) : QObject(parent)
 {
@@ -47,7 +48,11 @@ void MultiCookController::check()
     {
     case Idle:
         if (!container->isEmpty())
+        {
             state = Preheating;
+
+            HACCP::multiStart();
+        }
         break;
     case Preheating:
         checkPreheating();
@@ -81,6 +86,7 @@ void MultiCookController::checkPreheating()
     if (container->isEmpty())
     {
         state = Idle;
+        HACCP::done();
         oven->stop();
         return;
     }
@@ -110,6 +116,7 @@ void MultiCookController::checkRunning()
     if (container->isEmpty())
     {
         state = Idle;
+        HACCP::done();
         oven->stop();
         return;
     }
@@ -117,6 +124,7 @@ void MultiCookController::checkRunning()
     if (container->isFinished())
     {
         state = Finished;
+        HACCP::done();
         oven->stop();
         container->stop();
         return;
@@ -188,6 +196,7 @@ void MultiCookController::checkFinished()
     if (oven->door())
     {
         state = Idle;
+        HACCP::done();
 
         for (int i = 0; i < 10; i++)
         {
diff --git a/app/gui/oven_control/oven_control.pro b/app/gui/oven_control/oven_control.pro
index 8de850b..3c7a6b7 100644
--- a/app/gui/oven_control/oven_control.pro
+++ b/app/gui/oven_control/oven_control.pro
@@ -141,7 +141,8 @@ SOURCES += main.cpp\
     multicookselectionwindow.cpp \
     multicookmanualwindow.cpp \
     multicookautowindow.cpp \
-    multicookbook.cpp
+    multicookbook.cpp \
+    haccp.cpp
 
 
 HEADERS  += mainwindow.h \
@@ -273,7 +274,8 @@ HEADERS  += mainwindow.h \
     multicookselectionwindow.h \
     multicookmanualwindow.h \
     multicookautowindow.h \
-    multicookbook.h
+    multicookbook.h \
+    haccp.h
 
 FORMS    += mainwindow.ui \
     manualcookwindow.ui \
diff --git a/app/gui/oven_control/stringer.cpp b/app/gui/oven_control/stringer.cpp
index d43fef4..9b6aa2f 100644
--- a/app/gui/oven_control/stringer.cpp
+++ b/app/gui/oven_control/stringer.cpp
@@ -237,11 +237,14 @@ QString Stringer::dateTime(const QDateTime &dateTime, DateTimeType type)
 
 QString Stringer::time(int msecs)
 {
-    int t = qCeil(msecs / 1000.0);
+    return timeSecs(qCeil(msecs / 1000.0));
+}
 
-    int h = t / (60*60);
-    int m = (t/60) % 60;
-    int s = t % 60;
+QString Stringer::timeSecs(int secs)
+{
+    int h = secs / (60*60);
+    int m = (secs/60) % 60;
+    int s = secs % 60;
 
     return QString("%1:%2:%3")
             .arg(h, 2, 10, QLatin1Char('0'))
diff --git a/app/gui/oven_control/stringer.h b/app/gui/oven_control/stringer.h
index af51716..38d7b12 100644
--- a/app/gui/oven_control/stringer.h
+++ b/app/gui/oven_control/stringer.h
@@ -28,6 +28,7 @@ QString unusedTemperature();
 QString unusedTemperature(QString style);
 QString dateTime(const QDateTime &dateTime, DateTimeType type = TwoLine);
 QString time(int msecs);
+QString timeSecs(int secs);
 
 }
 
diff --git a/app/gui/oven_control/washwindow.cpp b/app/gui/oven_control/washwindow.cpp
index 504bea0..6debea0 100644
--- a/app/gui/oven_control/washwindow.cpp
+++ b/app/gui/oven_control/washwindow.cpp
@@ -11,6 +11,7 @@
 #include "ovenstatics.h"
 #include "cooldownpopup.h"
 #include "manualviewerdlg.h"
+#include "haccp.h"
 
 WashWindow::WashWindow(QWidget *parent) :
     QMainWindow(parent),
@@ -389,6 +390,7 @@ void WashWindow::onChanged()
         {
             state = Running;
 
+            HACCP::washStart(type);
             updateView();
         }
         else
@@ -403,6 +405,7 @@ void WashWindow::onChanged()
         {
             state = Idle;
 
+            HACCP::done();
             SoundPlayer::playStop();
             DirtyLevel::wash(type);
             OvenStatistics::getInstance()->setWashState(false);
@@ -424,6 +427,7 @@ void WashWindow::onChanged()
     case Stopping:
         if (!udp->getData().cleaning_sate)
         {
+            HACCP::done();
             SoundPlayer::playStop();
             OvenStatistics::getInstance()->setWashState(false);
 
@@ -437,6 +441,7 @@ void WashWindow::onChanged()
         {
             state = RunningClean;
 
+            HACCP::washStart(6);
             updateView();
         }
         else
@@ -450,6 +455,8 @@ void WashWindow::onChanged()
         else
         {
             state = Idle;
+
+            HACCP::done();
             SoundPlayer::playStop();
             OvenStatistics::getInstance()->setWashState(false);