diff --git a/HBMonv2/README.md b/HBMonv2/README.md new file mode 100644 index 0000000..c70a54c --- /dev/null +++ b/HBMonv2/README.md @@ -0,0 +1,133 @@ +**HBmonitor is a "web dashboard" for HBlink by N0MJS.** + +***This is version of HBMonitor V2 by SP2ONG 2019-2021*** + +The main difference between HBMonitor v1 and v2 is the layout, i.e. the main page shows condensed +information and on the subpages, you can see the individual content that was shown on v1 + +I recommend not running HBmonitor on the same computer as HBlink3 + +HBmonitor tested on Debian v9 and v10 + +This version of HBMonv2 requires a web server like apache2, lighttpd and +php support running on the server. + + + cd /opt + git clone https://github.com/sp2ong/HBMonv2.git + cd HBMonv2 + chmod +x install.sh + ./install.sh + cp config-SAMPLE.py config.py + edit config.py and change what you necessary + + You need to copy the contents of the /opt/HBMonv2/html directory to + the web server directory. Suppose your web server is available + as http://dmrserver.org, copy the file to for example /var/www/html + + If you copy files to /var/www/html/hbmon, HBMonitor will be + accessible from http://dmrserver.org/hbmon + + You can copy to /var/www/hbmon and start HBMonitor access by configuring + virtual the web server for subdomains e.g. hbmon.dmrserver.org + the access will then be http://hbmon.dmrserver.org + + In the html/include/ directory there is a config.php file in which you + set the color theme and name for your Dashboard. + + In the html/include/config.php you can defined height of Server Activity + window: 45px; 1 row, 60px 2 rows, 80px 3 rows: + define("HEIGHT_ACTIVITY","45px"); + + In the html directory there is a buttons.html file that you can tune to menu keys + + The logo image you can replace with file image in html directory img/logo.png + cp utils/lastheard /etc/cron.daily/ + chmod +x /etc/cron.daily/lastheard + cp utils/hbmon.service /lib/systemd/system/ + systemctl enable hbmon + systemctl start hbmon + systemctl status hbmon + forward TCP port 9000 and web server port in firewall + + Please setup your SYSTEM INFO subpage with the following instruction: + + https://github.com/sp2ong/HBMonv2/tree/main/sysinfo + + Please remember the table lastheard displays only station transmissions + that are longer than 2 sec. + use >=0 instead of >2 if you want to record all activities in line: + + if int(float(p[9])) > 2: + + If you want to have more than the last 15 entries in the Lastheard table + change in the monitor.py file line from: + + # maximum number of lists in lastheard on the main page + if n == 15: + to for example: + if n == 25: + + + I recommend that you do not use the BRIDGE_INC = True option to display bridge information + (if you have multiple bridges displaying this information will increase the CPU load, + try to use BRIDGES_INC = False in config.py) + + + *************************************************************************************** + + The HBMonv2 version without use external web server like apache2 etc is still available: + + cd /opt + git clone https://github.com/sp2ong/HBMonv2.git + cd HBMonv2 + git checkout webserver-python + chmod +x install.sh + ./install.sh + cp config-SAMPLE.py config.py + edit config.py and change what you necessary + cp utils/hbmon.service /lib/systemd/system/ + systemctl enable hbmon + systemctl start hbmon + systemctl status hbmon + forward TCP port 9000 and web server port 8080 in firewall + + ***************************************************************************************** +--- + +After update distribution of Linux jinja2 to version 3.x you can find problem with run HBMonitor with error: + + ValueError: The 'monitor' package was not installed in a way that PackageLoader understands. + + +You must uninstall jinja2 and reinstalling with this version Jinja2==2.11.3 + + pip3 uninstall jinja2 + +and + + pip3 install Jinja2==2.11.3 + +--- + +**hbmonitor3 by KC1AWV** + +Python 3 implementation of N0MJS HBmonitor for HBlink https://github.com/kc1awv/hbmonitor3 + +--- + +Copyright (C) 2013-2018 Cortney T. Buffington, N0MJS + +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 + +--- + + + diff --git a/HBMonv2/config_SAMPLE.py b/HBMonv2/config_SAMPLE.py new file mode 100644 index 0000000..066c62d --- /dev/null +++ b/HBMonv2/config_SAMPLE.py @@ -0,0 +1,31 @@ +CONFIG_INC = True # Include HBlink stats +HOMEBREW_INC = True # Display Homebrew Peers status +LASTHEARD_INC = True # Display lastheard table on main page +BRIDGES_INC = False # Display Bridge status and button +EMPTY_MASTERS = False # Display Enable (True) or DISABLE (False) empty masters in status +# +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 +CLIENT_TIMEOUT = 0 # Clients are timed out after this many seconds, 0 to disable + +# Generally you don't need to use this but +# if you don't want to show in lastherad received traffic from OBP link put NETWORK ID +# for example: "260210,260211,260212" +OPB_FILTER = "" + +# Files and stuff for loading alias files for mapping numbers to names +PATH = './' # MUST END IN '/' +PEER_FILE = 'peer_ids.json' # Will auto-download +SUBSCRIBER_FILE = 'subscriber_ids.json' # Will auto-download +TGID_FILE = 'talkgroup_ids.json' # User provided +LOCAL_SUB_FILE = 'local_subscriber_ids.json' # User provided (optional, leave '' if you don't use it) +LOCAL_PEER_FILE = 'local_peer_ids.json' # User provided (optional, leave '' if you don't use it) +LOCAL_TGID_FILE = 'local_talkgroup_ids.json' # User provided (optional, leave '' if you don't use it) +FILE_RELOAD = 15 # Number of days before we reload DMR-MARC database files +PEER_URL = 'https://database.radioid.net/static/rptrs.json' +SUBSCRIBER_URL = 'https://database.radioid.net/static/users.json' + +# Settings for log files +LOG_PATH = './log/' # MUST END IN '/' +LOG_NAME = 'hbmon.log' diff --git a/HBMonv2/html/bridges.php b/HBMonv2/html/bridges.php new file mode 100644 index 0000000..35ddc58 --- /dev/null +++ b/HBMonv2/html/bridges.php @@ -0,0 +1,35 @@ + + + + + +DMR Server monitor - STATUS + + + + + +
+ +
+
+

+

+
+
+ +

+
+
+

+ Copyright (c) 2016-2021
The Regents of the K0USY Group. All rights reserved.
Version SP2ONG 2019-2021

+ + +

+
+ + diff --git a/HBMonv2/html/buttons.html b/HBMonv2/html/buttons.html new file mode 100644 index 0000000..1df2b41 --- /dev/null +++ b/HBMonv2/html/buttons.html @@ -0,0 +1,60 @@ +
+ + + +  + +  + +  + +  + +  + +  + +  + +  + + + + + +
+

