diff --git a/README.md b/README.md
index adb7d57..21c35ea 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,29 @@
-# MMDVMHost-Websocketboard
\ No newline at end of file
+# MMDVMHost-Websocketboard
+
+## Introduction
+This is a very first development version of my new MMDVMDash using websockets-technology to get rid of high load on Raspberry Pi and others and keep those temperature down.
+
+Also this should improve user experience.
+
+## Installation
+You'll need to install several python3 modules. A concrete list will follow here later.
+
+### Installation steps
+* clone this repository to your home-directory
+* create directory with `sudo mkdir /opt/MMDVMDash`
+* change ownership to your user for example with `sudo chown -R pi /opt/MMDVMDash`
+* copy all files from repository into this folder
+* modify *logtailer.ini* to fit your needs
+* copy files in */opt/MMDVMDash/systemd* to */etc/systemd/system* or similar corresponding to your system
+* modify both scripts to fit your needs
+* enable services with following commmands, this results in starting both automatically after reboot:
+ * `sudo systemctl enable http.server.service`
+ * `sudo systemctl enable logtailer.service`
+* start services with following commmands:
+ * `sudo systemctl enable http.server.service`
+ * `sudo systemctl enable logtailer.service`
+
+Finally you should be able to get the new Dashboard calling the hostname of your hotspot
+
+## Screenshots
+![Screenshot of MMDVMDash Websocketboard](img/Screenshot.png "Screenshot of MMDVMDash Websocketboard")
\ No newline at end of file
diff --git a/html/.index.html.swp b/html/.index.html.swp
new file mode 100644
index 0000000..3c0fe21
Binary files /dev/null and b/html/.index.html.swp differ
diff --git a/html/index.html b/html/index.html
new file mode 100644
index 0000000..3963499
--- /dev/null
+++ b/html/index.html
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
+ MMDVM-Dashboard by DG9VH
+
+
+
+
+
+
+
+
+
+
+
+
+ DG9VH - MMDVM-Dashboard by DG9VH
+
+
+
+
+
+
+
+
+
+
+
+
+
Last Heard List of today's callsigns.
+
+
+
+
+
+
+ Time (UTC)
+ Mode
+ Callsign
+ Target
+ Source
+ Dur (s)
+ Loss
+ BER
+ QSO
+
+
+
+
+
+
+
+
+
+
+
Local Heard List of today
+
+
+
+
+
+
+ Time (UTC)
+ Mode
+ Callsign
+ Target
+ Source
+ Dur (s)
+ BER
+
+
+
+
+
+
+
+
+
+
+
Currently in QSO
+
+
+
+
Delete selected row
+
+
+
+ Callsign
+ Added at
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html/js/functions.js b/html/js/functions.js
new file mode 100644
index 0000000..bbc7cba
--- /dev/null
+++ b/html/js/functions.js
@@ -0,0 +1,159 @@
+// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333
+// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+// M: 2020-11-01 21:33:27.454 YSF, received network data from DG2MAS to DG-ID 0 at DG2MAS
+// M: 2020-11-01 21:33:35.025 YSF, received network end of transmission from DG2MAS to DG-ID 0, 7.7 seconds, 0% packet loss, BER: 0.0%
+function getTimestamp(logline) {
+ return logline.substring(3,22);
+}
+
+function getMode(logline) {
+ return logline.substring(27, logline.indexOf(","));
+}
+
+function getCallsign(logline) {
+ return logline.substring(logline.indexOf("from") + 5, logline.indexOf("to"));
+}
+
+function getTarget(logline) {
+ if(logline.indexOf("at") > 0) {
+ return logline.substring(logline.indexOf("to") + 3, logline.lastIndexOf("at"));
+ } else {
+ return logline.substring(logline.indexOf("to") + 3, logline.substring(logline.indexOf("to") + 3).indexOf(",") + logline.indexOf("to") + 3);
+ }
+}
+
+function getSource(logline) {
+ val = logline.substring(logline.indexOf("received") + 9);
+ val = val.substring(0, val.indexOf(" "));
+ if (val == "network")
+ val = "Net";
+ return val;
+}
+
+function getDuration(logline) {
+ if(logline.lastIndexOf("seconds") > 0) {
+ val = logline.substring(0, logline.lastIndexOf("seconds"));
+ val = val.substring(val.lastIndexOf(",") + 2);
+ return val;
+ } else {
+ return "";
+ }
+}
+
+function getLoss(logline) {
+ if(logline.lastIndexOf("seconds") > 0) {
+ val = logline.substring(logline.lastIndexOf("seconds") + 9, logline.indexOf("%"));
+ if (val.indexOf("BER") == -1) {
+ return val;
+ } else {
+ return "";
+ }
+ } else {
+ return "";
+ }
+}
+
+function getBER(logline) {
+ if(logline.lastIndexOf("BER") > 0) {
+ return logline.substring(logline.lastIndexOf("BER") + 4);
+ } else {
+ return "";
+ }
+}
+
+
+function getAddToQSO(logline) {
+ retval = 'Copy
';
+ return retval;
+}
+
+function clocktime() {
+ var now = new Date(),
+ h = now.getHours(),
+ m = now.getMinutes(),
+ s = now.getSeconds();
+ m = leadingZero(m);
+ s = leadingZero(s);
+ return h + ':' + m + ':' + s;
+
+}
+
+function leadingZero(zahl) {
+ zahl = (zahl < 10 ? '0' : '' )+ zahl;
+ return zahl;
+}
+
+function copyToQSO(callsign) {
+ $(document).ready(function() {
+ t_qso.row.add( [
+ callsign,
+ new Date().toUTCString()
+ ] ).draw();
+ });
+ alert("" + callsign + " added to in QSO-Tab");
+
+}
+
+function getLastHeard(document, event) {
+// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333
+// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+// M: 2020-11-01 21:33:27.454 YSF, received network data from DG2MAS to DG-ID 0 at DG2MAS
+// M: 2020-11-01 21:33:35.025 YSF, received network end of transmission from DG2MAS to DG-ID 0, 7.7 seconds, 0% packet loss, BER: 0.0%
+ $(document).ready(function() {
+ var rowIndexes = [];
+ t_lh.rows( function ( idx, data, node ) {
+ if(data[2] === getCallsign(event.data)){
+ rowIndexes.push(idx);
+ }
+ return false;
+ });
+ if (rowIndexes[0]) {
+ newData = [
+ getTimestamp(event.data),
+ getMode(event.data),
+ getCallsign(event.data),
+ getTarget(event.data),
+ getSource(event.data),
+ getDuration(event.data),
+ getLoss(event.data),
+ getBER(event.data),
+ getAddToQSO(event.data)
+ ]
+ t_lh.row(rowIndexes[0]).data( newData ).draw();
+ } else {
+ t_lh.row.add( [
+ getTimestamp(event.data),
+ getMode(event.data),
+ getCallsign(event.data),
+ getTarget(event.data),
+ getSource(event.data),
+ getDuration(event.data),
+ getLoss(event.data),
+ getBER(event.data),
+ getAddToQSO(event.data)
+ ] ).draw();
+ }
+ });
+}
+
+function getLocalHeard(document, event) {
+// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333
+// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+// M: 2020-11-01 21:33:27.454 YSF, received network data from DG2MAS to DG-ID 0 at DG2MAS
+// M: 2020-11-01 21:33:35.025 YSF, received network end of transmission from DG2MAS to DG-ID 0, 7.7 seconds, 0% packet loss, BER: 0.0%
+ $(document).ready(function() {
+ if (getSource(event.data) == "RF") {
+ if (getDuration(event.data) !== "") {
+ t_localh.row.add( [
+ getTimestamp(event.data),
+ getMode(event.data),
+ getCallsign(event.data),
+ getTarget(event.data),
+ getSource(event.data),
+ getDuration(event.data),
+ getBER(event.data)
+ ] ).draw();
+ }
+ }
+ });
+}
diff --git a/img/Screenshot.png b/img/Screenshot.png
new file mode 100644
index 0000000..efb7250
Binary files /dev/null and b/img/Screenshot.png differ
diff --git a/logtailer.ini b/logtailer.ini
new file mode 100644
index 0000000..caa3bb9
--- /dev/null
+++ b/logtailer.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+Host=0.0.0.0
+Port=5678
+Webport=8080
+
+[MMDVMHost]
+Logdir=/mnt/ramdisk/
+Prefix=MMDVM
+Num_Lines=10000
diff --git a/logtailer.py b/logtailer.py
new file mode 100644
index 0000000..b3536d7
--- /dev/null
+++ b/logtailer.py
@@ -0,0 +1,102 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+#import time
+import datetime
+import os.path
+import asyncio
+import logging
+import argparse
+import websockets
+import http.server
+import socketserver
+import configparser
+import os
+from collections import deque
+from urllib.parse import urlparse, parse_qs
+from ansi2html import Ansi2HTMLConverter
+from threading import Thread
+
+current_dir = os.getcwd()
+config = configparser.ConfigParser()
+config.read(current_dir + '/logtailer.ini')
+
+NUM_LINES = int(config['MMDVMHost']['Num_Lines'])
+
+# init
+logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO)
+conv = Ansi2HTMLConverter(inline=True)
+
+@asyncio.coroutine
+def view_log(websocket, path):
+ global config
+ logging.info('Connected, remote={}, path={}'.format(websocket.remote_address, path))
+
+ try:
+ now = datetime.datetime.now()
+ year = str(now.year)
+ month = str(now.month)
+ if len(month) == 1:
+ month = "0" + month
+ day = str(now.day)
+ if len(day) == 1:
+ day = "0" + day
+
+ file_path = config['MMDVMHost']['Logdir']+config['MMDVMHost']['Prefix']+"-"+year+"-"+month+"-"+day+".log"
+
+ if not os.path.isfile(file_path):
+ raise ValueError('Not found')
+
+ path = file_path
+ with open(file_path) as f:
+
+ content = ''.join(deque(f, NUM_LINES))
+ content = conv.convert(content, full=False)
+ lines = content.split("\n")
+ for line in lines:
+ if line.find('received') >0 and not line.find('network watchdog') > 0:
+ yield from websocket.send(line)
+
+ while True:
+ content = f.read()
+ if content:
+ content = conv.convert(content, full=False)
+ yield from websocket.send(content)
+ else:
+ yield from asyncio.sleep(1)
+
+ except ValueError as e:
+ try:
+ yield from websocket.send('{} '.format(e))
+ yield from websocket.close()
+ except Exception:
+ pass
+
+ log_close(websocket, path, e)
+
+ except Exception as e:
+ log_close(websocket, path, e)
+
+ else:
+ log_close(websocket, path)
+
+
+def log_close(websocket, path, exception=None):
+ message = 'Closed, remote={}, path={}'.format(websocket.remote_address, path)
+ if exception is not None:
+ message += ', exception={}'.format(exception)
+ logging.info(message)
+
+
+def websocketserver():
+ start_server = websockets.serve(view_log, config['DEFAULT']['Host'], config['DEFAULT']['Port'])
+ asyncio.get_event_loop().run_until_complete(start_server)
+ asyncio.get_event_loop().run_forever()
+
+
+def main():
+ websocketserver()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/systemd/http.server.service b/systemd/http.server.service
new file mode 100644
index 0000000..7342800
--- /dev/null
+++ b/systemd/http.server.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Python3 http.server
+After=network.target
+
+[Service]
+ExecStartPre=/bin/sleep 30
+Type=simple
+Restart=always
+# Modify for different location of Python3 or other port
+ExecStart=/usr/bin/python3 -m http.server 8000 --directory /opt/MMDVMDash/html
+
+[Install]
+WantedBy=multi-user.target
diff --git a/systemd/logtailer.service b/systemd/logtailer.service
new file mode 100644
index 0000000..7e65f4c
--- /dev/null
+++ b/systemd/logtailer.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=Python3 logtailer for MMDVMDash
+After=network.target
+
+[Service]
+ExecStartPre=/bin/sleep 30
+Type=simple
+Restart=always
+# Modify for different location of Python3 or other port
+WorkingDirectory=/opt/MMDVMDash/
+ExecStart=/usr/bin/python3 /opt/MMDVMDash/logtailer.py
+
+[Install]
+WantedBy=multi-user.target