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)ModeCallsignTargetSourceDur (s)LossBERQSO
+
+
+
+
+
+
+ +
Local Heard List of today
+
+ +
+ + + + + + + + + + + + +
Time (UTC)ModeCallsignTargetSourceDur (s)BER
+
+
+
+
+
+
+ +
Currently in QSO
+
+ +
+ + + + + + + + +
CallsignAdded 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 = '
'; + 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