diff --git a/HBMonv2/html/css/index.html b/HBMonv2/html/css/index.html new file mode 100644 index 0000000..7879e1c --- /dev/null +++ b/HBMonv2/html/css/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/HBMonv2/html/css/styles.php b/HBMonv2/html/css/styles.php new file mode 100644 index 0000000..a00f520 --- /dev/null +++ b/HBMonv2/html/css/styles.php @@ -0,0 +1,184 @@ + +.link +.button +.dropbtn + +#lact { + height:; + width: 100%; + border-collapse: collapse; + border:none; +} +#rcorner { + display: flex; + align-items: center; + justify-content: center; + vertical-align: middle; + text-align:center; + justify-content: center; + align-items: center; + border-radius: 10px; + -moz-border-radius:10px; + -webkit-border-radius:10px; + border: 1px solid LightGrey; + background: #e9e9e9; + font: 12pt arial, sans-serif; + font-weight:bold; + margin-top:2px; + margin-right:0px; + margin-left:0px; + margin-bottom:0px; + color:#002d62; + white-space:normal; + height: 100%; + line-height:21px; +} +#rcornerh { + display: flex; + display: -webkit-flex; + justify-content: center; + align-items: center; + border-radius:8px; + -moz-border-radius:8px; + -webkit-border-radius:8px; + font: 9pt arial, sans-serif; + font-weight:bold; + color:white; + height:25px; + line-height:25px; + +} + +table, td, th { + border: .5px solid #d0d0d0; + padding: 2px; + border-collapse: collapse; + border-spacing: 0; + text-align:center;} +tr.theme_color +th.theme_color + +html { + overflow: -moz-scrollbars-vertical; + overflow-y: scroll; +} + + +table.log {background-color: #f0f0f0; border-collapse: collapse; border: 1px solid #C1DAD7; width: 100%;} +th.log {height: 30px; text-align: center;} +tr:nth-child(even).log {background-color: #fafafa;text-align: center;} +td.log {font-family: Monospace; height: 20px;} + + +a:link { + color: #0066ff; + text-decoration: none; +} + +/* visited link */ +a:visited { + color: #0066ff; + text-decoration: none; +} + +/* mouse over link */ +a:hover { + color: hotpink; + text-decoration: underline; +} +/* selected link */ +a:active { + color: #0066ff; + text-decoration: none; +} +.tooltip { + position: relative; + opacity: 1; + display: inline-block; + border-bottom: 1px dotted black; +} + +.tooltip .tooltiptext { + visibility: hidden; + width: 280px; + background-color: #6E6E6E; + box-shadow: 4px 4px 6px #3b3b3b; + 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 { + border: none; + padding: 8px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 14px; + font-weight: 500; + margin: 4px 2px; + border-radius: 8px; + box-shadow: 0px 8px 10px rgba(0,0,0,0.1); +} + +.link:hover {background-color:rgb(140,140,140);background: rgb(140,140,140); color:white;} +.dropdown:hover .dropbtn {background-color:rgb(140,140,140);background: rgb(140,140,140); color:white;} + +.dropbtn { + border: none; + padding: 8px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 14px; + font-weight: 500; + margin: 4px 2px; + border-radius: 8px; + box-shadow: 0px 8px 10px rgba(0,0,0,0.1); +} + +/* The container
- 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: 1000; +} + +/* 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;} + diff --git a/HBMonv2/html/img/HBlink.png b/HBMonv2/html/img/HBlink.png new file mode 100644 index 0000000..ca22497 Binary files /dev/null and b/HBMonv2/html/img/HBlink.png differ diff --git a/HBMonv2/html/img/HBlink.svg b/HBMonv2/html/img/HBlink.svg new file mode 100644 index 0000000..f552227 --- /dev/null +++ b/HBMonv2/html/img/HBlink.svg @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + Produced by OmniGraffle 7.12.1 + 2018-03-14 13:37:44 +0000 + + + image/svg+xml + + + + + + + Canvas 2 + + Layer 1 + + + path6007 + + + + + + + + + + + + + + + + + + + + + path6381 + + + + + + + + + + + + diff --git a/HBMonv2/html/img/cpu.png b/HBMonv2/html/img/cpu.png new file mode 100644 index 0000000..3653b63 Binary files /dev/null and b/HBMonv2/html/img/cpu.png differ diff --git a/HBMonv2/html/img/hblink_powered.png b/HBMonv2/html/img/hblink_powered.png new file mode 100644 index 0000000..6b50e03 Binary files /dev/null and b/HBMonv2/html/img/hblink_powered.png differ diff --git a/HBMonv2/html/img/hbmon.png b/HBMonv2/html/img/hbmon.png new file mode 100644 index 0000000..6db4026 Binary files /dev/null and b/HBMonv2/html/img/hbmon.png differ diff --git a/HBMonv2/html/img/hdd.png b/HBMonv2/html/img/hdd.png new file mode 100644 index 0000000..2629dd1 Binary files /dev/null and b/HBMonv2/html/img/hdd.png differ diff --git a/HBMonv2/html/img/index.html b/HBMonv2/html/img/index.html new file mode 100644 index 0000000..7879e1c --- /dev/null +++ b/HBMonv2/html/img/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/HBMonv2/html/img/logo.png b/HBMonv2/html/img/logo.png new file mode 100644 index 0000000..c4174f1 Binary files /dev/null and b/HBMonv2/html/img/logo.png differ diff --git a/HBMonv2/html/img/mem.png b/HBMonv2/html/img/mem.png new file mode 100644 index 0000000..83c3055 Binary files /dev/null and b/HBMonv2/html/img/mem.png differ diff --git a/HBMonv2/html/img/mrtg/index.html b/HBMonv2/html/img/mrtg/index.html new file mode 100644 index 0000000..7879e1c --- /dev/null +++ b/HBMonv2/html/img/mrtg/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/HBMonv2/html/img/mrtg/localhost_2-day.png b/HBMonv2/html/img/mrtg/localhost_2-day.png new file mode 100644 index 0000000..ba69ddc Binary files /dev/null and b/HBMonv2/html/img/mrtg/localhost_2-day.png differ diff --git a/HBMonv2/html/img/tempC.png b/HBMonv2/html/img/tempC.png new file mode 100644 index 0000000..d14a6ea Binary files /dev/null and b/HBMonv2/html/img/tempC.png differ diff --git a/HBMonv2/html/include/config.php b/HBMonv2/html/include/config.php new file mode 100644 index 0000000..a228e53 --- /dev/null +++ b/HBMonv2/html/include/config.php @@ -0,0 +1,41 @@ + diff --git a/HBMonv2/html/include/index.html b/HBMonv2/html/include/index.html new file mode 100644 index 0000000..7879e1c --- /dev/null +++ b/HBMonv2/html/include/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/HBMonv2/html/index.php b/HBMonv2/html/index.php new file mode 100644 index 0000000..3405cf0 --- /dev/null +++ b/HBMonv2/html/index.php @@ -0,0 +1,45 @@ + + + + + +DMR Server monitor - STATUS + + + + + +
+ +
+
+

+

+
+ +
+ +

+
+ + +
+

+Copyright (c) 2016-2021
The Regents of the K0USY Group. All rights reserved.
Version SP2ONG 2019-2021

+ + +

+
+
+ + diff --git a/HBMonv2/html/info.php b/HBMonv2/html/info.php new file mode 100644 index 0000000..a968803 --- /dev/null +++ b/HBMonv2/html/info.php @@ -0,0 +1,59 @@ + + + + + +DMR Server monitor - Info + + + + + +
+ +
+
+

+

+
+ + +
+
+ .: Talk Groups :.  + + + + + + + + + + + + + + + + + + + +
TG#TS 1TS 2Description
 TG 5    D | S Talk group XLX132-D D-Star/DMR/C4FM.
 TG 9999    D | S Echo (Parrot).
+
+Hotspot: D - duplex | S - simplex +

+ +

+Copyright (c) 2016-2021
The Regents of the K0USY Group. All rights reserved.
Version SP2ONG 2019-2021

+ + +

+
+ + diff --git a/HBMonv2/html/log.php b/HBMonv2/html/log.php new file mode 100644 index 0000000..656a7f8 --- /dev/null +++ b/HBMonv2/html/log.php @@ -0,0 +1,119 @@ + + + + + + +DMR Server monitor - STATUS + + + + + +
+ +
+
+

+

+
+ +
+

+

+
+ + + + +"; +$s_r = "".$s.' '.$date_eu[2].".".$date_eu[1].".".$date_eu[0].$s.' '.substr($log_time[$i],11,5).$s.' '.$user_call[$i]." (".$user_id[$i].")".$s.''.TRIM($user_name[$i]).''.$s.''.$tg[$i].''.$s.' '.$tgname[$i].''.$s."
".round($transmit_timer[$i])."
".$s.$system[$i]."\n"; +} + +echo "\n
LastHeard
  Date Time Callsign (DMR-Id)  Name TG#  TG NameTX (s) Source +
"; +$s_m = ""; + +// output to html table from the newest entry to the oldest +for ($i=count($log_time)-1; $i >= 0; $i--) + +{ +// prepare date string for output in european format +$split_date = substr($log_time[$i],0,10); +$date_eu = explode("-", $split_date); + +$ts[$i] = substr($ts[$i],-1); +$tg[$i] = substr($tg[$i],2); + +// define special character convert for number zero - we write calls with number zero with this character in logs in Germany +$src_name[$i] = str_replace("0","Ø",$src_name[$i]); +if (substr($user_call[$i],2,1)=="0") { $user_call[$i] = str_replace("0","Ø",$user_call[$i]); } + +$log_time[$i]=substr($log_time[$i],0,19); + +// thats a special thing for an Id comes without DMR-Id from PEGASUS project - it means we need to convert to "NoCall" thats for calls from source ECHOLINK +if ($user_id[$i]=="1234567") {$user_call[$i] = "*NoCallsign*"; $user_id[$i]="-";} + +// output table +echo "
"; + +// close logfile after parsing +fclose ($handle); +?> +
+

+Copyright (c) 2016-2021
The Regents of the K0USY Group. All rights reserved.
Version SP2ONG 2019-2021
+ + +© developed by DL1BZ as logging-extension of HBmonitor (2018,2019)
+

+
+
+ + diff --git a/HBMonv2/html/masters.php b/HBMonv2/html/masters.php new file mode 100644 index 0000000..944ecee --- /dev/null +++ b/HBMonv2/html/masters.php @@ -0,0 +1,35 @@ + + + + + +DMR Server monitor - Masters + + + + + +
+ +
+
+

+

+ +
+
+ +

+
+

+Copyright (c) 2016-2021
The Regents of the K0USY Group. All rights reserved.
Version SP2ONG 2019-2021

+ + +

+
+ + diff --git a/HBMonv2/html/moni.php b/HBMonv2/html/moni.php new file mode 100644 index 0000000..085aaf4 --- /dev/null +++ b/HBMonv2/html/moni.php @@ -0,0 +1,42 @@ + + + + + +DMR Server - Monitor + + + + + +
+ +
+
+

+

+
+ +
+ +

+
+

+ +
+
+ .: Call log window :.  +

+
+

+Copyright (c) 2016-2021
The Regents of the K0USY Group. All rights reserved.
Version SP2ONG 2019-2021

+ + +

+
+ + diff --git a/HBMonv2/html/opb.php b/HBMonv2/html/opb.php new file mode 100644 index 0000000..7d76b67 --- /dev/null +++ b/HBMonv2/html/opb.php @@ -0,0 +1,35 @@ + + + + + +DMR Server monitor - OpenBridge + + + + + +
+ +
+
+

+

+
+ +
+ +

+
+

+Copyright (c) 2016-2021
The Regents of the K0USY Group. All rights reserved.
Version SP2ONG 2019-2021

+ + +

+
+ + diff --git a/HBMonv2/html/peers.php b/HBMonv2/html/peers.php new file mode 100644 index 0000000..3f2b6b2 --- /dev/null +++ b/HBMonv2/html/peers.php @@ -0,0 +1,35 @@ + + + + + +DMR Server monitor - Peers + + + + + +
+ +
+
+

+

+
+ +
+ +

+
+

+Copyright (c) 2016-2021
The Regents of the K0USY Group. All rights reserved.
Version SP2ONG 2019-2021

+ + +

+
+ + diff --git a/HBMonv2/html/scripts/hbmon.js b/HBMonv2/html/scripts/hbmon.js new file mode 100644 index 0000000..3dc2d50 --- /dev/null +++ b/HBMonv2/html/scripts/hbmon.js @@ -0,0 +1,79 @@ + var sock = null; + var ellog = null; + + window.onload = function() { + var wsuri; + + ellog = document.getElementById('log'); + + bridge_table = document.getElementById('bridge'); + main_table = document.getElementById('main'); + masters_table = document.getElementById('masters'); + opb_table = document.getElementById('opb'); + peers_table = document.getElementById('peers'); + + wsuri = (((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.hostname + ":9000"); + + if ("WebSocket" in window) { + sock = new WebSocket(wsuri); + } else if ("MozWebSocket" in window) { + sock = new MozWebSocket(wsuri); + } else { + if (ellog != null) { + log("Browser does not support WebSocket!");} + } + + if (sock) { + sock.onopen = function() { + if (ellog != null) { + log("Connected to " + wsuri);} + } + sock.onclose = function(e) { + if (ellog != null) { + log("Connection closed (wasClean = " + e.wasClean + ", code = " + e.code + ", reason = '" + e.reason + "')");} + sock = null; + bridge_table.innerHTML = ""; + main_table.innerHTML = ""; + masters_table.innerHTML = ""; + opb_table.innerHTML = ""; + peers_table.innerHTML = ""; + } + sock.onmessage = function(e) { + var opcode = e.data.slice(0,1); + var message = e.data.slice(1); + if (opcode == "b") { + Bmsg(message); + } else if (opcode == "c") { + Cmsg(message); + } else if (opcode == "i") { + Imsg(message); + } else if (opcode == "o") { + Omsg(message); + } else if (opcode == "p") { + Pmsg(message); + } else if (opcode == "l") { + if (ellog != null) { + log(message);} + } else if (opcode == "q") { + log(message); + bridge_table.innerHTML = ""; + main_table.innerHTML = ""; + masters_table.innerHTML = ""; + opb_table.innerHTML = ""; + peers_table.innerHTML = ""; + } else { + log("Unknown Message Received: " + message); + } + } + } + }; + + function Bmsg(_msg) {bridge_table.innerHTML = _msg;}; + function Cmsg(_msg) {masters_table.innerHTML = _msg;}; + function Imsg(_msg) {main_table.innerHTML = _msg;}; + function Omsg(_msg) {opb_table.innerHTML = _msg;}; + function Pmsg(_msg) {peers_table.innerHTML = _msg;}; + + function log(_msg) { + ellog.innerHTML += _msg + '\n'; + ellog.scrollTop = ellog.scrollHeight;}; diff --git a/HBMonv2/html/scripts/index.html b/HBMonv2/html/scripts/index.html new file mode 100644 index 0000000..7879e1c --- /dev/null +++ b/HBMonv2/html/scripts/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/HBMonv2/html/sysinfo.php b/HBMonv2/html/sysinfo.php new file mode 100644 index 0000000..c00c69b --- /dev/null +++ b/HBMonv2/html/sysinfo.php @@ -0,0 +1,55 @@ + + + + + + +DMR Server monitor - System Info + + + + + +
+ +
+
+

+

+
+ + +
+ .: System Info :.  +
+ +

+ +

+ +

+ +

+ +

+

+BLUE Outgoing Traffic in Bits per Second | GREEN Incoming Traffic in Bits per Second +
+
+
+

+Copyright (c) 2016-2021
The Regents of the K0USY Group. All rights reserved.
Version SP2ONG 2019-2021

+ + +

+
+ + diff --git a/HBMonv2/install.sh b/HBMonv2/install.sh new file mode 100644 index 0000000..2dc9e5a --- /dev/null +++ b/HBMonv2/install.sh @@ -0,0 +1,6 @@ +#! /bin/bash + +# Install the required support programs +apt-get install python3 python3-pip python3-dev libffi-dev libssl-dev cargo sed -y +pip3 install setuptools wheel +pip3 install -r requirements.txt diff --git a/HBMonv2/local_talkgroup_ids.json b/HBMonv2/local_talkgroup_ids.json new file mode 100644 index 0000000..5f00249 --- /dev/null +++ b/HBMonv2/local_talkgroup_ids.json @@ -0,0 +1,14 @@ +{ + "results": [ + { + "tgid": 1, + "callsign": "TG Local 1", + "id": "1" + }, + { + "tgid": 2, + "callsign": "TG Local 2", + "id": "2" + } + ] +} diff --git a/HBMonv2/monitor.py b/HBMonv2/monitor.py new file mode 100644 index 0000000..d280257 --- /dev/null +++ b/HBMonv2/monitor.py @@ -0,0 +1,1065 @@ +#!/usr/bin/env python3 +# +############################################################################### +# Copyright (C) 2016-2019 Cortney T. Buffington, N0MJS +# +# 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 +# +############################################################################### +############################################################################### +# +# HBMonitor v2 (2021) Version by Waldek SP2ONG +# +############################################################################### + +# Standard modules +import logging +import sys +import datetime + +import os +import csv +from itertools import islice +from subprocess import check_call, CalledProcessError + +# Twisted modules +from twisted.internet.protocol import ReconnectingClientFactory, Protocol +from twisted.protocols.basic import NetstringReceiver +from twisted.internet import reactor, task + +import base64 + +# 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, try_download, bytes_4 +from json import load as jload + +# Configuration variables and constants +from config import * + +# SP2ONG - Increase the value if HBlink link break occurs +NetstringReceiver.MAX_LENGTH = 500000000 + +# 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': {}, 'SETUP': {}} +BRIDGES = {} +BTABLE = {'BRIDGES': {}, 'SETUP': {}} +#BTABLE['BRIDGES'] = {} +BRIDGES_RX = '' +CONFIG_RX = '' +LOGBUF = deque(100*[''], 100) + +RED = 'ff6600' +BLACK = '000000' +GREEN = '90EE90' +GREEN2 = '008000' +BLUE = '0000ff' +ORANGE = 'ff8000' +WHITE = 'ffffff' +WHITE2 = 'f9f9f9f9' +YELLOW = 'fffccd' + +# Define setup setings +CTABLE['SETUP']['LASTHEARD'] = LASTHEARD_INC +BTABLE['SETUP']['BRIDGES'] = BRIDGES_INC + +# create empty systems list +sys_list = [] + +# OPB Filter for lastheard +def get_opbf(): + if len(OPB_FILTER) !=0: + mylist = OPB_FILTER.replace(' ','').split(',') + else: + mylist = [] + return mylist + +# For importing HTML templates +def get_template(_file): + with open(_file, 'r') as html: + return html.read() + +# LONG VERSION - MAKES A FULL DICTIONARY OF INFORMATION BASED ON TYPE OF ALIAS FILE +# BASED ON DOWNLOADS FROM RADIOID.NET +# moved from dmr_utils3 +def mk_full_id_dict(_path, _file, _type): + _dict = {} + try: + with open(_path+_file, 'r', encoding='latin1') as _handle: + records = jload(_handle) + if 'count' in [*records]: + records.pop('count') + records = records[[*records][0]] + _handle.close + if _type == 'peer': + for record in records: + try: + _dict[int(record['id'])] = { + 'CALLSIGN': record['callsign'], + 'CITY': record['city'], + 'STATE': record['state'], + 'COUNTRY': record['country'], + 'FREQ': record['frequency'], + 'CC': record['color_code'], + 'OFFSET': record['offset'], + 'LINKED': record['ts_linked'], + 'TRUSTEE': record['trustee'], + 'NETWORK': record['ipsc_network'] + } + except: + pass + elif _type == 'subscriber': + for record in records: + # Try to craete a string name regardless of existing data + if (('surname' in record.keys()) and ('fname'in record.keys())): + _name = str(record['fname']) + elif 'fname' in record.keys(): + _name = str(record['fname']) + elif 'surname' in record.keys(): + _name = str(record['surname']) + else: + _name = 'NO NAME' + # Make dictionary entry, if any of the information below isn't in the record, it wil be skipped + try: + _dict[int(record['id'])] = { + 'CALLSIGN': record['callsign'], + 'NAME': _name, + 'CITY': record['city'], + 'STATE': record['state'], + 'COUNTRY': record['country'] + } + except: + pass + elif _type == 'tgid': + for record in records: + try: + _dict[int(record['id'])] = { + 'NAME': record['callsign'] + } + except: + pass + return _dict + except IOError: + return _dict + +# THESE ARE THE SAME THING FOR LEGACY PURPOSES +# moved from dmr_urils3 +def get_alias(_id, _dict, *args): + if type(_id) == bytes: + _id = int_id(_id) + if _id in _dict: + if args: + retValue = [] + for _item in args: + try: + retValue.append(_dict[_id][_item]) + except TypeError: + return _dict[_id] + return retValue + else: + return _dict[_id] + return _id + +# 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(" ") + +# 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 cleanTE(): +################################################## +# Cleaning entries in tables - Timeout (5 min) +# + timeout = datetime.datetime.now().timestamp() + + for system in CTABLE['MASTERS']: + for peer in CTABLE['MASTERS'][system]['PEERS']: + for timeS in range(1,3): + if CTABLE['MASTERS'][system]['PEERS'][peer][timeS]['TS']: + ts = CTABLE['MASTERS'][system]['PEERS'][peer][timeS]['TIMEOUT'] + td = ts - timeout if ts > timeout else timeout - ts + td = int(round(abs((td)) / 60)) + if td > 3: + CTABLE['MASTERS'][system]['PEERS'][peer][timeS]['TS'] = False + CTABLE['MASTERS'][system]['PEERS'][peer][timeS]['COLOR'] = BLACK + CTABLE['MASTERS'][system]['PEERS'][peer][timeS]['BGCOLOR'] = WHITE2 + CTABLE['MASTERS'][system]['PEERS'][peer][timeS]['TYPE'] = '' + CTABLE['MASTERS'][system]['PEERS'][peer][timeS]['SUB'] = '' + CTABLE['MASTERS'][system]['PEERS'][peer][timeS]['SRC'] = '' + CTABLE['MASTERS'][system]['PEERS'][peer][timeS]['DEST'] = '' + + for system in CTABLE['PEERS']: + for timeS in range(1,3): + if CTABLE['PEERS'][system][timeS]['TS']: + ts = CTABLE['PEERS'][system][timeS]['TIMEOUT'] + td = ts - timeout if ts > timeout else timeout - ts + td = int(round(abs((td)) / 60)) + if td > 3: + CTABLE['PEERS'][system][timeS]['TS'] = False + CTABLE['PEERS'][system][timeS]['COLOR'] = BLACK + CTABLE['PEERS'][system][timeS]['BGCOLOR'] = WHITE2 + CTABLE['PEERS'][system][timeS]['TYPE'] = '' + CTABLE['PEERS'][system][timeS]['SUB'] = '' + CTABLE['PEERS'][system][timeS]['SRC'] = '' + CTABLE['PEERS'][system][timeS]['DEST'] = '' + + for system in CTABLE['OPENBRIDGES']: + for streamId in list(CTABLE['OPENBRIDGES'][system]['STREAMS']): + ts = CTABLE['OPENBRIDGES'][system]['STREAMS'][streamId][3] + td = ts - timeout if ts > timeout else timeout - ts + td = int(round(abs((td)) / 60)) + if td > 3: + del CTABLE['OPENBRIDGES'][system]['STREAMS'][streamId] + + +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'].strip().isdigit() and _peer_conf['RX_FREQ'].strip().isdigit() and str(type(_peer_conf['TX_FREQ'])).find("bytes") != -1 and str(type(_peer_conf['RX_FREQ'])).find("bytes") != -1: + if _peer_conf['TX_FREQ'][:3] == b'000' or _peer_conf['TX_FREQ'][:1] == b'0' or _peer_conf['RX_FREQ'][:3] == b'000' or _peer_conf['RX_FREQ'][:1] == b'0': + _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' + else: + _ctable_peer['TX_FREQ'] = 'N/A' + _ctable_peer['RX_FREQ'] = 'N/A' + # timeslots are kinda complicated too. 0 = none, 1 or 2 mean that one slot, 3 is both, and anything 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'] == b'0'): + _ctable_peer['SLOTS'] = 'NONE' + elif (_peer_conf['SLOTS'] == b'1' or _peer_conf['SLOTS'] == b'2'): + _ctable_peer['SLOTS'] = _peer_conf['SLOTS'].decode('utf-8') + elif (_peer_conf['SLOTS'] == b'3'): + _ctable_peer['SLOTS'] = 'Duplex' + else: + _ctable_peer['SLOTS'] = 'Simplex' + + # Simple translation items + if str(type(_peer_conf['PACKAGE_ID'])).find("bytes") != -1: + _ctable_peer['PACKAGE_ID'] = _peer_conf['PACKAGE_ID'].decode('utf-8') + else: + _ctable_peer['PACKAGE_ID'] = _peer_conf['PACKAGE_ID'] + + if str(type(_peer_conf['SOFTWARE_ID'])).find("bytes") != -1: + _ctable_peer['SOFTWARE_ID'] = _peer_conf['SOFTWARE_ID'].decode('utf-8') + else: + _ctable_peer['SOFTWARE_ID'] = _peer_conf['SOFTWARE_ID'] + + if str(type(_peer_conf['LOCATION'])).find("bytes") != -1: + _ctable_peer['LOCATION'] = _peer_conf['LOCATION'].decode('utf-8').strip() + else: + _ctable_peer['LOCATION'] = _peer_conf['LOCATION'] + + if str(type(_peer_conf['DESCRIPTION'])).find("bytes") != -1: + _ctable_peer['DESCRIPTION'] = _peer_conf['DESCRIPTION'].decode('utf-8').strip() + else: + _ctable_peer['DESCRIPTION'] = _peer_conf['DESCRIPTION'] + + if str(type(_peer_conf['URL'])).find("bytes") != -1: + _ctable_peer['URL'] = _peer_conf['URL'].decode('utf-8').strip() + else: + _ctable_peer['URL'] = _peer_conf['URL'] + + if str(type(_peer_conf['CALLSIGN'])).find("bytes") != -1: + _ctable_peer['CALLSIGN'] = _peer_conf['CALLSIGN'].decode('utf-8').strip() + else: + _ctable_peer['CALLSIGN'] = _peer_conf['CALLSIGN'] + + if str(type(_peer_conf['COLORCODE'])).find("bytes") != -1: + _ctable_peer['COLORCODE'] = _peer_conf['COLORCODE'].decode('utf-8').strip() + else: + _ctable_peer['COLORCODE'] = _peer_conf['COLORCODE'] + + _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) + + # Process 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'] + + if str(type(_hbp_data['LOCATION'])).find("bytes") != -1: + _stats_table['PEERS'][_hbp]['LOCATION'] = _hbp_data['LOCATION'].decode('utf-8').strip() + else: + _stats_table['PEERS'][_hbp]['LOCATION'] = _hbp_data['LOCATION'] + + if str(type(_hbp_data['DESCRIPTION'])).find("bytes") != -1: + _stats_table['PEERS'][_hbp]['DESCRIPTION'] = _hbp_data['DESCRIPTION'].decode('utf-8').strip() + else: + _stats_table['PEERS'][_hbp]['DESCRIPTION'] = _hbp_data['DESCRIPTION'] + + if str(type(_hbp_data['URL'])).find("bytes") != -1: + _stats_table['PEERS'][_hbp]['URL'] = _hbp_data['DESCRIPTION'].decode('utf-8').strip() + else: + _stats_table['PEERS'][_hbp]['URL'] = _hbp_data['DESCRIPTION'] + + if str(type(_hbp_data['CALLSIGN'])).find("bytes") != -1: + _stats_table['PEERS'][_hbp]['CALLSIGN'] = _hbp_data['CALLSIGN'].decode('utf-8').strip() + else: + _stats_table['PEERS'][_hbp]['CALLSIGN'] = _hbp_data['CALLSIGN'] + + _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']) + _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']['CONNECTED'] = "-- --" + _stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = 0 + _stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = 0 + 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']) + _stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = _hbp_data['STATS']['PINGS_SENT'] + _stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = _hbp_data['STATS']['PINGS_ACKD'] + else: + _stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = "-- --" + _stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = 0 + _stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = 0 + if _hbp_data['SLOTS'] == b'0': + _stats_table['PEERS'][_hbp]['SLOTS'] = 'NONE' + elif _hbp_data['SLOTS'] == b'1' or _hbp_data['SLOTS'] == b'2': + _stats_table['PEERS'][_hbp]['SLOTS'] = _hbp_data['SLOTS'].decode('utf-8') + elif _hbp_data['SLOTS'] == b'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']) + _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: + _stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = "-- --" + _stats_table['PEERS'][_hbp]['STATS']['CONNECTION'] = _config[_hbp]['XLXSTATS']['CONNECTION'] + _stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = 0 + _stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = 0 + else: + if _config[_hbp]['STATS']['CONNECTION'] == "YES": + _stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = since(_config[_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'] + else: + _stats_table['PEERS'][_hbp]['STATS']['CONNECTED'] = "-- --" + _stats_table['PEERS'][_hbp]['STATS']['CONNECTION'] = _config[_hbp]['STATS']['CONNECTION'] + _stats_table['PEERS'][_hbp]['STATS']['PINGS_SENT'] = 0 + _stats_table['PEERS'][_hbp]['STATS']['PINGS_ACKD'] = 0 + + cleanTE() + 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: + main = 'i' + itemplate.render(_table=CTABLE,dbridges=BTABLE['SETUP']['BRIDGES']) + dashboard_server.broadcast(main) + peers = 'p' + ptemplate.render(_table=CTABLE,dbridges=BTABLE['SETUP']['BRIDGES']) + dashboard_server.broadcast(peers) + masters = 'c' + ctemplate.render(_table=CTABLE,dbridges=BTABLE['SETUP']['BRIDGES'],emaster=EMPTY_MASTERS) + dashboard_server.broadcast(masters) + opb = 'o'+ otemplate.render(_table=CTABLE,dbridges=BTABLE['SETUP']['BRIDGES']) + dashboard_server.broadcast(opb) + if BRIDGES and BRIDGES_INC and BTABLE['SETUP']['BRIDGES']: + bridges = 'b' + btemplate.render(_table=BTABLE,dbridges=BTABLE['SETUP']['BRIDGES']) + dashboard_server.broadcast(bridges) + 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]) + timeout = datetime.datetime.now().timestamp() + + if system in CTABLE['MASTERS']: + for peer in CTABLE['MASTERS'][system]['PEERS']: + if sourcePeer == peer: + bgcolor = RED + crxstatus = "RX" + color = WHITE + else: + bgcolor = GREEN + crxstatus = "TX" + color = BLACK + + if action == 'START': + CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['TIMEOUT'] = timeout + 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]['CALL'] = '{}'.format(alias_call(sourceSub, subscriber_ids)) + CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['SRC'] = peer + CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['DEST'] = 'TG {}    {}'.format(destination,alias_tgid(destination,talkgroup_ids)) + CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['TG'] = 'TG {}'.format(destination) + CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['TRX'] = crxstatus + 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]['CALL'] = '' + CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['SRC'] = '' + CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['DEST'] = '' + CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['TG'] = '' + CTABLE['MASTERS'][system]['PEERS'][peer][timeSlot]['TRX'] = '' + + if system in CTABLE['OPENBRIDGES']: + if action == 'START': + CTABLE['OPENBRIDGES'][system]['STREAMS'][streamId] = (trx, alias_call(sourceSub, subscriber_ids),'{}'.format(destination),timeout) + 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 + prxstatus = "RX" + color = WHITE + else: + bgcolor = GREEN + prxstatus = "TX" + color = BLACK + + if action == 'START': + CTABLE['PEERS'][system][timeSlot]['TIMEOUT'] = timeout + CTABLE['PEERS'][system][timeSlot]['TS'] = True + CTABLE['PEERS'][system][timeSlot]['COLOR'] = color + CTABLE['PEERS'][system][timeSlot]['BGCOLOR'] = bgcolor + CTABLE['PEERS'][system][timeSlot]['SUB'] = '{} ({})'.format(alias_short(sourceSub, subscriber_ids), sourceSub) + CTABLE['PEERS'][system][timeSlot]['CALL'] = '{}'.format(alias_call(sourceSub, subscriber_ids)) + CTABLE['PEERS'][system][timeSlot]['SRC'] = sourcePeer + CTABLE['PEERS'][system][timeSlot]['DEST'] = 'TG {}    {}'.format(destination,alias_tgid(destination,talkgroup_ids)) + CTABLE['PEERS'][system][timeSlot]['TG'] = 'TG {}'.format(destination) + CTABLE['PEERS'][system][timeSlot]['TRX'] = prxstatus + 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]['CALL'] = '' + CTABLE['PEERS'][system][timeSlot]['SRC'] = '' + CTABLE['PEERS'][system][timeSlot]['DEST'] = '' + CTABLE['PEERS'][system][timeSlot]['TG'] = '' + CTABLE['PEERS'][system][timeSlot]['TRX'] = '' + + 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, BRIDGES_INC + _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 and BTABLE['SETUP']['BRIDGES']: + 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) + opbfilter = get_opbf() + if p[0] == 'GROUP VOICE' and p[2] != 'TX' and p[5] not in opbfilter: + if p[1] == 'END': + start_sys=0 + for x in sys_list: + if x[0]== p[3] and x[1] == p[4]: + sys_list.pop() + start_sys=1 + break + if p[1] == 'END' and start_sys==1: + log_message = '{} {} {} SYS: {:8.8s} SRC_ID: {:9.9s} TS: {} TGID: {:7.7s} {:17.17s} SUB: {:9.9s}; {:18.18s} Time: {}s '.format(_now[10:19], p[0][6:], p[1], p[3], p[5], 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: + # save QSOs to lastheared.log for which transmission duration is longer than 2 sec, + # use >=0 instead of >2 if you want to record all activities + 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(LOG_PATH+"lastheard.log", "a", encoding="UTF-8", errors="ignore") + lh_logfile.write(log_lh_message + '\n') + lh_logfile.close() + # Lastheard in Dashboard by SP2ONG + my_list=[] + n=0 + f = open(PATH+"templates/lastheard.html", "w", encoding="UTF-8", errors="ignore") + f.write("
\n") + f.write(" .: Lastheard :. \n") + f.write("\n") + f.write("\n") + with open(LOG_PATH+"lastheard.log", "r", encoding="UTF-8", errors="ignore") as textfile: + for row in islice(reversed(list(csv.reader(textfile))),200): + duration=row[1] + dur=str(int(float(duration.strip()))) + if row[10] not in my_list: + if row[11].strip().isdigit() or row[11] == "N0CALL" or row[11] == "NOCALL": + qrz = ""+row[11]+"" + else: + qrz = ""+row[11]+" ("+row[10]+")" + if len(row) < 13: + hline="" + my_list.append(row[10]) + n += 1 + else: + hline="" + my_list.append(row[10]) + n += 1 + f.write(hline+"\n") + # maximum number of lists in lastheard on the main page + if n == 15: + break + f.write("
DateTimeCallsign (DMR-Id)NameTG#TG NameTX (s)System
"+row[0][:10]+""+row[0][11:16]+""+qrz+""+row[8][2:]+""+row[9]+""+dur+""+row[4]+"
"+row[0][:10]+""+row[0][11:16]+""+qrz+""+row[12]+""+row[8][2:]+""+row[9]+""+dur+""+row[4]+"

") + f.close() + # End of Lastheard + # Removing obsolete entries from the sys_list (3 sec) + deleteList=[] + processNo = 0 + timeO = datetime.datetime.now().timestamp() + for item in sys_list: + td = item[2] - timeO if item[2] > timeO else timeO - item[2] + td = int(round(abs((td)) / 60)) + if td > 3: + deleteList.insert(0,processNo) + processNo +=1 + if len(deleteList) >0: + for item in deleteList: + del sys_list[item] + elif p[1] == 'START': + log_message = '{} {} {} SYS: {:8.8s} SRC_ID: {:9.9s} TS: {} TGID: {:7.7s} {:17.17s} SUB: {:9.9s}; {:18.18s}'.format(_now[10:19], p[0][6:], p[1], p[3], p[5], p[7],p[8], alias_tgid(int(p[8]),talkgroup_ids), p[6], alias_short(int(p[6]), subscriber_ids)) + timeST = datetime.datetime.now().timestamp() + sys_list.append([p[3],p[4],timeST]) + elif p[1] == 'END' and start_sys==0: + log_message = '{} {} {} SYS: {:8.8s} SRC_ID: {:9.9s} TS: {} TGID: {:7.7s} {:17.17s} SUB: {:9.9s}; {:18.18s} Time: {}s '.format(_now[10:19], p[0][6:], p[1], p[3], p[5], p[7],p[8],alias_tgid(int(p[8]),talkgroup_ids), p[6], alias_short(int(p[6]), subscriber_ids), int(float(p[9]))) + elif p[1] == 'END WITHOUT MATCHING START': + log_message = '{} {} {} on SYSTEM {:8.8s}: SRC_ID: {:9.9s} TS: {} TGID: {:7.7s} {:17.17s} SUB: {:9.9s}; {:18.18s}'.format(_now[10:19], p[0][6:], p[1], p[3], p[5], 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[10:19]) + + dashboard_server.broadcast('l' + log_message) + LOGBUF.append(log_message) + + else: + logging.debug('{} UNKNOWN LOG MESSAGE'.format(_now[10:19])) + + 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): + CTABLE['MASTERS'].clear() + CTABLE['PEERS'].clear() + CTABLE['OPENBRIDGES'].clear() + BTABLE['BRIDGES'].clear() + 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): + global INFO, MONITOR, OPENBRIDGE + def onConnect(self, request): + logging.info('Client connecting: %s', request.peer) + + def onOpen(self): + #if BTABLE['SETUP']['BRIDGES']: + # ddbridges = True + #else: + # ddbridges = False + logging.info('WebSocket connection open.') + self.factory.register(self) + if BRIDGES and BRIDGES_INC and BTABLE['SETUP']['BRIDGES']: + self.sendMessage(('b' + btemplate.render(_table=BTABLE,dbridges=BTABLE['SETUP']['BRIDGES'])).encode('utf-8')) + self.sendMessage(('c' + ctemplate.render(_table=CTABLE,dbridges=BTABLE['SETUP']['BRIDGES'],emaster=EMPTY_MASTERS)).encode('utf-8')) + self.sendMessage(('p' + ptemplate.render(_table=CTABLE,dbridges=BTABLE['SETUP']['BRIDGES'])).encode('utf-8')) + self.sendMessage(('o' + otemplate.render(_table=CTABLE,dbridges=BTABLE['SETUP']['BRIDGES'])).encode('utf-8')) + self.sendMessage(('i' + itemplate.render(_table=CTABLE,dbridges=BTABLE['SETUP']['BRIDGES'])).encode('utf-8')) + for _message in LOGBUF: + if _message: + _bmessage = ('l' + _message).encode('utf-8') + self.sendMessage(_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) + +###################################################################### +# + +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 \n\n\tHBMonitor v2 SP2ONG 2019-2021\n\n') + # Check lastheard.log + if os.path.isfile(LOG_PATH+"lastheard.log"): + try: + check_call("sed -i -e 's|\\x0||g' {}".format(LOG_PATH+"lastheard.log"), shell=True) + logging.info('Check lastheard.log file') + except CalledProcessError as err: + print(err) + # 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_talkgroup_ids = mk_full_id_dict(PATH, LOCAL_TGID_FILE, 'tgid') + if local_talkgroup_ids: + logging.info('ID ALIAS MAPPER: local_talkgroup_ids added to talkgroup_ids dictionary') + talkgroup_ids.update(local_talkgroup_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']) + ) + + # define tables template + itemplate = env.get_template('main_table.html') + ptemplate = env.get_template('peers_table.html') + ctemplate = env.get_template('masters_table.html') + otemplate = env.get_template('opb_table.html') + btemplate = env.get_template('bridge_table.html') + + # 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()) + + + # HBmonitor does not require the use of SSL as no "sensitive data" is sent to it but if you want to use SSL: + # create websocket server to push content to clients via SSL https:// + # the web server apache2 should be configured with a signed certificate for example Letsencrypt + # we need install pyOpenSSL required by twisted: pip3 install pyOpenSSL + # and add load ssl module in line number 43: from twisted.internet import reactor, task, ssl + # + # put certificate https://letsencrypt.org/ used in apache server + #certificate = ssl.DefaultOpenSSLContextFactory('/etc/letsencrypt/live/hbmon.dmrserver.org/privkey.pem', '/etc/letsencrypt/live/hbmon.dmrserver.org/cert.pem') + #dashboard_server = dashboardFactory('wss://*:9000') + #dashboard_server.protocol = dashboard + #reactor.listenSSL(9000, dashboard_server,certificate) + + # Create websocket server to push content to clients via http:// non SSL + dashboard_server = dashboardFactory('ws://*:9000') + dashboard_server.protocol = dashboard + reactor.listenTCP(9000, dashboard_server) + + + reactor.run() diff --git a/HBMonv2/requirements.txt b/HBMonv2/requirements.txt new file mode 100644 index 0000000..4cfa963 --- /dev/null +++ b/HBMonv2/requirements.txt @@ -0,0 +1,6 @@ +Twisted +dmr_utils3 +bitstring +autobahn +jinja2==2.11.3 + diff --git a/HBMonv2/sysinfo/Readme.txt b/HBMonv2/sysinfo/Readme.txt new file mode 100644 index 0000000..dd9a8fd --- /dev/null +++ b/HBMonv2/sysinfo/Readme.txt @@ -0,0 +1,146 @@ +Monitoring system +================== + +You can use and install ezSM tool to monitor your server instead described below method. +The ezSM you can download from: https://www.ezservermonitor.com/ + + cd /var/www/html + git clone https://github.com/shevabam/ezservermonitor-web.git + mv ezservermonitor-web esm + cd esm/conf/ + + Edit file esm.config.json and read documentation about configuration: https://www.ezservermonitor.com/esm-web/documentation + Edit file /var/www/html/buttons.html and change link from + + + to: + + + or you can add to sysinfo.php below line following html code: + + + +You can put in esm.config.json monitor services like HBMonitor, HBlink like: + + "services": { + "show_port": false, + "list": [ + { + "name": "Web Server", + "host": "localhost", + "port": 80, + "protocol": "tcp" + }, + { + "name": "HBMonitor", + "host": "localhost", + "port": 9000, + "protocol": "tcp" + }, + { + "name": "HBLink", + "host": "localhost", + "port": 4321, + "protocol": "tcp" + } + + + +==================================================== +Alternative SYSInfo based on rrdtools and scripts +==================================================== +Below is a description of how to monitor the system using rrdtools and scripts : + +Install package: + + sudo apt-get install rrdtool -y + +Change scripts to execute: + + chmod +x /opt/HBMonv2/sysinfo/cpu.sh + chmod +x /opt/HBMonv2/sysinfo/graph.sh + chmod +x /opt/HBMonv2/sysinfo/rrd-db.sh + +Run script create database + + cd /opt/HBMonv2/sysinfo + ./rrd-db.sh + + +Edit file + + /opt/HBMonv2/sysinfo/cpu.sh + +Setup in WEB_PATH path to your web server html directory +for example /var/www/html or /var/www/html/hbmon +where is located your html files of HBMon + +Edit file + + /opt/HBMonv2/sysinfo/graph.sh + +Setup in WEB_PATH path to your web server html directory +for example /var/www/html or /var/www/html/hbmon +where is located your html files of HBMon + + +Setup temperature depend of your computer +On raspberry pi or PC you can use sensors package to get temperature CPU + +If you don't want to show temperature on the Pi, comment out the line that gets the temp + +Copy file sysinfo-cron to /etc/cron.d/ and restart crontab + /etc/init.d/cron restart + + +Optional display network traffic +=============================== + +Instal package mrtg and snmp + + sudo apt-get install mrtg snmp snmpd -y + +Edit file + + /etc/snmp/snmpd.conf + +and set as below + + + rocommunity public localhost + #rocommunity public default -V systemonly + #rocommunity6 public default -V systemonly + + +Restart snmpd + + systemctl restart snmpd + + +Create config for mrtg: + + cfgmaker -zero-speed=10000 public@localhost > /etc/mrtg.cfg + +Please edit /etc/mrtg.cfg and change diretory to store image change WorkDir with +path to your webserver html directory where is html files for HBMon: + + WorkDir:/var/www/html/hbmon/img/mrtg + + or + + WorkDir:/var/www/html/img/mrtg + +Put below lines in section your network card +and replace localhost_2 to your name network card as result cfgmaker generate in mrtg.cfg + + XSize[localhost_2]: 600 + Options[localhost_2]: growright, bits + Unscaled[localhost_2]: d + +Tune MaxBytes value for exmample 50000 to set vertical scale graph + +Please edit template file where is which graph you are want display /var/www/html/sysinfo.php +and check / verify name of img from mrtg: + diff --git a/HBMonv2/sysinfo/cpu.sh b/HBMonv2/sysinfo/cpu.sh new file mode 100755 index 0000000..98002bd --- /dev/null +++ b/HBMonv2/sysinfo/cpu.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +# Setup web server directory where is html files HBMon +WEB_PATH='/var/www/html/' + +# CPU load +load=`/bin/sed "s/\([0-9]\\.[0-9]\\{2\\}\)\ \([0-9]\\.[0-9]\\{2\\}\)\ \([0-9]\\.[0-9]\\{2\\}\).*/\1:\2:\3/" < /proc/loadavg`:`/usr/bin/head -n 1 /proc/stat | /bin/sed "s/^cpu\ \+\([0-9]*\)\ \([0-9]*\)\ \([0-9]*\).*/\1:\2:\3/"` + +# Get time +NOW=`date -u +%s` + +# Update db ===================================================== + +/usr/bin/rrdtool update /opt/HBMonv2/sysinfo/load.rrd $NOW:$load + +# Generate images ================================================================ + + +# CPU loads +/usr/bin/rrdtool graph $WEB_PATH/img/cpu.png \ +-Y -r -u 100 -l 0 -L 5 -v "CPU usage" -w 600 -h 70 -t "CPU status 24H - `/bin/date`" \ +-c ARROW\#000000 -x MINUTE:30:MINUTE:30:HOUR:1:0:%H \ +DEF:user=/opt/HBMonv2/sysinfo/load.rrd:cpuuser:AVERAGE \ +DEF:nice=/opt/HBMonv2/sysinfo/load.rrd:cpunice:AVERAGE \ +DEF:sys=/opt/HBMonv2/sysinfo/load.rrd:cpusystem:AVERAGE \ +CDEF:idle=100,user,nice,sys,+,+,- \ +COMMENT:" " \ +AREA:user\#FF0000:"CPU user" \ +STACK:nice\#000099:"CPU nice" \ +STACK:sys\#FFFF00:"CPU system" \ +STACK:idle\#00FF00:"CPU idle" \ +COMMENT:" \j" >/dev/null + diff --git a/HBMonv2/sysinfo/graph.sh b/HBMonv2/sysinfo/graph.sh new file mode 100755 index 0000000..3d5ea5c --- /dev/null +++ b/HBMonv2/sysinfo/graph.sh @@ -0,0 +1,165 @@ +#!/bin/bash + +# Setup path web server directory where is html files of HBMon +WEB_PATH='/var/www/html/' + +# Get values + +# Temperature CPU (not working for VPS) + +# Setup temperature for CPU ============ + + +#For Raspberry PI, comment next 4 lines if you don't want temperature: + +FILE=/sys/class/thermal/thermal_zone0/temp +if [[ -f "$FILE" ]]; then +tempC=`cat /sys/class/thermal/thermal_zone0/temp |awk '{printf("%.1f",$1/1000)}'` +fi + +# For computers not like Raspberry PI install package +# at install lm-sensors +# and run: sensors-detect +# after this check result run command: sensors to see temperature CPU + +if [ -z "$tempC" ] ; then +tempC=`sensors | grep -i "Core 0" | grep "$1" | sed -re "s/.*:[^+]*?[+]([.0-9]+)[ °]C.*/\1/g"` +fi + +#===================================== + +# Usage of hdd / +hdd=`df -h | awk '$NF=="/"{printf "%s",$5}'|sed 's/.$//'|awk '{printf("%.1f",$1)}'` + +# Memory usage +mem=`free -m | awk 'NR==2{printf "%.1f", $3*100/$2 }'` + +# CPU load +load=`/bin/sed "s/\([0-9]\\.[0-9]\\{2\\}\)\ \([0-9]\\.[0-9]\\{2\\}\)\ \([0-9]\\.[0-9]\\{2\\}\).*/\1:\2:\3/" < /proc/loadavg`:`/usr/bin/head -n 1 /proc/stat | /bin/sed "s/^cpu\ \+\([0-9]*\)\ \([0-9]*\)\ \([0-9]*\).*/\1:\2:\3/"` + +# Get time +NOW=`date -u +%s` + +# Update db ===================================================== + +if [ -n "$tempC" ] ; then +/usr/bin/rrdtool update /opt/HBMonv2/sysinfo/tempC.rrd $NOW:$tempC +fi + +/usr/bin/rrdtool update /opt/HBMonv2/sysinfo/mem.rrd $NOW:$mem +/usr/bin/rrdtool update /opt/HBMonv2/sysinfo/hdd.rrd $NOW:$hdd +/usr/bin/rrdtool update /opt/HBMonv2/sysinfo/load.rrd $NOW:$load + +# Generate images ================================================================ + + +if [ -n "$tempC" ] ; then +# Temperature CPU +/usr/bin/rrdtool graph $WEB_PATH/img/tempC.png -t "Temperature CPU 24H - `/bin/date`" \ +--rigid --alt-y-grid --alt-autoscale --units-exponent 0 \ +-w 600 -h 70 --upper-limit 100 --vertical-label 'Temperature [C]' --slope-mode --start -86400 \ +DEF:ave=/opt/HBMonv2/sysinfo/tempC.rrd:temp:AVERAGE \ +CDEF:C=ave,100,GE,ave,0,IF AREA:C#7F0000: \ +CDEF:D=ave,95,GE,ave,100,LT,ave,100,IF,0,IF AREA:D#9E0000: \ +CDEF:E=ave,90,GE,ave,95,LT,ave,95,IF,0,IF AREA:E#BD0000: \ +CDEF:F=ave,85,GE,ave,90,LT,ave,90,IF,0,IF AREA:F#DD0000: \ +CDEF:G=ave,80,GE,ave,85,LT,ave,85,IF,0,IF AREA:G#FC0000: \ +CDEF:H=ave,75,GE,ave,80,LT,ave,80,IF,0,IF AREA:H#FF1D00: \ +CDEF:I=ave,70,GE,ave,75,LT,ave,75,IF,0,IF AREA:I#FC3D00: \ +CDEF:J=ave,65,GE,ave,70,LT,ave,70,IF,0,IF AREA:J#FF5C00: \ +CDEF:K=ave,60,GE,ave,65,LT,ave,65,IF,0,IF AREA:K#FF7C00: \ +CDEF:L=ave,55,GE,ave,60,LT,ave,60,IF,0,IF AREA:L#FFBA00: \ +CDEF:M=ave,50,GE,ave,55,LT,ave,55,IF,0,IF AREA:M#FFD900: \ +CDEF:N=ave,45,GE,ave,50,LT,ave,50,IF,0,IF AREA:N#FFF900: \ +CDEF:O=ave,40,GE,ave,45,LT,ave,45,IF,0,IF AREA:O#E5FF1A: \ +CDEF:P=ave,35,GE,ave,40,LT,ave,40,IF,0,IF AREA:P#C6FF39: \ +CDEF:Q=ave,30,GE,ave,35,LT,ave,35,IF,0,IF AREA:Q#A6FF58: \ +CDEF:R=ave,25,GE,ave,30,LT,ave,30,IF,0,IF AREA:R#87FF78: \ +CDEF:S=ave,20,GE,ave,25,LT,ave,25,IF,0,IF AREA:S#69FE96: \ +CDEF:T=ave,15,GE,ave,20,LT,ave,20,IF,0,IF AREA:T#49FEB6: \ +CDEF:U=ave,10,GE,ave,15,LT,ave,15,IF,0,IF AREA:U#2AFED5: \ +CDEF:VV=ave,5,GE,ave,10,LT,ave,10,IF,0,IF AREA:VV#0BFFF4: \ +CDEF:WW=ave,0,GE,ave,5,LT,ave,5,IF,0,IF AREA:WW#0BFFF4: \ +CDEF:A=ave \ +VDEF:V=ave,AVERAGE \ +LINE1:ave \ +LINE1:A#000000:Temperature \ +DEF:tmax=/opt/HBMonv2/sysinfo/tempC.rrd:temp:MAX \ +DEF:tmin=/opt/HBMonv2/sysinfo/tempC.rrd:temp:MIN \ +'GPRINT:ave:LAST:Last\: %2.1lf C' \ +'GPRINT:tmin:MIN:Minimum\: %2.1lf C' \ +'GPRINT:tmax:MAX:Maximum\: %2.1lf C\j' >/dev/null +fi + +# Memory usage +/usr/bin/rrdtool graph $WEB_PATH/img/mem.png -t "Memory usage 24H - `grep MemTotal /proc/meminfo | awk '{printf "%.0f MB", $2/1024}'` - `/bin/date`" \ +--rigid --alt-y-grid --alt-autoscale --units-exponent 0 \ +-w 600 -h 70 --upper-limit 100 --vertical-label 'Memory usage [%]' --slope-mode --start -86400 \ +DEF:ave=/opt/HBMonv2/sysinfo/mem.rrd:mem:AVERAGE \ +CDEF:C=ave,100,GE,ave,0,IF AREA:C#7F0000: \ +CDEF:D=ave,95,GE,ave,100,LT,ave,100,IF,0,IF AREA:D#9E0000: \ +CDEF:E=ave,90,GE,ave,95,LT,ave,95,IF,0,IF AREA:E#BD0000: \ +CDEF:F=ave,85,GE,ave,90,LT,ave,90,IF,0,IF AREA:F#DD0000: \ +CDEF:G=ave,80,GE,ave,85,LT,ave,85,IF,0,IF AREA:G#FC0000: \ +CDEF:H=ave,75,GE,ave,80,LT,ave,80,IF,0,IF AREA:H#FF1D00: \ +CDEF:I=ave,70,GE,ave,75,LT,ave,75,IF,0,IF AREA:I#FC3D00: \ +CDEF:J=ave,65,GE,ave,70,LT,ave,70,IF,0,IF AREA:J#FF5C00: \ +CDEF:K=ave,60,GE,ave,65,LT,ave,65,IF,0,IF AREA:K#FF7C00: \ +CDEF:L=ave,55,GE,ave,60,LT,ave,60,IF,0,IF AREA:L#FFBA00: \ +CDEF:M=ave,50,GE,ave,55,LT,ave,55,IF,0,IF AREA:M#FFD900: \ +CDEF:N=ave,45,GE,ave,50,LT,ave,50,IF,0,IF AREA:N#FFF900: \ +CDEF:O=ave,40,GE,ave,45,LT,ave,45,IF,0,IF AREA:O#E5FF1A: \ +CDEF:P=ave,35,GE,ave,40,LT,ave,40,IF,0,IF AREA:P#C6FF39: \ +CDEF:Q=ave,30,GE,ave,35,LT,ave,35,IF,0,IF AREA:Q#A6FF58: \ +CDEF:R=ave,25,GE,ave,30,LT,ave,30,IF,0,IF AREA:R#87FF78: \ +CDEF:S=ave,20,GE,ave,25,LT,ave,25,IF,0,IF AREA:S#69FE96: \ +CDEF:T=ave,15,GE,ave,20,LT,ave,20,IF,0,IF AREA:T#49FEB6: \ +CDEF:U=ave,10,GE,ave,15,LT,ave,15,IF,0,IF AREA:U#2AFED5: \ +CDEF:VV=ave,5,GE,ave,10,LT,ave,10,IF,0,IF AREA:VV#0BFFF4: \ +CDEF:WW=ave,0,GE,ave,5,LT,ave,5,IF,0,IF AREA:WW#0BFFF4: \ +CDEF:A=ave \ +VDEF:V=ave,AVERAGE \ +LINE1:ave \ +LINE1:A#000000:Memory_usage_% \ +DEF:tmax=/opt/HBMonv2/sysinfo/mem.rrd:mem:MAX \ +DEF:tmin=/opt/HBMonv2/sysinfo/mem.rrd:mem:MIN \ +'GPRINT:ave:LAST:Last\: %2.1lf ' \ +'GPRINT:tmin:MIN:Minimum\: %2.1lf ' \ +'GPRINT:tmax:MAX:Maximum\: %2.1lf \j' >/dev/null + +# Disk usage +/usr/bin/rrdtool graph $WEB_PATH/img/hdd.png -t "Disk usage 24H - Size: `df -h / |awk 'NR==2 { print $2 }'` - `/bin/date`" \ +--rigid --alt-y-grid --alt-autoscale --units-exponent 0 \ +-w 600 -h 70 --upper-limit 100 --vertical-label 'Disk usage [%]' --slope-mode --start -86400 \ +DEF:ave=/opt/HBMonv2/sysinfo/hdd.rrd:hdd:AVERAGE \ +CDEF:C=ave,100,GE,ave,0,IF AREA:C#7F0000: \ +CDEF:D=ave,95,GE,ave,100,LT,ave,100,IF,0,IF AREA:D#9E0000: \ +CDEF:E=ave,90,GE,ave,95,LT,ave,95,IF,0,IF AREA:E#BD0000: \ +CDEF:F=ave,85,GE,ave,90,LT,ave,90,IF,0,IF AREA:F#DD0000: \ +CDEF:G=ave,80,GE,ave,85,LT,ave,85,IF,0,IF AREA:G#FC0000: \ +CDEF:H=ave,75,GE,ave,80,LT,ave,80,IF,0,IF AREA:H#FF1D00: \ +CDEF:I=ave,70,GE,ave,75,LT,ave,75,IF,0,IF AREA:I#FC3D00: \ +CDEF:J=ave,65,GE,ave,70,LT,ave,70,IF,0,IF AREA:J#FF5C00: \ +CDEF:K=ave,60,GE,ave,65,LT,ave,65,IF,0,IF AREA:K#FF7C00: \ +CDEF:L=ave,55,GE,ave,60,LT,ave,60,IF,0,IF AREA:L#FFBA00: \ +CDEF:M=ave,50,GE,ave,55,LT,ave,55,IF,0,IF AREA:M#FFD900: \ +CDEF:N=ave,45,GE,ave,50,LT,ave,50,IF,0,IF AREA:N#FFF900: \ +CDEF:O=ave,40,GE,ave,45,LT,ave,45,IF,0,IF AREA:O#E5FF1A: \ +CDEF:P=ave,35,GE,ave,40,LT,ave,40,IF,0,IF AREA:P#C6FF39: \ +CDEF:Q=ave,30,GE,ave,35,LT,ave,35,IF,0,IF AREA:Q#A6FF58: \ +CDEF:R=ave,25,GE,ave,30,LT,ave,30,IF,0,IF AREA:R#87FF78: \ +CDEF:S=ave,20,GE,ave,25,LT,ave,25,IF,0,IF AREA:S#69FE96: \ +CDEF:T=ave,15,GE,ave,20,LT,ave,20,IF,0,IF AREA:T#49FEB6: \ +CDEF:U=ave,10,GE,ave,15,LT,ave,15,IF,0,IF AREA:U#2AFED5: \ +CDEF:VV=ave,5,GE,ave,10,LT,ave,10,IF,0,IF AREA:VV#0BFFF4: \ +CDEF:WW=ave,0,GE,ave,5,LT,ave,5,IF,0,IF AREA:WW#0BFFF4: \ +CDEF:A=ave \ +VDEF:V=ave,AVERAGE \ +LINE1:ave \ +LINE1:A#000000:Disk_usage_% \ +DEF:tmax=/opt/HBMonv2/sysinfo/hdd.rrd:hdd:MAX \ +DEF:tmin=/opt/HBMonv2/sysinfo/hdd.rrd:hdd:MIN \ +'GPRINT:ave:LAST:Last\: %2.1lf ' \ +'GPRINT:tmin:MIN:Minimum\: %2.1lf ' \ +'GPRINT:tmax:MAX:Maximum\: %2.1lf \j' >/dev/null + diff --git a/HBMonv2/sysinfo/rrd-db.sh b/HBMonv2/sysinfo/rrd-db.sh new file mode 100755 index 0000000..7a8ae28 --- /dev/null +++ b/HBMonv2/sysinfo/rrd-db.sh @@ -0,0 +1,52 @@ +#!/bin/sh +# Db for temperature CPU +rrdtool create /opt/HBMonv2/sysinfo/tempC.rrd \ +--step 300 \ +DS:temp:GAUGE:600:0:100 \ +RRA:AVERAGE:0.5:1:288 \ +RRA:AVERAGE:0.5:3:672 \ +RRA:MIN:0.5:1:288 \ +RRA:MIN:0.5:3:672 \ +RRA:MAX:0.5:1:288 \ +RRA:MAX:0.5:3:672 \ +RRA:LAST:0.5:1:288 \ +RRA:LAST:0.5:3:672 + +# Db for memory usage +rrdtool create /opt/HBMonv2/sysinfo/mem.rrd \ +--step 300 \ +DS:mem:GAUGE:600:0:100 \ +RRA:AVERAGE:0.5:1:288 \ +RRA:AVERAGE:0.5:3:672 \ +RRA:MIN:0.5:1:288 \ +RRA:MIN:0.5:3:672 \ +RRA:MAX:0.5:1:288 \ +RRA:MAX:0.5:3:672 \ +RRA:LAST:0.5:1:288 \ +RRA:LAST:0.5:3:672 + +# Db for disk usage +rrdtool create /opt/HBMonv2/sysinfo/hdd.rrd \ +--step 300 \ +DS:hdd:GAUGE:600:0:100 \ +RRA:AVERAGE:0.5:1:288 \ +RRA:AVERAGE:0.5:3:672 \ +RRA:MIN:0.5:1:288 \ +RRA:MIN:0.5:3:672 \ +RRA:MAX:0.5:1:288 \ +RRA:MAX:0.5:3:672 \ +RRA:LAST:0.5:1:288 \ +RRA:LAST:0.5:3:672 + +# Db for CPU load +rrdtool create /opt/HBMonv2/sysinfo/load.rrd -s 60 \ +DS:load1:GAUGE:180:0:U \ +DS:load5:GAUGE:180:0:U \ +DS:load15:GAUGE:180:0:U \ +DS:cpuuser:COUNTER:180:0:100 \ +DS:cpunice:COUNTER:180:0:100 \ +DS:cpusystem:COUNTER:180:0:100 \ +RRA:AVERAGE:0.5:1:1440 \ +RRA:AVERAGE:0.5:1440:1 \ +RRA:MIN:0.5:1440:1 \ +RRA:MAX:0.5:1440:1 diff --git a/HBMonv2/sysinfo/sysinfo-cron b/HBMonv2/sysinfo/sysinfo-cron new file mode 100644 index 0000000..87299d1 --- /dev/null +++ b/HBMonv2/sysinfo/sysinfo-cron @@ -0,0 +1,3 @@ +CRONDARGS=-s -m off +*/5 * * * * root /opt/HBMonv2/sysinfo/graph.sh +*/2 * * * * root /opt/HBMonv2/sysinfo/cpu.sh diff --git a/HBMonv2/templates/bridge_table.html b/HBMonv2/templates/bridge_table.html new file mode 100644 index 0000000..3ddbfb0 --- /dev/null +++ b/HBMonv2/templates/bridge_table.html @@ -0,0 +1,55 @@ +
+ .: Bridge status :.  + {% if _table['BRIDGES']|length == 0 and _table['SETUP']['BRIDGES'] != False %} + + + + +
Waiting for data from the Server ...
+{% elif _table['SETUP']['BRIDGES'] == False %} + + + + +
BRIDGES display is disabled by setup
+{% else %} +{% for _bridge, _bridge_data in _table['BRIDGES'].items() %} + + + + + + + + + + + +

