diff --git a/cutelog/serial.py b/cutelog/serial.py
new file mode 100644
index 0000000000000000000000000000000000000000..4f2d46c28e5105593ea88df851623e57d448419f
--- /dev/null
+++ b/cutelog/serial.py
@@ -0,0 +1,102 @@
+import serial
+import pickle
+import struct
+import re
+import sys
+from datetime import datetime
+
+from qtpy.QtCore import QThread, Signal
+from qtpy.QtNetwork import QHostAddress, QTcpSocket
+
+from .config import CONFIG
+
+class SerialConnection(QThread):
+    disconnected = False
+    re_line = re.compile(r"(?P<tsmin>\d{2,}):(?P<tssec>\d{2}.\d{3}) (?P<file>[\dA-Za-z/_\-.]+):(?P<line>\d+) (?P<func>[\dA-Za-z/_\-\.]+) (?P<level>[A-Za-z\d]+) \[(?P<name>[\dA-Za-z/_\-.]+)\] (?P<msg>.*)")
+
+    def __init__(self, parent, port, baud):
+        super().__init__(parent)
+        print("Opening serial port {}:{}".format(port, baud))
+
+        self.ser = serial.Serial(port, baud, timeout=1)
+        _, self.tcp_port = CONFIG.listen_address
+        self.tcp_host = QHostAddress("127.0.0.1")
+        
+        self.start()
+        print("thread started")
+
+    def onDisconnect(self):
+        disconnect = True
+        print("on disconnect")
+
+    def run(self):
+        sock = QTcpSocket(None)
+        sock.connectToHost(self.tcp_host, self.tcp_port)
+        sock.disconnected.connect(self.onDisconnect)
+        if not sock.waitForConnected():
+            print("Socket error!")
+            print(sock.errorString())
+            return
+        
+        if self.ser is None:
+            print("Error opening serial port")
+            return
+        
+        while True:
+            logline = self.ser.readline().decode("utf-8").strip("\n\r")
+                
+            match = self.re_line.fullmatch(logline)
+
+            if match is None:
+                d = {'args': None,
+                    'created': datetime.now().timestamp(),
+                    'exc_info': None,
+                    'filename': '',
+                    'funcName': '',
+                    'levelname': 'DEBUG',
+                    'levelno': 0,
+                    'lineno': 0,
+                    'module': '',
+                    'msecs': 0,
+                    'msg': logline,
+                    'name': 'raw_serial',
+                    'pathname': '',
+                    'process': 0,
+                    'processName': '',
+                    'relativeCreated': 0,
+                    'stack_info': None,
+                    'thread': 0,
+                    'threadName': '',
+                    'extra_column': ''}
+            else:         
+                d = {'args': None,
+                    'created': int(match.group('tsmin'))*60*1000 + float(match.group('tssec'))*1000,
+                    'exc_info': None,
+                    'filename': match.group('file'),
+                    'funcName': match.group('func'),
+                    'levelname': match.group('level'),
+                    'levelno': 10,
+                    'lineno': match.group('line'),
+                    'module': 'serial',
+                    'msecs': 0,
+                    'msg': match.group('msg'),
+                    'name': match.group('name'),
+                    'pathname': match.group('file'),
+                    'process': 0,
+                    'processName': '',
+                    'relativeCreated': 0,
+                    'stack_info': None,
+                    'thread': 0,
+                    'threadName': '',
+                    'extra_column': ''}
+            
+            s = pickle.dumps(d)
+
+            sock.write(struct.pack(">L", len(s)))
+            sock.write(s)
+            sock.flush()
+
+            if sock.state() != QTcpSocket.SocketState.ConnectedState:
+                print("thread stop")
+                self.ser.close()
+                break
\ No newline at end of file