Add files via upload

Switch to HBmonitor3
This commit is contained in:
Waldek 2019-09-17 21:18:38 +02:00 committed by GitHub
parent 3cc9686388
commit 50616e3513
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1295 additions and 0 deletions

53
README.md Normal file
View File

@ -0,0 +1,53 @@
# hbmonitor3
Python 3 implementation of N0MJS HBmonitor for HBlink
***This software is VERY, VERY, VERY new!***
Since Python 3 is the way of the future, I'm updating the HBmonitor code from Python 2 to Python 3.
__THIS SOFTWARE IS WORKING__, hopefully...
Questions, comments, and complaints can be forwarded to the DVSwitch group at [DVSwitch - HBlink Subgroup](https://dvswitch.groups.io/g/HBlink/topics)
If you would like to contribute to this effort, please submit updated code as a PR to this repository.
***73, KC1AWV***
---
Modified version by SP2ONG 2019
---
**Socket-Based Reporting for HBlink**
Over the years, the biggest request recevied for HBlink (other than call-routing/bridging tools) has been web-based diagnostics and/or statistics for the program.
I strongly disagree with including the amount of overhead this would require inside HBlink -- which still runs nicely on very modest resources. That it does this, and is in Python is a point of pride for me... Just let me have this one, ok? What I have done is added some hooks to HBlink, which will be expanded over time, whereby it listens on a TCP socket and provides the raw data necessary for a "web dashboard", or really any external logging or statistics gathering program.
HBmonitor is my take on a "web dashboard" for HBlink.
***THIS SOFTWARE IS VERY, VERY NEW***
Right now, I'm just getting into how this should work, what does work well, what does not... and I am NOT a web applications programmer, so yeah, that javascript stuff is gonna look bad. Know what you're doing? Help me!
It has now reached a point where folks who know what they're doing can probably make it work reasonably well, so I'm opening up the project to the public.
***GOALS OF THE PROJECT***
Some things I'm going to stick to pretty closely. Here they are:
+ HBmonitor be one process that includes a webserver
+ Websockets are used for pushing data to the browser - no long-polling, etc.
+ Does not provide data that's easily misunderstood
***0x49 DE N0MJS***
Copyright (C) 2013-2018 Cortney T. Buffington, N0MJS <n0mjs@me.com>
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

25
config_SAMPLE.py Normal file
View File

@ -0,0 +1,25 @@
REPORT_NAME = 'Dashboard local of DMR Network' # Name of the monitored HBlink system
CONFIG_INC = True # Include HBlink stats
LASTHEARD_INC = True # Include Lastheard (10)
HOMEBREW_INC = Ture # Include Homebrew Peers
BRIDGES_INC = True # Include Bridge stats (confbrige.py)
HBLINK_IP = '127.0.0.1' # HBlink's IP Address
HBLINK_PORT = 4321 # HBlink's TCP reporting socket
FREQUENCY = 10 # Frequency to push updates to web clients
WEB_SERVER_PORT = 8080 # Has to be above 1024 if you're not running as root
CLIENT_TIMEOUT = 0 # Clients are timed out after this many seconds, 0 to disable
# Files and stuff for loading alias files for mapping numbers to names
PATH = './' # MUST END IN '/'
PEER_FILE = 'peer_ids.json' # Will auto-download from DMR-MARC
SUBSCRIBER_FILE = 'subscriber_ids.json' # Will auto-download from DMR-MARC
TGID_FILE = 'talkgroup_ids.json' # User provided, should be in "integer TGID, TGID name" format
LOCAL_SUB_FILE = 'local_subscriber_ids.json' # User provided (optional, leave '' if you don't use it), follow the format of DMR-MARC
LOCAL_PEER_FILE = 'local_peer_ids.json' # User provided (optional, leave '' if you don't use it), follow the format of DMR-MARC
FILE_RELOAD = 7 # Number of days before we reload DMR-MARC database files
PEER_URL = 'https://www.radioid.net/static/rptrs.json'
SUBSCRIBER_URL = 'https://www.radioid.net/static/users.json'
# Settings for log files
LOG_PATH = './log/' # MUST END IN '/'
LOG_NAME = 'hbmon.log'

363
index_template.html Normal file
View File

@ -0,0 +1,363 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>HBLink monitor</title>
<script type="text/javascript">
var sock = null;
window.onload = function() {
var wsuri;
hblink_table = document.getElementById('hblink');
confbridge_table = document.getElementById('bridge');
wsuri = "ws://" + window.location.hostname + ":9000";
if ("WebSocket" in window) {
sock = new WebSocket(wsuri);
} else if ("MozWebSocket" in window) {
sock = new MozWebSocket(wsuri);
} else {
log("Browser does not support WebSocket!");
}
if (sock) {
sock.onopen = function() {
log("Connected to " + wsuri);
}
sock.onclose = function(e) {
log("Connection closed (wasClean = " + e.wasClean + ", code = " + e.code + ", reason = '" + e.reason + "')");
hblink_table.innerHTML = "";
confbridge_table.innerHTML = "";
sock = null;
}
sock.onmessage = function(e) {
var opcode = e.data.slice(0,1);
var message = e.data.slice(1);
if (opcode == "d") {
hblink(message);
} else if (opcode == "b") {
confbridge(message);
} else if (opcode == "l") {
log(message);
} else if (opcode == "q") {
log(message);
hblink_table.innerHTML = "";
confbridge_table.innerHTML = "";
} else {
log("Unknown Message Received: " + message);
}
}
}
};
function hblink(_msg) {
hblink_table.innerHTML = _msg;
};
function confbridge(_msg) {
confbridge_table.innerHTML = _msg;
};
function log(_msg) {
};
</script>
<style>
a:link {
color: brown;
text-decoration: none;
}
/* visited link */
a:visited {
color: brown;
text-decoration: none;
}
/* mouse over link */
a:hover {
color: hotpink;
text-decoration: underline;
}
/* selected link */
a:active {
color: brown;
text-decoration: none;
}
.tooltip {
position: relative;
opacity: 1;
display: inline-block;
border-bottom: 1px dotted black;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 260px;
background-color: #6E6E6E;
box-shadow: 4px 4px 6px #800000;
color: #FFFFFF;
text-align: left;
border-radius: 6px;
padding: 8px 0;
left: 100%
opacity: 1;
/* Position the tooltip */
position: absolute;
z-index: 1;
}
.tooltip:hover .tooltiptext {
right: 100%
opacity: 1;
visibility: visible;
}
.button {
background-color: #aa6103;
border: none;
color: white;
padding: 8px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
margin: 4px 2px;
border-radius: 8px;
box-shadow: 0px 8px 10px rgba(0,0,0,0.1);
}
.link {background-color: #a66300;}
.link:hover {background-color: #3e8e41;}
.dropbtn {
background-color: #aa6103;
border: none;
color: white;
padding: 8px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
margin: 4px 2px;
border-radius: 8px;
box-shadow: 0px 8px 10px rgba(0,0,0,0.1);
}
/* The container <div> - needed to position the dropdown content */
.dropdown {
position: relative;
display: inline-block;
}
/* Dropdown Content (Hidden by Default) */
.dropdown-content {
display: none;
position: absolute;
background-color: #f1f1f1;
min-width: 140px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
}
/* Links inside the dropdown */
.dropdown-content a {
color: black;
padding: 6px 16px;
text-decoration: none;
display: block;
}
/* Change color of dropdown links on hover */
.dropdown-content a:hover {background-color: #ddd;}
/* Show the dropdown menu on hover */
.dropdown:hover .dropdown-content {display: block;}
/* Change the background color of the dropdown button when the dropdown content is shown */
.dropdown:hover .dropbtn {background-color: #3e8e41;}
table, td, th {border: .5px solid black; padding: 2px; border-collapse: collapse; text-align:center;}
</style>
</head>
<body style="background-color: #EEEEEE;font: 10pt arial, sans-serif;">
<center><div style="width:1250px; text-align: center; margin-top:5px;">
<a target='_blank' href=https://hblink.pl><img src="data:image/png;base64,
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"/></a>
</div>
<div style="width: 1100px;">
<p style="text-align:center;"><span style="color:#000;font-size: 17px; font-weight:bold;"><<<system_name>>></span></p>
<p style="text-align:center;"><<<timeout_warning>>></p>
<!-- Buttons HTML code
<div class="dropdown">
<button class="dropbtn">Others HBLink</button>
<div class="dropdown-content">
<a target='_blank' href="#">HBLink 1</a>
<a target='_blank' href="#">HBLink 2</a>
</div>
</div>
&nbsp;
<div class="dropdown">
<button class="dropbtn">Reflectors</button>
<div class="dropdown-content">
<a target='_blank' href="#">YSF Reflector</a>
<a target='_blank' href="#">XLX950</a>
</div>
</div>
&nbsp;
<a target='_blank' href="#/log.php"><button class="button link">Lastheard</button></a>
&nbsp;
-->
<noscript>You must enable JavaScript</noscript>
<p id="hblink"></p>
<p id="bridge"></p>
</div>
<!-- LOG monitor
<fieldset style="width: 1200px; margin-left:0px;margin-right:0px;font-size:14px;border-top-left-radius: 10px; border-top-right-radius: 10px;border-bottom-left-radius: 10px; border-bottom-right-radius: 10px;"><legend><b><font color="brown">&nbsp;.: Call Log Window :.&nbsp;</font></b></legend>
<pre id="log" style="height: 20em; text-align: left; overflow-y: scroll; font-size:13px; background-color: #000000; color:#00fd2c;"></pre>
</fieldset></center>
-->
<p style="text-align: center;"><span style="text-align: center;">
Copyright (c) 2016, 2017, 2018, 2019<br>The Regents of the K0USY Group. All rights reserved.<br>Modified by SP2ONG 2019.<br><br></span>
<!-- THIS COPYRIGHT NOTICE MUST BE DISPLAYED AS A CONDITION OF THE LICENCE GRANT FOR THIS SOFTWARE. ALL DERIVATEIVES WORKS MUST CARRY THIS NOTICE -->
</p>
</body>
</html>

6
install.sh Normal file
View File

@ -0,0 +1,6 @@
#! /bin/bash
# Install the required support programs
apt-get install python3 python3-pip python3-dev libffi-dev libssl-dev -y
pip3 install setuptools wheel
pip3 install -r requirements.txt

33
local_peer_ids.json Normal file
View File

@ -0,0 +1,33 @@
{"rptrs":[
{"locator":"2603154",
"id":"2603154",
"callsign":"SP3KFQ",
"city":"Chojnice",
"state":"Pomorskie",
"country":"Polska",
"frequency":"440.58750",
"color_code":2,
"offset":"+5.000",
"assigned":"Peer",
"ts_linked":"TS1 TS2",
"trustee":"SP2KFQ",
"map_info":"",
"map":0,
"ipsc_network":"HBLink"},
{"locator":"260312201",
"id":"260312201",
"callsign":"SP3PMK",
"city":"Torun",
"state":"Pomorskie",
"country":"Polska",
"frequency":"438.5175",
"color_code":2,
"offset":"+5.000",
"assigned":"Peer",
"ts_linked":"TS1 TS2",
"trustee":"SP3KFQ",
"map_info":"",
"map":0,
"ipsc_network":"HBLink"}
]}

25
local_subscriber_ids.json Normal file
View File

@ -0,0 +1,25 @@
{
"count": 2,
"results": [
{
"callsign": "N0CALL",
"city": "",
"country": "",
"fname": "N0CALL",
"id": 1234567,
"remarks": "",
"state": "",
"surname": ""
},
{
"callsign": "SP2ABC",
"city": "",
"country": "",
"fname": "Jan",
"id": 26032254,
"remarks": "",
"state": "",
"surname": ""
}
]
}

785
monitor.py Normal file
View File

@ -0,0 +1,785 @@
#!/usr/bin/env python3
#
###############################################################################
# Copyright (C) 2016-2019 Cortney T. Buffington, N0MJS <n0mjs@me.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
###############################################################################
#
# Python 3 port by Steve Miller, KC1AWV <smiller@kc1awv.net>
#
###############################################################################
# Standard modules
import logging
import sys
import csv
from itertools import islice
# Twisted modules
from twisted.internet.protocol import ReconnectingClientFactory, Protocol
from twisted.protocols.basic import NetstringReceiver
from twisted.internet import reactor, task
from twisted.web.server import Site
from twisted.web.resource import Resource
# Autobahn provides websocket service under Twisted
from autobahn.twisted.websocket import WebSocketServerProtocol, WebSocketServerFactory
# Specific functions to import from standard modules
from time import time, strftime, localtime
from pickle import loads
from binascii import b2a_hex as h
from os.path import getmtime
from collections import deque
from time import time
# Web templating environment
from jinja2 import Environment, PackageLoader, select_autoescape
# Utilities from K0USY Group sister project
from dmr_utils3.utils import int_id, get_alias, try_download, mk_full_id_dict, bytes_4
# Configuration variables and constants
from config import *
# SP2ONG - Increase the value if HBlink link break occurs
#NetstringReceiver.MAX_LENGTH = 500000
# Opcodes for reporting protocol to HBlink
OPCODE = {
'CONFIG_REQ': '\x00',
'CONFIG_SND': '\x01',
'BRIDGE_REQ': '\x02',
'BRIDGE_SND': '\x03',
'CONFIG_UPD': '\x04',
'BRIDGE_UPD': '\x05',
'LINK_EVENT': '\x06',
'BRDG_EVENT': '\x07',
}
# Global Variables:
CONFIG = {}
CTABLE = {'MASTERS': {}, 'PEERS': {}, 'OPENBRIDGES': {}}
BRIDGES = {}
BTABLE = {}
BTABLE['BRIDGES'] = {}
BRIDGES_RX = ''
CONFIG_RX = ''
LOGBUF = deque(100*[''], 100)
RED = 'ff6347'
BLACK = '000000'
GREEN = '90EE90'
GREEN2 = '008000'
BLUE = '0000ff'
ORANGE = 'ff8000'
WHITE = 'ffffff'
WHITE2 = 'f9f9f9f9'
YELLOW = 'fffccd'
# For importing HTML templates
def get_template(_file):
with open(_file, 'r') as html:
return html.read()
# Alias string processor
def alias_string(_id, _dict):
alias = get_alias(_id, _dict, 'CALLSIGN', 'CITY', 'STATE')
if type(alias) == list:
for x,item in enumerate(alias):
if item == None:
alias.pop(x)
return ', '.join(alias)
else:
return alias
def alias_short(_id, _dict):
alias = get_alias(_id, _dict, 'CALLSIGN', 'NAME')
if type(alias) == list:
for x,item in enumerate(alias):
if item == None:
alias.pop(x)
return ', '.join(alias)
else:
return str(alias)
def alias_call(_id, _dict):
alias = get_alias(_id, _dict, 'CALLSIGN')
if type(alias) == list:
for x,item in enumerate(alias):
if item == None:
alias.pop(x)
return ', '.join(alias)
else:
return str(alias)
def alias_tgid(_id, _dict):
alias = get_alias(_id, _dict, 'NAME')
if type(alias) == list:
return str(alias[0])
else:
return str(alias)
# Return friendly elapsed time from time in seconds.
def since(_time):
now = int(time())
_time = now - int(_time)
seconds = _time % 60
minutes = int(_time/60) % 60
hours = int(_time/60/60) % 24
days = int(_time/60/60/24)
if days:
return '{}d {}h'.format(days, hours)
elif hours:
return '{}h {}m'.format(hours, minutes)
elif minutes:
return '{}m {}s'.format(minutes, seconds)
else:
return '{}s'.format(seconds)
def add_hb_peer(_peer_conf, _ctable_loc, _peer):
_ctable_loc[int_id(_peer)] = {}
_ctable_peer = _ctable_loc[int_id(_peer)]
# if the Frequency is 000.xxx assume it's not an RF peer, otherwise format the text fields
# (9 char, but we are just software) see https://wiki.brandmeister.network/index.php/Homebrew/example/php2
if _peer_conf['TX_FREQ'][:3].decode('utf-8') == '000' or _peer_conf['RX_FREQ'][:3].decode('utf-8') == '000':
_ctable_peer['TX_FREQ'] = 'N/A'
_ctable_peer['RX_FREQ'] = 'N/A'
else:
_ctable_peer['TX_FREQ'] = _peer_conf['TX_FREQ'][:3].decode('utf-8') + '.' + _peer_conf['TX_FREQ'][3:7].decode('utf-8') + ' MHz'
_ctable_peer['RX_FREQ'] = _peer_conf['RX_FREQ'][:3].decode('utf-8') + '.' + _peer_conf['RX_FREQ'][3:7].decode('utf-8') + ' MHz'
# timeslots are kinda complicated too. 0 = none, 1 or 2 mean that one slot, 3 is both, and anythign else it considered DMO
# Slots (0, 1=1, 2=2, 1&2=3 Duplex, 4=Simplex) see https://wiki.brandmeister.network/index.php/Homebrew/example/php2
if (_peer_conf['SLOTS'].decode('utf-8') == '0'):
_ctable_peer['SLOTS'] = 'NONE'
elif (_peer_conf['SLOTS'].decode('utf-8') == '1' or _peer_conf['SLOTS'].decode('utf-8') == '2'):
_ctable_peer['SLOTS'] = _peer_conf['SLOTS'].decode('utf-8')
elif (_peer_conf['SLOTS'].decode('utf-8') == '3'):
_ctable_peer['SLOTS'] = 'Duplex'
else:
_ctable_peer['SLOTS'] = 'Simplex'
# Simple translation items
_ctable_peer['SOFTWARE_ID'] = _peer_conf['SOFTWARE_ID'].decode('utf-8').strip()
_ctable_peer['PACKAGE_ID'] = _peer_conf['PACKAGE_ID'].decode('utf-8').strip()
_ctable_peer['COLORCODE'] = _peer_conf['COLORCODE'].decode('utf-8')
_ctable_peer['CALLSIGN'] = _peer_conf['CALLSIGN'].decode('utf-8')
_ctable_peer['LOCATION'] = _peer_conf['LOCATION'].decode('utf-8')
_ctable_peer['CONNECTION'] = _peer_conf['CONNECTION']
_ctable_peer['CONNECTED'] = since(_peer_conf['CONNECTED'])
_ctable_peer['IP'] = _peer_conf['IP']
_ctable_peer['PORT'] = _peer_conf['PORT']
#_ctable_peer['LAST_PING'] = _peer_conf['LAST_PING']
# SLOT 1&2 - for real-time montior: make the structure for later use
for ts in range(1,3):
_ctable_peer[ts]= {}
_ctable_peer[ts]['COLOR'] = ''
_ctable_peer[ts]['BGCOLOR'] = ''
_ctable_peer[ts]['TS'] = ''
_ctable_peer[ts]['TYPE'] = ''
_ctable_peer[ts]['SUB'] = ''
_ctable_peer[ts]['SRC'] = ''
_ctable_peer[ts]['DEST'] = ''
######################################################################
#
# Build the HBlink connections table
#
def build_hblink_table(_config, _stats_table):
for _hbp, _hbp_data in list(_config.items()):
if _hbp_data['ENABLED'] == True:
# Process Master Systems
if _hbp_data['MODE'] == 'MASTER':
_stats_table['MASTERS'][_hbp] = {}
if _hbp_data['REPEAT']:
_stats_table['MASTERS'][_hbp]['REPEAT'] = "repeat"
else:
_stats_table['MASTERS'][_hbp]['REPEAT'] = "isolate"
_stats_table['MASTERS'][_hbp]['PEERS'] = {}
for _peer in _hbp_data['PEERS']:
add_hb_peer(_hbp_data['PEERS'][_peer], _stats_table['MASTERS'][_hbp]['PEERS'], _peer)
# Proccess Peer Systems
elif (_hbp_data['MODE'] == 'XLXPEER' or _hbp_data['MODE'] == 'PEER') and HOMEBREW_INC:
_stats_table['PEERS'][_hbp] = {}
_stats_table['PEERS'][_hbp]['MODE'] = _hbp_data['MODE']
_stats_table['PEERS'][_hbp]['CALLSIGN'] = _hbp_data['CALLSIGN'].decode('utf-8')
_stats_table['PEERS'][_hbp]['LOCATION'] = _hbp_data['LOCATION'].decode('utf-8')
_stats_table['PEERS'][_hbp]['RADIO_ID'] = int_id(_hbp_data['RADIO_ID'])
_stats_table['PEERS'][_hbp]['MASTER_IP'] = _hbp_data['MASTER_IP']
_stats_table['PEERS'][_hbp]['MASTER_PORT'] = _hbp_data['MASTER_PORT']
_stats_table['PEERS'][_hbp]['STATS'] = {}
if _stats_table['PEERS'][_hbp]['MODE'] == 'XLXPEER':
_stats_table['PEERS'][_hbp]['STATS']['CONNECTION'] = _hbp_data['XLXSTATS']['CONNECTION']
if _hbp_data['XLXSTATS']['CONNECTION'] == "YES":
_stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = since(_hbp_data['XLXSTATS']['CONNECTED'])
else:
_stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = "-- --"
_stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = _hbp_data['XLXSTATS']['PINGS_SENT']
_stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = _hbp_data['XLXSTATS']['PINGS_ACKD']
else:
_stats_table['PEERS'][_hbp]['STATS']['CONNECTION'] = _hbp_data['STATS']['CONNECTION']
if _hbp_data['STATS']['CONNECTION'] == "YES":
_stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = since(_hbp_data['STATS']['CONNECTED'])
else:
_stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = "-- --"
_stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = _hbp_data['STATS']['PINGS_SENT']
_stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = _hbp_data['STATS']['PINGS_ACKD']
if _hbp_data['SLOTS'].decode('utf-8') == 0:
_stats_table['PEERS'][_hbp]['SLOTS'] = 'NONE'
elif _hbp_data['SLOTS'].decode('utf-8') == '1' or _hbp_data['SLOTS'].decode('utf-8') == '2':
_stats_table['PEERS'][_hbp]['SLOTS'] = _hbp_data['SLOTS'].decode('utf-8')
elif _hbp_data['SLOTS'].decode('utf-8') == '3':
_stats_table['PEERS'][_hbp]['SLOTS'] = '1&2'
else:
_stats_table['PEERS'][_hbp]['SLOTS'] = 'DMO'
# SLOT 1&2 - for real-time montior: make the structure for later use
for ts in range(1,3):
_stats_table['PEERS'][_hbp][ts]= {}
_stats_table['PEERS'][_hbp][ts]['COLOR'] = ''
_stats_table['PEERS'][_hbp][ts]['BGCOLOR'] = ''
_stats_table['PEERS'][_hbp][ts]['TS'] = ''
_stats_table['PEERS'][_hbp][ts]['TYPE'] = ''
_stats_table['PEERS'][_hbp][ts]['SUB'] = ''
_stats_table['PEERS'][_hbp][ts]['SRC'] = ''
_stats_table['PEERS'][_hbp][ts]['DEST'] = ''
# Process OpenBridge systems
elif _hbp_data['MODE'] == 'OPENBRIDGE':
_stats_table['OPENBRIDGES'][_hbp] = {}
_stats_table['OPENBRIDGES'][_hbp]['NETWORK_ID'] = int_id(_hbp_data['NETWORK_ID'])
_stats_table['OPENBRIDGES'][_hbp]['TARGET_IP'] = _hbp_data['TARGET_IP']
_stats_table['OPENBRIDGES'][_hbp]['TARGET_PORT'] = _hbp_data['TARGET_PORT']
_stats_table['OPENBRIDGES'][_hbp]['STREAMS'] = {}
#return(_stats_table)
def update_hblink_table(_config, _stats_table):
# Is there a system in HBlink's config monitor doesn't know about?
for _hbp in _config:
if _config[_hbp]['MODE'] == 'MASTER':
for _peer in _config[_hbp]['PEERS']:
if int_id(_peer) not in _stats_table['MASTERS'][_hbp]['PEERS'] and _config[_hbp]['PEERS'][_peer]['CONNECTION'] == 'YES':
logger.info('Adding peer to CTABLE that has registerred: %s', int_id(_peer))
add_hb_peer(_config[_hbp]['PEERS'][_peer], _stats_table['MASTERS'][_hbp]['PEERS'], _peer)
# Is there a system in monitor that's been removed from HBlink's config?
for _hbp in _stats_table['MASTERS']:
remove_list = []
if _config[_hbp]['MODE'] == 'MASTER':
for _peer in _stats_table['MASTERS'][_hbp]['PEERS']:
if bytes_4(_peer) not in _config[_hbp]['PEERS']:
remove_list.append(_peer)
for _peer in remove_list:
logger.info('Deleting stats peer not in hblink config: %s', _peer)
del (_stats_table['MASTERS'][_hbp]['PEERS'][_peer])
# Update connection time
for _hbp in _stats_table['MASTERS']:
for _peer in _stats_table['MASTERS'][_hbp]['PEERS']:
if bytes_4(_peer) in _config[_hbp]['PEERS']:
_stats_table['MASTERS'][_hbp]['PEERS'][_peer]['CONNECTED'] = since(_config[_hbp]['PEERS'][bytes_4(_peer)]['CONNECTED'])
for _hbp in _stats_table['PEERS']:
if _stats_table['PEERS'][_hbp]['MODE'] == 'XLXPEER':
if _config[_hbp]['XLXSTATS']['CONNECTION'] == "YES":
_stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = since(_config[_hbp]['XLXSTATS']['CONNECTED'])
else:
_stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = "-- --"
_stats_table['PEERS'][_hbp]['STATS']['CONNECTION'] = _config[_hbp]['XLXSTATS']['CONNECTION']
_stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = _config[_hbp]['XLXSTATS']['PINGS_SENT']
_stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = _config[_hbp]['XLXSTATS']['PINGS_ACKD']
else:
if _config[_hbp]['STATS']['CONNECTION'] == "YES":
_stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = since(_config[_hbp]['STATS']['CONNECTED'])
else:
_stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = "-- --"
_stats_table['PEERS'][_hbp]['STATS']['CONNECTION'] = _config[_hbp]['STATS']['CONNECTION']
_stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = _config[_hbp]['STATS']['PINGS_SENT']
_stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = _config[_hbp]['STATS']['PINGS_ACKD']
build_stats()
######################################################################
#
# CONFBRIDGE TABLE FUNCTIONS
#
def build_bridge_table(_bridges):
_stats_table = {}
_now = time()
_cnow = strftime('%Y-%m-%d %H:%M:%S', localtime(_now))
for _bridge, _bridge_data in list(_bridges.items()):
_stats_table[_bridge] = {}
for system in _bridges[_bridge]:
_stats_table[_bridge][system['SYSTEM']] = {}
_stats_table[_bridge][system['SYSTEM']]['TS'] = system['TS']
_stats_table[_bridge][system['SYSTEM']]['TGID'] = int_id(system['TGID'])
if system['TO_TYPE'] == 'ON' or system['TO_TYPE'] == 'OFF':
if system['TIMER'] - _now > 0:
_stats_table[_bridge][system['SYSTEM']]['EXP_TIME'] = int(system['TIMER'] - _now)
else:
_stats_table[_bridge][system['SYSTEM']]['EXP_TIME'] = 'Expired'
if system['TO_TYPE'] == 'ON':
_stats_table[_bridge][system['SYSTEM']]['TO_ACTION'] = 'Disconnect'
else:
_stats_table[_bridge][system['SYSTEM']]['TO_ACTION'] = 'Connect'
else:
_stats_table[_bridge][system['SYSTEM']]['EXP_TIME'] = 'N/A'
_stats_table[_bridge][system['SYSTEM']]['TO_ACTION'] = 'None'
if system['ACTIVE'] == True:
_stats_table[_bridge][system['SYSTEM']]['ACTIVE'] = 'Connected'
_stats_table[_bridge][system['SYSTEM']]['COLOR'] = BLACK
_stats_table[_bridge][system['SYSTEM']]['BGCOLOR'] = GREEN
elif system['ACTIVE'] == False:
_stats_table[_bridge][system['SYSTEM']]['ACTIVE'] = 'Disconnected'
_stats_table[_bridge][system['SYSTEM']]['COLOR'] = WHITE
_stats_table[_bridge][system['SYSTEM']]['BGCOLOR'] = RED
for i in range(len(system['ON'])):
system['ON'][i] = str(int_id(system['ON'][i]))
_stats_table[_bridge][system['SYSTEM']]['TRIG_ON'] = ', '.join(system['ON'])
for i in range(len(system['OFF'])):
system['OFF'][i] = str(int_id(system['OFF'][i]))
_stats_table[_bridge][system['SYSTEM']]['TRIG_OFF'] = ', '.join(system['OFF'])
return _stats_table
######################################################################
#
# BUILD HBlink AND CONFBRIDGE TABLES FROM CONFIG/BRIDGES DICTS
# THIS CURRENTLY IS A TIMED CALL
#
build_time = time()
def build_stats():
global build_time
now = time()
if True: #now > build_time + 1:
if CONFIG:
table = 'd' + dtemplate.render(_table=CTABLE)
dashboard_server.broadcast(table)
if BRIDGES:
table = 'b' + btemplate.render(_table=BTABLE['BRIDGES'])
dashboard_server.broadcast(table)
build_time = now
def timeout_clients():
now = time()
try:
for client in dashboard_server.clients:
if dashboard_server.clients[client] + CLIENT_TIMEOUT < now:
logger.info('TIMEOUT: disconnecting client %s', dashboard_server.clients[client])
try:
dashboard.sendClose(client)
except Exception as e:
logger.error('Exception caught parsing client timeout %s', e)
except:
logger.info('CLIENT TIMEOUT: List does not exist, skipping. If this message persists, contact the developer')
def rts_update(p):
callType = p[0]
action = p[1]
trx = p[2]
system = p[3]
streamId = p[4]
sourcePeer = int(p[5])
sourceSub = int(p[6])
timeSlot = int(p[7])
destination = int(p[8])
if system in CTABLE['MASTERS']:
for peer in CTABLE['MASTERS'][system]['PEERS']:
if sourcePeer == peer:
bgcolor = RED
color = WHITE
else:
bgcolor = GREEN
color = BLACK
if action == 'START':
CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['TS'] = True
CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['COLOR'] = color
CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['BGCOLOR'] = bgcolor
CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['TYPE'] = callType
CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['SUB'] = '{} ({})'.format(alias_short(sourceSub, subscriber_ids), sourceSub)
CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['SRC'] = peer
CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['DEST'] = '{} ({})'.format(alias_tgid(destination,talkgroup_ids),destination)
if action == 'END':
CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['TS'] = False
CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['COLOR'] = BLACK
CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['BGCOLOR'] = WHITE2
CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['TYPE'] = ''
CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['SUB'] = ''
CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['SRC'] = ''
CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['DEST'] = ''
if system in CTABLE['OPENBRIDGES']:
CTABLE['OPENBRIDGES'][system]['TRX'] = trx
if trx == 'RX':
CTABLE['OPENBRIDGES'][system]['COLOR'] = GREEN2
else:
CTABLE['OPENBRIDGES'][system]['COLOR'] = RED
if action == 'START':
CTABLE['OPENBRIDGES'][system]['STREAMS'][streamId] = (trx, alias_call(sourceSub, subscriber_ids),'TG{}'.format(destination))
if action == 'END':
if streamId in CTABLE['OPENBRIDGES'][system]['STREAMS']:
del CTABLE['OPENBRIDGES'][system]['STREAMS'][streamId]
if system in CTABLE['PEERS']:
bgcolor = GREEN
if trx == 'RX':
bgcolor = RED
color = WHITE
else:
bgcolor = GREEN
color = BLACK
if action == 'START':
if destination == 9 and sourcePeer == 2602122:
CTABLE['PEERS'][system][timeSlot]['TS'] = False
CTABLE['PEERS'][system][timeSlot]['COLOR'] = BLACK
CTABLE['PEERS'][system][timeSlot]['BGCOLOR'] = WHITE2
CTABLE['PEERS'][system][timeSlot]['TYPE'] = ''
CTABLE['PEERS'][system][timeSlot]['SUB'] = ''
CTABLE['PEERS'][system][timeSlot]['SRC'] = ''
CTABLE['PEERS'][system][timeSlot]['DEST'] = ''
else:
CTABLE['PEERS'][system][timeSlot]['TS'] = True
CTABLE['PEERS'][system][timeSlot]['COLOR'] = color
CTABLE['PEERS'][system][timeSlot]['BGCOLOR'] = bgcolor
# CTABLE['PEERS'][system][timeSlot]['TYPE'] = callType[6:]
CTABLE['PEERS'][system][timeSlot]['SUB'] = '{} ({})'.format(alias_short(sourceSub, subscriber_ids), sourceSub)
CTABLE['PEERS'][system][timeSlot]['SRC'] = sourcePeer
CTABLE['PEERS'][system][timeSlot]['DEST'] = '{} ({})'.format(alias_tgid(destination,talkgroup_ids),destination)
if action == 'END':
CTABLE['PEERS'][system][timeSlot]['TS'] = False
CTABLE['PEERS'][system][timeSlot]['COLOR'] = BLACK
CTABLE['PEERS'][system][timeSlot]['BGCOLOR'] = WHITE2
CTABLE['PEERS'][system][timeSlot]['TYPE'] = ''
CTABLE['PEERS'][system][timeSlot]['SUB'] = ''
CTABLE['PEERS'][system][timeSlot]['SRC'] = ''
CTABLE['PEERS'][system][timeSlot]['DEST'] = ''
build_stats()
######################################################################
#
# PROCESS INCOMING MESSAGES AND TAKE THE CORRECT ACTION DEPENING ON
# THE OPCODE
#
def process_message(_bmessage):
global CTABLE, CONFIG, BRIDGES, CONFIG_RX, BRIDGES_RX
_message = _bmessage.decode('utf-8', 'ignore')
opcode = _message[:1]
_now = strftime('%Y-%m-%d %H:%M:%S %Z', localtime(time()))
if opcode == OPCODE['CONFIG_SND']:
logging.debug('got CONFIG_SND opcode')
CONFIG = load_dictionary(_bmessage)
CONFIG_RX = strftime('%Y-%m-%d %H:%M:%S', localtime(time()))
if CTABLE['MASTERS']:
update_hblink_table(CONFIG, CTABLE)
else:
build_hblink_table(CONFIG, CTABLE)
elif opcode == OPCODE['BRIDGE_SND']:
logging.debug('got BRIDGE_SND opcode')
BRIDGES = load_dictionary(_bmessage)
BRIDGES_RX = strftime('%Y-%m-%d %H:%M:%S', localtime(time()))
if BRIDGES_INC:
BTABLE['BRIDGES'] = build_bridge_table(BRIDGES)
elif opcode == OPCODE['LINK_EVENT']:
logging.info('LINK_EVENT Received: {}'.format(repr(_message[1:])))
elif opcode == OPCODE['BRDG_EVENT']:
logging.info('BRIDGE EVENT: {}'.format(repr(_message[1:])))
p = _message[1:].split(",")
rts_update(p)
if p[0] == 'GROUP VOICE' and p[2] != 'TX':
if p[1] == 'END':
log_message = '{}: {} {}: SYS: {:8.8s} SRC: {:9.9s}; {:9.9s} TS: {} TGID: {:7.7s} {:17.17s} SUB: {:9.9s}; {:18.18s} Time: {}s'.format(_now[11:], p[0][6:], p[1], p[3], p[5], alias_call(int(p[5]), subscriber_ids), p[7],p[8],alias_tgid(int(p[8]),talkgroup_ids), p[6], alias_short(int(p[6]), subscriber_ids), int(float(p[9])))
# log only to file if system is NOT OpenBridge event (not logging open bridge system, name depends on your OB definitions) AND transmit time is LONGER as 2sec (make sense for very short transmits)
if LASTHEARD_INC:
if int(float(p[9]))> 2:
log_lh_message = '{},{},{},{},{},{},{},TS{},TG{},{},{},{}'.format(_now, p[9], p[0], p[1], p[3], p[5], alias_call(int(p[5]), subscriber_ids), p[7], p[8],alias_tgid(int(p[8]),talkgroup_ids),p[6], alias_short(int(p[6]), subscriber_ids))
lh_logfile = open('/opt/HBmonitor/log/lastheard.log', "a")
lh_logfile.write(log_lh_message + '\n')
lh_logfile.close()
# Lastheard in Dashboard by SP2ONG
my_list=[]
n=0
f = open("/opt/HBmonitor/templates/lastheard.html", "w")
f.write("<br><fieldset style=\"border-radius: 8px; background-color:#e0e0e0e0; text-algin: lef; margin-left:15px;margin-right:15px;font-size:14px;border-top-left-radius: 10px; border-top-right-radius: 10px;border-bottom-left-radius: 10px; border-bottom-right-radius: 10px;\">\n")
f.write("<legend><b><font color=\"#000\">&nbsp;.: Lastheard (10) :.&nbsp;</font></b></legend>\n")
f.write("<table style=\"width:100%; font: 10pt arial, sans-serif\">\n")
f.write("<TR style=\" height: 32px;font: 10pt arial, sans-serif; background-color:#7e6959; color:white\"><TH>Date<TH>Time<TH>Slot<TH>TG#<TH>TG Name<TH>Callsign (DMR-Id)<TH>Name<TH>Dur TX (s)<TH>Source ID<TH>System</TR>\n")
with open('/opt/HBmonitor/log/lastheard.log', 'r') as textfile:
for row in islice(reversed(list(csv.reader(textfile))),100):
duration=row[1]
dur=str(int(float(duration.strip())))
if row[10] not in my_list:
if len(row) < 13:
hline="<TR style=\"background-color:#f9f9f9f9;\"><TD>"+row[0][:10]+"<TD>"+row[0][11:16]+"<TD>"+row[7][2:]+"<TD><font color=#996633><b>"+row[8][2:]+"</b></font><TD><font color=#356244><b>"+row[9]+"</b></font><TD><font color=brown><b><a target=\"_blank\" href=https://qrz.com/db/"+row[11]+">"+row[11]+"</a></b></font><span style=\"font: 7pt arial,sans-serif\"> ("+row[10]+")</span><TD><font color=#002d62><b></b></font><TD>"+dur+"<TD>"+row[5]+"<TD>"+row[4]+"</TR>"
my_list.append(row[10])
n += 1
else:
hline="<TR style=\"background-color:#f9f9f9f9;\"><TD>"+row[0][:10]+"<TD>"+row[0][11:16]+"<TD>"+row[7][2:]+"<TD><font color=#996633><b>"+row[8][2:]+"</b></font><TD><font color=#356244><b>"+row[9]+"</b></font><TD><font color=brown><b><a target=\"_blank\" href=https://qrz.com/db/"+row[11]+">"+row[11]+"</a></b></font><span style=\"font: 7pt arial,sans-serif\"> ("+row[10]+")</span><TD><font color=#002d62><b>"+row[12]+"</b></font><TD>"+dur+"<TD>"+row[5]+"<TD>"+row[4]+"</TR>"
my_list.append(row[10])
n += 1
f.write(hline+"\n")
if n == 10:
break
f.write("</table></fieldset><br>")
f.close()
# End of Lastheard
elif p[1] == 'START':
log_message = '{}: {} {}: SYS: {:8.8s} SRC: {:9.9s}; {:9.9s} TS: {} TGID: {:7.7s} {:17.17s} SUB: {:9.9s}; {:18.18s}'.format(_now[11:], p[0][6:], p[1], p[3], p[5], alias_call(int(p[5]), subscriber_ids), p[7],p[8], alias_tgid(int(p[8]),talkgroup_ids), p[6], alias_short(int(p[6]), subscriber_ids))
elif p[1] == 'END WITHOUT MATCHING START':
log_message = '{}: {} {} on SYSTEM {:8.8s}: SRC: {:9.9s}; {:9.9s} TS: {} TGID: {:7.7s} {:17.17s} SUB: {:9.9s}; {:18.18s}'.format(_now[11:], p[0][6:], p[1], p[3], p[5], alias_call(int(p[5]), subscriber_ids), p[7], p[8],alias_tgid(int(p[8]),talkgroup_ids),p[6], alias_short(int(p[6]), subscriber_ids))
else:
log_message = '{}: UNKNOWN GROUP VOICE LOG MESSAGE'.format(_now)
dashboard_server.broadcast('l' + log_message)
LOGBUF.append(log_message)
else:
logging.debug('{}: UNKNOWN LOG MESSAGE'.format(_now))
else:
logging.debug('got unknown opcode: {}, message: {}'.format(repr(opcode), repr(_message[1:])))
def load_dictionary(_message):
data = _message[1:]
return loads(data)
logging.debug('Successfully decoded dictionary')
######################################################################
#
# COMMUNICATION WITH THE HBlink INSTANCE
#
class report(NetstringReceiver):
def __init__(self):
pass
def connectionMade(self):
pass
def connectionLost(self, reason):
pass
def stringReceived(self, data):
process_message(data)
class reportClientFactory(ReconnectingClientFactory):
def __init__(self):
logging.info('reportClient object for connecting to HBlink.py created at: %s', self)
def startedConnecting(self, connector):
logging.info('Initiating Connection to Server.')
if 'dashboard_server' in locals() or 'dashboard_server' in globals():
dashboard_server.broadcast('q' + 'Connection to HBlink Established')
def buildProtocol(self, addr):
logging.info('Connected.')
logging.info('Resetting reconnection delay')
self.resetDelay()
return report()
def clientConnectionLost(self, connector, reason):
logging.info('Lost connection. Reason: %s', reason)
ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
dashboard_server.broadcast('q' + 'Connection to HBlink Lost')
def clientConnectionFailed(self, connector, reason):
logging.info('Connection failed. Reason: %s', reason)
ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
######################################################################
#
# WEBSOCKET COMMUNICATION WITH THE DASHBOARD CLIENT
#
class dashboard(WebSocketServerProtocol):
def onConnect(self, request):
logging.info('Client connecting: %s', request.peer)
def onOpen(self):
logging.info('WebSocket connection open.')
self.factory.register(self)
self.sendMessage(('d' + dtemplate.render(_table=CTABLE)).encode('utf-8'))
self.sendMessage(('b' + btemplate.render(_table=BTABLE['BRIDGES'])).encode('utf-8'))
for _message in LOGBUF:
if _message:
self.sendMessage('l' + _bmessage)
def onMessage(self, payload, isBinary):
if isBinary:
logging.info('Binary message received: %s bytes', len(payload))
else:
logging.info('Text message received: %s', payload)
def connectionLost(self, reason):
WebSocketServerProtocol.connectionLost(self, reason)
self.factory.unregister(self)
def onClose(self, wasClean, code, reason):
logging.info('WebSocket connection closed: %s', reason)
class dashboardFactory(WebSocketServerFactory):
def __init__(self, url):
WebSocketServerFactory.__init__(self, url)
self.clients = {}
def register(self, client):
if client not in self.clients:
logging.info('registered client %s', client.peer)
self.clients[client] = time()
def unregister(self, client):
if client in self.clients:
logging.info('unregistered client %s', client.peer)
del self.clients[client]
def broadcast(self, msg):
logging.debug('broadcasting message to: %s', self.clients)
for c in self.clients:
c.sendMessage(msg.encode('utf8'))
logging.debug('message sent to %s', c.peer)
######################################################################
#
# STATIC WEBSERVER
#
class web_server(Resource):
isLeaf = True
def render_GET(self, request):
logging.info('static website requested: %s', request)
return (index_html).encode('utf-8')
if __name__ == '__main__':
logging.basicConfig(
level=logging.INFO,
filename = (LOG_PATH + LOG_NAME),
filemode='a',
format='%(asctime)s %(levelname)s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)
logger = logging.getLogger(__name__)
logging.info('monitor.py starting up')
logger.info('\n\n\tCopyright (c) 2016, 2017, 2018, 2019\n\tThe Regents of the K0USY Group. All rights reserved.\n\n\tPython 3 port:\n\t2019 Steve Miller, KC1AWV <smiller@kc1awv.net>\n\n\tModifed by:\t SP2ONG 2019\n\n')
# Download alias files
result = try_download(PATH, PEER_FILE, PEER_URL, (FILE_RELOAD * 86400))
logging.info(result)
result = try_download(PATH, SUBSCRIBER_FILE, SUBSCRIBER_URL, (FILE_RELOAD * 86400))
logging.info(result)
# Make Alias Dictionaries
peer_ids = mk_full_id_dict(PATH, PEER_FILE, 'peer')
if peer_ids:
logging.info('ID ALIAS MAPPER: peer_ids dictionary is available')
subscriber_ids = mk_full_id_dict(PATH, SUBSCRIBER_FILE, 'subscriber')
if subscriber_ids:
logging.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
talkgroup_ids = mk_full_id_dict(PATH, TGID_FILE, 'tgid')
if talkgroup_ids:
logging.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
local_subscriber_ids = mk_full_id_dict(PATH, LOCAL_SUB_FILE, 'subscriber')
if local_subscriber_ids:
logging.info('ID ALIAS MAPPER: local_subscriber_ids added to subscriber_ids dictionary')
subscriber_ids.update(local_subscriber_ids)
local_peer_ids = mk_full_id_dict(PATH, LOCAL_PEER_FILE, 'peer')
if local_peer_ids:
logging.info('ID ALIAS MAPPER: local_peer_ids added peer_ids dictionary')
peer_ids.update(local_peer_ids)
# Jinja2 Stuff
env = Environment(
loader=PackageLoader('monitor', 'templates'),
autoescape=select_autoescape(['html', 'xml'])
)
dtemplate = env.get_template('hblink_table.html')
btemplate = env.get_template('bridge_table.html')
# Create Static Website index file
index_html = get_template(PATH + 'index_template.html')
index_html = index_html.replace('<<<system_name>>>', REPORT_NAME)
if CLIENT_TIMEOUT > 0:
index_html = index_html.replace('<<<timeout_warning>>>', 'Continuous connections not allowed. Connections time out in {} seconds'.format(CLIENT_TIMEOUT))
else:
index_html = index_html.replace('<<<timeout_warning>>>', '')
# Start update loop
update_stats = task.LoopingCall(build_stats)
update_stats.start(FREQUENCY)
# Start a timout loop
if CLIENT_TIMEOUT > 0:
timeout = task.LoopingCall(timeout_clients)
timeout.start(10)
# Connect to HBlink
reactor.connectTCP(HBLINK_IP, HBLINK_PORT, reportClientFactory())
# Create websocket server to push content to clients
dashboard_server = dashboardFactory('ws://*:9000')
dashboard_server.protocol = dashboard
reactor.listenTCP(9000, dashboard_server)
# Create static web server to push initial index.html
website = Site(web_server())
reactor.listenTCP(WEB_SERVER_PORT, website)
reactor.run()

5
requirements.txt Normal file
View File

@ -0,0 +1,5 @@
Twisted
dmr_utils3
bitstring
autobahn
jinja2