{{ _bridge }}

+ + + + + + + + + + + {% for system, _system_data in _table['BRIDGES'][_bridge].items() %} + + + + + + + + + + + {% endfor %} +
SystemSlotTG#StatusTimeoutTimeout ActionConnect TG#Disconnect TG#
{{ system }}{{ _table['BRIDGES'][_bridge][system]['TS'] }}{{ _table['BRIDGES'][_bridge][system]['TGID'] }}{{ _table['BRIDGES'][_bridge][system]['ACTIVE'] }}{{ _table['BRIDGES'][_bridge][system]['EXP_TIME'] }}{{ _table['BRIDGES'][_bridge][system]['TO_ACTION'] }}{{ _table['BRIDGES'][_bridge][system]['TRIG_ON'] }}{{ _table['BRIDGES'][_bridge][system]['TRIG_OFF'] }}
+{% endfor %} + {% endif %} +
+ diff --git a/HBMonv2/templates/lastheard.html b/HBMonv2/templates/lastheard.html new file mode 100644 index 0000000..9008f26 --- /dev/null +++ b/HBMonv2/templates/lastheard.html @@ -0,0 +1,5 @@ +
+ .: Lastheard :.  + + +
DateTimeCallsign (DMR-Id)NameTG#TG NameTX (s)System

