diff --git a/app/gui/oven_control/udphandler.cpp b/app/gui/oven_control/udphandler.cpp
index 2d3bf24..a67ecde 100644
--- a/app/gui/oven_control/udphandler.cpp
+++ b/app/gui/oven_control/udphandler.cpp
@@ -2,7 +2,7 @@
 
 #include <QtDebug>
 
-#define IPC_UDP_SYS_HOST    "192.168.2.110"
+#define IPC_UDP_SYS_HOST    "127.0.0.1"
 #define IPC_UDP_SYS_PORT    4001
 #define IPC_UDP_GUI_PORT    4000
 
@@ -45,6 +45,8 @@ void UdpHandler::readPendingDatagrams()
 
 void UdpHandler::processDatagram(QByteArray &datagram)
 {
+    qDebug() << "Received";
+    sock->writeDatagram(datagram, QHostAddress("192.168.4.191"), 4000);
     packet_t *packet = (packet_t *) datagram.data();
     switch (packet->header)
     {
diff --git a/app/gui/packet/main.cpp b/app/gui/packet/main.cpp
new file mode 100644
index 0000000..b48f94e
--- /dev/null
+++ b/app/gui/packet/main.cpp
@@ -0,0 +1,11 @@
+#include "mainwindow.h"
+#include <QApplication>
+
+int main(int argc, char *argv[])
+{
+    QApplication a(argc, argv);
+    MainWindow w;
+    w.show();
+
+    return a.exec();
+}
diff --git a/app/gui/packet/mainwindow.cpp b/app/gui/packet/mainwindow.cpp
new file mode 100644
index 0000000..ee29c28
--- /dev/null
+++ b/app/gui/packet/mainwindow.cpp
@@ -0,0 +1,130 @@
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+
+#include <QTableWidgetItem>
+
+#include "tablevalue.h"
+
+typedef struct {
+    int header;
+    char body[];
+} STRUCT_PACK packet_t;
+
+MainWindow::MainWindow(QWidget *parent) :
+    QMainWindow(parent),
+    ui(new Ui::MainWindow)
+{
+    ui->setupUi(this);
+    sock = new QUdpSocket(this);
+    if (!sock->bind(4000))
+        exit(EXIT_FAILURE);
+
+    connect(sock, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams()));
+
+    ui->controlTable->setRowCount(sizeof(oven_control_t) / sizeof(U16));
+    ui->controlTable->setColumnCount(3);
+    ui->controlTable->setHorizontalHeaderLabels(QString("Address,Value,Description").split(","));
+
+    for (int row = 0; row < ui->controlTable->rowCount(); row++)
+    {
+        ui->controlTable->setItem(row, 0, new QTableWidgetItem(QString("").sprintf("0x%04X", row)));
+
+        TableValue *item = new TableValue;
+        item->setText("0x0000");
+        ui->controlTable->setCellWidget(row, 1, item);
+    }
+
+    ui->stateTable->setRowCount(sizeof(oven_state_t) / sizeof(U16));
+    ui->stateTable->setColumnCount(3);
+    ui->stateTable->setHorizontalHeaderLabels(QString("Address,Value,Description").split(","));
+
+    for (int row = 0; row < ui->stateTable->rowCount(); row++)
+    {
+        ui->stateTable->setItem(row, 0, new QTableWidgetItem(QString().sprintf("0x%04X", row)));
+
+        TableValue *item = new TableValue;
+        item->setText("0x0000");
+        ui->stateTable->setCellWidget(row, 1, item);
+    }
+}
+
+MainWindow::~MainWindow()
+{
+    delete ui;
+}
+
+void MainWindow::readPendingDatagrams()
+{
+    while (sock->hasPendingDatagrams()) {
+        QByteArray datagram;
+        datagram.resize(sock->pendingDatagramSize());
+        QHostAddress sender;
+        quint16 senderPort;
+
+        sock->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
+
+        processDatagram(datagram);
+    }
+}
+
+void MainWindow::processDatagram(QByteArray &datagram)
+{
+    packet_t *packet = (packet_t *) datagram.data();
+    switch (packet->header)
+    {
+    case HDR_OVEN_CONTROL:
+        processControl((oven_control_t *) packet->body);
+        break;
+    case HDR_OVEN_STATE:
+        processState((oven_state_t *) packet->body);
+        break;
+    case HDR_ERROR_CODE:
+        break;
+    }
+}
+
+void MainWindow::processControl(oven_control_t *control)
+{
+    if (memcmp(&this->control, control, sizeof(this->control)))
+        updateControl(control);
+}
+
+void MainWindow::processState(oven_state_t *state)
+{
+    if (memcmp(&this->state, state, sizeof(this->state)))
+        updateState(state);
+}
+
+void MainWindow::updateControl(oven_control_t *control)
+{
+    U16 *base = (U16 *) &this->control;
+    U16 *operand = (U16 *) control;
+    for (int row = 0; row < ui->controlTable->rowCount(); row++)
+    {
+        U16 b = *(base + row);
+        U16 o = *(operand + row);
+        if (b != o)
+        {
+            *(base + row) = o;
+            TableValue *val = (TableValue *) ui->controlTable->cellWidget(row, 1);
+            val->setText(QString().sprintf("0x%04X", o));
+        }
+    }
+}
+
+void MainWindow::updateState(oven_state_t *state)
+{
+    U16 *base = (U16 *) &this->state;
+    U16 *operand = (U16 *) state;
+    for (int row = 0; row < ui->stateTable->rowCount(); row++)
+    {
+        U16 b = *(base + row);
+        U16 o = *(operand + row);
+        if (b != o)
+        {
+            *(base + row) = o;
+            TableValue *val = (TableValue *) ui->stateTable->cellWidget(row, 1);
+            val->setText(QString().sprintf("0x%04X", o));
+        }
+    }
+}
diff --git a/app/gui/packet/mainwindow.h b/app/gui/packet/mainwindow.h
new file mode 100644
index 0000000..ea05914
--- /dev/null
+++ b/app/gui/packet/mainwindow.h
@@ -0,0 +1,36 @@
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+#include <QUdpSocket>
+
+#include "../oven_control/all_share.h"
+
+namespace Ui {
+class MainWindow;
+}
+
+class MainWindow : public QMainWindow
+{
+    Q_OBJECT
+
+public:
+    explicit MainWindow(QWidget *parent = 0);
+    ~MainWindow();
+
+private:
+    Ui::MainWindow *ui;
+    QUdpSocket *sock;
+    oven_control_t control;
+    oven_state_t state;
+
+private slots:
+    void readPendingDatagrams();
+    void processDatagram(QByteArray &datagram);
+    void processControl(oven_control_t *control);
+    void processState(oven_state_t *state);
+    void updateControl(oven_control_t *control);
+    void updateState(oven_state_t *state);
+};
+
+#endif // MAINWINDOW_H
diff --git a/app/gui/packet/mainwindow.ui b/app/gui/packet/mainwindow.ui
new file mode 100644
index 0000000..241fade
--- /dev/null
+++ b/app/gui/packet/mainwindow.ui
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>818</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Data Viewer</string>
+  </property>
+  <widget class="QWidget" name="centralWidget">
+   <layout class="QHBoxLayout" name="horizontalLayout">
+    <item>
+     <widget class="QTableWidget" name="controlTable"/>
+    </item>
+    <item>
+     <widget class="QTableWidget" name="stateTable"/>
+    </item>
+   </layout>
+  </widget>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/app/gui/packet/packet.pro b/app/gui/packet/packet.pro
new file mode 100644
index 0000000..40ceb08
--- /dev/null
+++ b/app/gui/packet/packet.pro
@@ -0,0 +1,22 @@
+#-------------------------------------------------
+#
+# Project created by QtCreator 2017-03-03T21:21:48
+#
+#-------------------------------------------------
+
+QT       += core gui network
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+TARGET = packet
+TEMPLATE = app
+
+
+SOURCES += main.cpp\
+        mainwindow.cpp \
+    tablevalue.cpp
+
+HEADERS  += mainwindow.h \
+    tablevalue.h
+
+FORMS    += mainwindow.ui
diff --git a/app/gui/packet/tablevalue.cpp b/app/gui/packet/tablevalue.cpp
new file mode 100644
index 0000000..d022d4c
--- /dev/null
+++ b/app/gui/packet/tablevalue.cpp
@@ -0,0 +1,34 @@
+#include "tablevalue.h"
+
+#include <QColor>
+
+#include <QtDebug>
+
+void TableValue::setText(const QString &str)
+{
+    QLabel::setText(str);
+    timer.start(2000);
+    animationTimer.start(33);
+
+    updateColor();
+}
+
+void TableValue::updateColor()
+{
+    int remain = timer.remainingTime();
+    if (remain < 0)
+        remain = 0;
+
+    if (remain > 2000)
+        remain = 2000;
+
+    qreal percentage = ((qreal) remain / 2000) * 0.5;
+    int b = 255 * percentage;
+    if (b < 0)
+        b = 0;
+
+    if (b > 255)
+        b = 255;
+
+    setStyleSheet(QString().sprintf("background-color: rgba(255, 0, 0, %d); color: rgb(0, 0, 0)", b));
+}
diff --git a/app/gui/packet/tablevalue.h b/app/gui/packet/tablevalue.h
new file mode 100644
index 0000000..2696d31
--- /dev/null
+++ b/app/gui/packet/tablevalue.h
@@ -0,0 +1,33 @@
+#ifndef TABLEVALUE_H
+#define TABLEVALUE_H
+
+#include <QObject>
+#include <QWidget>
+#include <QLabel>
+#include <QTableWidgetItem>
+#include <QTimer>
+
+class TableValue : public QLabel
+{
+    Q_OBJECT
+
+public:
+    explicit TableValue()
+    {
+        timer.setSingleShot(true);
+        connect(&animationTimer, SIGNAL(timeout()), this, SLOT(updateColor()));
+        connect(&timer, SIGNAL(timeout()), &animationTimer, SLOT(stop()));
+        connect(&timer, SIGNAL(timeout()), this, SLOT(updateColor()));
+    }
+
+    void setText(const QString &text);
+
+private:
+    QTimer timer;
+    QTimer animationTimer;
+
+private slots:
+    void updateColor();
+};
+
+#endif // TABLEVALUE_H