diff --git a/HBMonv2/templates/main_table.html b/HBMonv2/templates/main_table.html new file mode 100644 index 0000000..58702ba --- /dev/null +++ b/HBMonv2/templates/main_table.html @@ -0,0 +1,118 @@ + +
+ .: Server Activity :.  +{% if _table['MASTERS']|length >0 %} + + + + + + + +{% endif %} +
Active QSO's
+
+ {% for _master in _table['MASTERS'] %} + {% for _client, _cdata in _table['MASTERS'][_master]['PEERS'].items() %} + {% if _cdata[1]['TS'] == True or _cdata[2]['TS'] == True %} + {% if _cdata[1]['TRX'] == "RX" %} + [M{{ _cdata[1]['CALL']|safe }} +  >> {{ _cdata[1]['TG']|safe }}]  + {% endif %} + {% if _cdata[2]['TRX'] == "RX" %} + [M{{ _cdata[2]['CALL']|safe}} +  >> {{_cdata[2]['TG']|safe}}]  + {% endif %} + {% endif %} + {% endfor %} + {% endfor %} +{% else %} + + + + +
Waiting for data from the Server ...
+ {% endif %} + {% for _peer, _pdata in _table['PEERS'].items() %} + {% if _pdata[1]['TS'] == True or _pdata[2]['TS'] == True %} + {% if _pdata[1]['TRX'] == "RX" %} + [M{{ _pdata[1]['CALL']|safe }} +  >> {{ _pdata[1]['TG']|safe }}]  + {% endif %} + {% if _pdata[2]['TRX'] == "RX" %} + [M{{ _pdata[2]['CALL']|safe }} +  >> {{ _pdata[2]['TG']|safe }}]  + {% endif %} + {% endif %} + {% endfor %} +{% if _table['OPENBRIDGES']|length >0 %} + {% for _openbridge in _table['OPENBRIDGES'] %} + {% set rx = namespace(value=0) %} + {% if _table['OPENBRIDGES'][_openbridge]['STREAMS']|length >0 %} + {% for entry in _table['OPENBRIDGES'][_openbridge]['STREAMS'] if _table['OPENBRIDGES'][_openbridge]['STREAMS'][entry][0]=='RX' %} + {% set rx.value=1 %} + {% endfor %} + {% if rx.value == 1 %} + {% for entry in _table['OPENBRIDGES'][_openbridge]['STREAMS'] if _table['OPENBRIDGES'][_openbridge]['STREAMS'][entry][0] == 'RX' %} [O{{ _table['OPENBRIDGES'][_openbridge]['STREAMS'][entry][1]}} >> TG {{ _table['OPENBRIDGES'][_openbridge]['STREAMS'][entry][2]}}] {% endfor %} + {% endif %} + {% endif %} + {% endfor %} +
+
+{% if _table['SETUP']['LASTHEARD'] == True %} + {% include 'lastheard.html' ignore missing %} +{% endif %} +
+ .: Connected to Server :.  + +{% if _table['MASTERS']|length >0 %} +
+
+
+
  USERS:
+
+ {% for _master in _table['MASTERS'] %} + {% if _table['MASTERS'][_master]['PEERS']|length >0 %} + {% for _client, _cdata in _table['MASTERS'][_master]['PEERS'].items() %} + + {{_cdata['CALLSIGN']}} + + +    DMR ID: {{ _client }}
+ {% if _cdata['RX_FREQ'] == 'N/A' and _cdata['TX_FREQ'] == 'N/A' %} +    Type: IP Network
+ {% else %} +    Type: Radio ({{ _cdata['SLOTS'] }})
+ {% endif %} +    Hardware: {{_cdata['PACKAGE_ID'] }} +
   Soft_Ver: {{_cdata['SOFTWARE_ID'] }} +
   Info: {{_cdata['LOCATION']}} +
   Master: {{_master}} +
  + {% endfor %} + {% endif %} + {% endfor %} +
+{% endif %} +{% if _table['PEERS']|length >0 %} +
+
  PEERS:
+
+ {% for _peer, _pdata in _table['PEERS'].items() %} +   {{_peer}}   + {% if _table['PEERS'][_peer]['STATS']['CONNECTION'] == 'YES' %} + +
Connected
+
+ {% else %} + +
Disconnected
+
+ {% endif %} +
  + {% endfor %} +
+{% endif %} +
+
+
diff --git a/HBMonv2/templates/masters_table.html b/HBMonv2/templates/masters_table.html new file mode 100644 index 0000000..978af6f --- /dev/null +++ b/HBMonv2/templates/masters_table.html @@ -0,0 +1,55 @@ +
+ .: Masters status :.  +{% if _table['MASTERS']|length >0 %} + + + + + + + + + + + {% for _master in _table['MASTERS'] %} + {% if ((_table['MASTERS'][_master]['PEERS']|length==0 or _table['MASTERS'][_master]['PEERS']|length>0) and emaster==True) or (_table['MASTERS'][_master]['PEERS']|length>0 and emaster==False) %} + + + + + {% for _client, _cdata in _table['MASTERS'][_master]['PEERS'].items() %} + + + + + + + + + + + + + + {% endfor %} + {% endif %} +{% endfor %} +
HB Protocol
Master Systems
Callsign (DMR Id)
Info
Time ConnectedSlotSourceDestination
{{_master}}
{{_table['MASTERS'][_master]['REPEAT']}}
{{ _cdata['CALLSIGN']}} + (Id: {{ _client }}) + + {% if _cdata['RX_FREQ'] == 'N/A' and _cdata['TX_FREQ'] == 'N/A' %} +    Type: IP Network
+ {% else %} +    Type: Radio ({{ _cdata['SLOTS'] }})
+ {% endif %} +    Soft_Ver: {{_cdata['SOFTWARE_ID'] }} +
   Hardware: {{_cdata['PACKAGE_ID'] }}
+
{{_cdata['LOCATION']}}
{{ _cdata['CONNECTED'] }}TS1{{ _cdata[1]['SUB']|safe }}{{ _cdata[1]['DEST']|safe }}
TS2{{ _cdata[2]['SUB']|safe }}{{ _cdata[2]['DEST']|safe }}
+{% else %} + + + + +
Waiting for data from the Server ...
+{% endif %} +
diff --git a/HBMonv2/templates/opb_table.html b/HBMonv2/templates/opb_table.html new file mode 100644 index 0000000..8f69238 --- /dev/null +++ b/HBMonv2/templates/opb_table.html @@ -0,0 +1,25 @@ +
+ .: OpenBridge status :.  + {% if _table['OPENBRIDGES']|length >0 %} + + + + + + + {% for _openbridge in _table['OPENBRIDGES'] %} + + + + + + {% endfor %} +
OpenBridge
Systems
Network IDActive QSOs
{{ _openbridge}}
Net ID: {{ _table['OPENBRIDGES'][_openbridge]['NETWORK_ID'] }}
{% for entry in _table['OPENBRIDGES'][_openbridge]['STREAMS'] %}[{{ _table['OPENBRIDGES'][_openbridge]['STREAMS'][entry][0] }}: {{ _table['OPENBRIDGES'][_openbridge]['STREAMS'][entry][1] }} >> TG {{ _table['OPENBRIDGES'][_openbridge]['STREAMS'][entry][2] }}] {% endfor %}
+ {% else %} + + + + +
Waiting for data from Server ... or not defined on Server
+ {% endif %} +
diff --git a/HBMonv2/templates/peers_table.html b/HBMonv2/templates/peers_table.html new file mode 100644 index 0000000..4760e9e --- /dev/null +++ b/HBMonv2/templates/peers_table.html @@ -0,0 +1,37 @@ +
+ .: Peers status :.  +{% if _table['PEERS']|length >0 %} + + + + + + + + + + {% for _peer, _pdata in _table['PEERS'].items() %} + + + + + + + + + + + + + + + {% endfor %} +
HB Protocol
Peer Systems
Callsign (DMR Id)
Info
Connected
TX/RX/Lost
SlotSourceDestination
{{ _peer}}
Mode: {{ _table['PEERS'][_peer]['MODE'] }}
{{_table['PEERS'][_peer]['CALLSIGN']}} (Id: {{ _table['PEERS'][_peer]['RADIO_ID'] }})   Linked Time Slot: {{ _table['PEERS'][_peer]['SLOTS'] }}

{{_table['PEERS'][_peer]['LOCATION']}}
{{ _table['PEERS'][_peer]['STATS']['CONNECTED'] }}
{{ _table['PEERS'][_peer]['STATS']['PINGS_SENT'] }} / {{ _table['PEERS'][_peer]['STATS']['PINGS_ACKD'] }} / {{ _table['PEERS'][_peer]['STATS']['PINGS_SENT'] - _table['PEERS'][_peer]['STATS']['PINGS_ACKD'] }}
TS1{{ _pdata[1]['SUB']|safe }}{{ _pdata[1]['DEST']|safe }}
TS2{{ _pdata[2]['SUB']|safe }}{{ _pdata[2]['DEST']|safe }}
+ {% else %} + + + + +
Waiting for data from the Server ... or not defined on Server
+ {% endif %} +
diff --git a/HBMonv2/utils/Readme.md b/HBMonv2/utils/Readme.md new file mode 100644 index 0000000..44f78ca --- /dev/null +++ b/HBMonv2/utils/Readme.md @@ -0,0 +1,21 @@ +Requirements a webserver with activated PHP (apache, nginx or whatever) – PHP 7.x is ok + + +Extension of hbmonitor – we log if a call is ended (I think it’s better as start) Please check permissions for wr +iting the logfile in target folder ! + + +Call this script with crontab for everyday use. + +Put this file in /etc/cron.daily/ and add attribute: + +chmod +x /etc/cron.daily/lastheard + + + +Call the website with http://YOUR_HOST/log.php it runs with a refresh/reload time of 30sec, change the script for +other timeset. + + + +Thank you, Heiko DL1BZ, who shared the lastheard code. diff --git a/HBMonv2/utils/hbmon.service b/HBMonv2/utils/hbmon.service new file mode 100644 index 0000000..15faa19 --- /dev/null +++ b/HBMonv2/utils/hbmon.service @@ -0,0 +1,17 @@ +[Unit] +Description=HBMonitor +# To make the network-online.target available +# systemctl enable systemd-networkd-wait-online.service + +After=network-online.target syslog.target +Wants=network-online.target + +[Service] +StandardOutput=null +WorkingDirectory=/opt/HBMonv2 +RestartSec=3 +ExecStart=/usr/bin/python3 /opt/HBMonv2/monitor.py +Restart=on-abort + +[Install] +WantedBy=multi-user.target diff --git a/HBMonv2/utils/lastheard b/HBMonv2/utils/lastheard new file mode 100755 index 0000000..02cbf2e --- /dev/null +++ b/HBMonv2/utils/lastheard @@ -0,0 +1,8 @@ +#!/bin/bash +# +# copy this file to /etc/cron.daily/ + +mv /opt/HBMonv2/log/lastheard.log /opt/HBMonv2/log/lastheard.log.save +/usr/bin/tail -250 /opt/HBMonv2/log/lastheard.log.save > /opt/HBMonv2/log/lastheard.log +mv /opt/HBMonv2/log/lastheard.log /opt/HBMonv2/log/lastheard.log.save +/usr/bin/tail -250 /opt/HBMonv2/log/lastheard.log.save > /opt/HBMonv2/log/lastheard.log