mirror of
				https://github.com/ShaYmez/HBmonitor.git
				synced 2025-10-31 12:50:20 -04:00 
			
		
		
		
	Initial Commit
This commit is contained in:
		
						commit
						00f03510ce
					
				
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| *.pyc | ||||
| config.py | ||||
| *.csv | ||||
							
								
								
									
										40
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| --- | ||||
| ### FOR SUPPORT, DISCUSSION, GETTING INVOLVED ### | ||||
| 
 | ||||
| Please join the DVSwitch group at groups.io for online forum support, discussion, and to become part of the development team. | ||||
| 
 | ||||
| DVSwitch@groups.io  | ||||
| 
 | ||||
| --- | ||||
| 
 | ||||
| **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 | ||||
							
								
								
									
										18
									
								
								config_SAMPLE.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								config_SAMPLE.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| REPORT_NAME     = 'system.domain.name'  # Name of the monitored HBlink system | ||||
| CONFIG_INC      = True                  # Include HBlink stats | ||||
| BRIDGES_INC     = True                  # Include Bridge stats (confbrige.py) | ||||
| DMRLINK_IP      = '127.0.0.1'           # HBlink's IP Address | ||||
| DMRLINK_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 | ||||
| 
 | ||||
| # Files and stuff for loading alias files for mapping numbers to names | ||||
| PATH            = './'                          # MUST END IN '/' | ||||
| PEER_FILE       = 'peer_ids.csv'                # Will auto-download from DMR-MARC | ||||
| SUBSCRIBER_FILE = 'subscriber_ids.csv'          # Will auto-download from DMR-MARC | ||||
| TGID_FILE       = 'talkgroup_ids.csv'           # User provided, should be in "integer TGID, TGID name" format | ||||
| LOCAL_SUB_FILE  = 'local_subscriber_ids.csv'    # User provided (optional, leave '' if you don't use it), follow the format of DMR-MARC | ||||
| LOCAL_PEER_FILE = 'local_peer_ids.csv'          # 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        = 'http://radioid.net/static/rptrs.csv' | ||||
| SUBSCRIBER_URL  = 'http://radioid.net/static/users.csv' | ||||
							
								
								
									
										83
									
								
								index_template.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								index_template.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|    <head> | ||||
|       <script type="text/javascript"> | ||||
|          var sock = null; | ||||
|          var ellog = null; | ||||
|           | ||||
|          window.onload = function() { | ||||
|             var wsuri; | ||||
|              | ||||
|             ellog = document.getElementById('log'); | ||||
|             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) { | ||||
|             ellog.innerHTML += _msg + '\n'; | ||||
|             ellog.scrollTop = ellog.scrollHeight; | ||||
|          }; | ||||
|           | ||||
|       </script> | ||||
|    </head> | ||||
|     <body style="font: 10pt arial, sans-serif"> | ||||
|          <h1><center>HBlink Monitoring Server</center></h1> | ||||
|          <h3><center><<<system_name>>></center></h3> | ||||
|          <hr> | ||||
|          <noscript>You must enable JavaScript</noscript> | ||||
|          <style>table, td, th {border: .5px solid black; padding: 2px; border-collapse: collapse; text-align:center;}</style> | ||||
|          <p id="hblink"></p> | ||||
|          <p id="bridge"></p> | ||||
|          <hr> | ||||
|          <h3>Event Log Window:</h3> | ||||
|          <pre id="log" style="height: 10em; overflow-y: scroll; background-color: #ccc;"></pre> | ||||
|     </body> | ||||
| </html> | ||||
							
								
								
									
										0
									
								
								logfile.log
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								logfile.log
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										5
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| Twisted>=12.0.0 | ||||
| dmr_utils>=0.1.7 | ||||
| bitstring>=3.1.3 | ||||
| autobahn>=0.17.2 | ||||
| jinja2>=2.9.6 | ||||
							
								
								
									
										39
									
								
								templates/bridge_table.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								templates/bridge_table.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | ||||
| <hr> | ||||
| <h3>Bridge Group Status Tables:</h3> | ||||
| {% for _bridge, _bridge_data in _table.iteritems() %} | ||||
| <table style="width:100%; font: 10pt arial, sans-serif"> | ||||
|     <colgroup> | ||||
|         <col style="width: 10%" /> | ||||
|         <col style="width: 5%"  /> | ||||
|         <col style="width: 5%"  /> | ||||
|         <col style="width: 10%" /> | ||||
|         <col style="width: 10%" /> | ||||
|         <col style="width: 10%" /> | ||||
|         <col style="width: 25%" /> | ||||
|         <col style="width: 25%" /> | ||||
|     </colgroup> | ||||
|     <h4>Conference Bridge: {{ _bridge }}</h4> | ||||
|     <tr style="width:100%; font: 10pt arial, sans-serif; background-color:#666666; color:white"> | ||||
|         <th>System</th> | ||||
|         <th>Slot</th> | ||||
|         <th>TGID</th> | ||||
|         <th>Status</th> | ||||
|         <th>Timeout</th> | ||||
|         <th>Timeout Action</th> | ||||
|         <th>Connect TGIDs</th> | ||||
|         <th>Disconnect TGIDs</th> | ||||
|     </tr> | ||||
|     {% for system, _system_data in _table[_bridge].iteritems() %} | ||||
|     <tr> | ||||
|         <td>{{ system }}</td> | ||||
|         <td>{{ _table[_bridge][system]['TS'] }}</td> | ||||
|         <td>{{ _table[_bridge][system]['TGID'] }}</td> | ||||
|         <td style="background-color:{{ _table[_bridge][system]['COLOR'] }}">{{ _table[_bridge][system]['ACTIVE'] }}</td> | ||||
|         <td>{{ _table[_bridge][system]['EXP_TIME'] }}</td> | ||||
|         <td>{{ _table[_bridge][system]['TO_ACTION'] }}</td> | ||||
|         <td>{{ _table[_bridge][system]['TRIG_ON'] }}</td> | ||||
|         <td>{{ _table[_bridge][system]['TRIG_OFF'] }}</td> | ||||
|     </tr> | ||||
|     {% endfor %} | ||||
| </table> | ||||
| {% endfor %} | ||||
							
								
								
									
										72
									
								
								templates/hblink_table.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								templates/hblink_table.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | ||||
| <h3>HBlink Status Tables:</h3> | ||||
| <h4>Master Systems</h4> | ||||
| <table style="width:100%; font: 10pt arial, sans-serif"> | ||||
|     <colgroup> | ||||
|         <col style="width: 20%" /> | ||||
|         <col style="width: 15%" /> | ||||
|         <col style="width: 15%" /> | ||||
|         <col style="width: 10%" /> | ||||
|         <col style="width: 15%" /> | ||||
|         <col style="width: 15%" /> | ||||
|         <col style="width: 10%" /> | ||||
|     </colgroup> | ||||
|     <tr style="width:100%; font: 10pt arial, sans-serif; background-color:#666666; color:white"> | ||||
|         <th>HBP System</th> | ||||
|         <th>Client Radio ID</th> | ||||
|         <th>Callsign</th> | ||||
|         <th>Connection</th> | ||||
|         <th>Pings</br>Received</th> | ||||
|         <th>IP</th> | ||||
|         <th>Port</th> | ||||
|     </tr> | ||||
|      | ||||
|     {% for _master in _table['MASTERS'] %} | ||||
|      | ||||
|     <tr> | ||||
|         <td style="font-weight:bold" rowspan="{{ _table['MASTERS'][_master]|length * 2 }}"> {{ _master}} </td> | ||||
|         {% for _client, _cdata in _table['MASTERS'][_master]['CLIENTS'].iteritems() %} | ||||
|         <td>{{ _client }}</td> | ||||
|         <td>{{ _cdata['CALLSIGN'] }}</td> | ||||
|         <td style={{ 'background-color:#00ff00' if _cdata['CONNECTION'] == 'YES' else ';background-color:#ff0000' }}>{{ _cdata['CONNECTION'] }}</td> | ||||
|         <td>{{ _cdata['PINGS_RECEIVED'] }}</td> | ||||
|         <td>{{ _cdata['IP'] }}</td> | ||||
|         <td>{{ _cdata['PORT'] }}</td> | ||||
|     </tr> | ||||
|     {% endfor %} | ||||
|     {% endfor %} | ||||
| </table> | ||||
| 
 | ||||
| <h4>Client Systems</h4> | ||||
| <table style="width:100%; font: 10pt arial, sans-serif"> | ||||
|     <colgroup> | ||||
|         <col style="width: 20%" /> | ||||
|         <col style="width: 15%" /> | ||||
|         <col style="width: 15%" /> | ||||
|         <col style="width: 10%" /> | ||||
|         <col style="width: 15%" /> | ||||
|         <col style="width: 15%" /> | ||||
|         <col style="width: 10%" /> | ||||
|     </colgroup> | ||||
|     <tr style="width:100%; font: 10pt arial, sans-serif; background-color:#666666; color:white"> | ||||
|         <th>HBP System</th> | ||||
|         <th>Client Radio ID</th> | ||||
|         <th>Callsign</th> | ||||
|         <th>Connection</th> | ||||
|         <th>Ping Sent</th> | ||||
|         <th>Ping Ack</th> | ||||
|         <th>Master</th> | ||||
|     </tr> | ||||
|      | ||||
|     {% for _client in _table['CLIENTS'] %} | ||||
|      | ||||
|     <tr> | ||||
|         <td style="font-weight:bold"> {{ _client}} </td> | ||||
|         <td>{{ _table['CLIENTS'][_client]['RADIO_ID'] }}</td> | ||||
|         <td>{{ _table['CLIENTS'][_client]['CALLSIGN'] }}</td> | ||||
|         <td style={{ 'background-color:#00ff00' if _table['CLIENTS'][_client]['STATS']['CONNECTION'] == 'YES' else ';background-color:#ff0000' }}>{{ _table['CLIENTS'][_client]['STATS']['CONNECTION'] }}</td> | ||||
|         <td>{{ _table['CLIENTS'][_client]['STATS']['PINGS_SENT'] }}</td> | ||||
|         <td>{{ _table['CLIENTS'][_client]['STATS']['PINGS_ACKD'] }}</td> | ||||
|         <td>{{ _table['CLIENTS'][_client]['MASTER_IP'] }}</td> | ||||
|     </tr> | ||||
|     {% endfor %} | ||||
| </table> | ||||
							
								
								
									
										415
									
								
								web_tables.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										415
									
								
								web_tables.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,415 @@ | ||||
| #!/usr/bin/env python | ||||
| # | ||||
| ############################################################################### | ||||
| #   Copyright (C) 2016  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 | ||||
| ############################################################################### | ||||
| 
 | ||||
| from __future__ import print_function | ||||
| 
 | ||||
| # Standard modules  | ||||
| import logging | ||||
| import sys | ||||
| 
 | ||||
| # 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.static import File | ||||
| 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 pprint import pprint | ||||
| from time import time, strftime, localtime | ||||
| from cPickle import loads | ||||
| from binascii import b2a_hex as h | ||||
| from os.path import getmtime | ||||
| from collections import deque | ||||
| 
 | ||||
| # Web templating environment | ||||
| from jinja2 import Environment, PackageLoader, select_autoescape | ||||
| 
 | ||||
| # Utilities from K0USY Group sister project | ||||
| from dmr_utils.utils import int_id, get_alias, try_download, mk_full_id_dict | ||||
| 
 | ||||
| # Configuration variables and IPSC constants | ||||
| from config import * | ||||
| #from ipsc_const import * | ||||
| 
 | ||||
| # 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': {}, 'CLIENTS': {}} | ||||
| BRIDGES     = {} | ||||
| BTABLE      = {} | ||||
| BTABLE['BRIDGES'] = {} | ||||
| BRIDGES_RX  = '' | ||||
| CONFIG_RX   = '' | ||||
| LOGBUF      = deque(100*[''], 100) | ||||
| RED         = '#ff0000' | ||||
| GREEN       = '#00ff00' | ||||
| BLUE        = '#0000ff' | ||||
| ORANGE      = '#ff8000' | ||||
| WHITE       = '#ffffff' | ||||
| 
 | ||||
| 
 | ||||
| # 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 | ||||
| 
 | ||||
| # Build the HBlink connections table | ||||
| def build_hblink_table(_config): | ||||
|     _stats_table = {'MASTERS': {}, 'CLIENTS': {}} | ||||
|     for _hbp, _hbp_data in _config.iteritems():  | ||||
|         if _hbp_data['ENABLED'] == True: | ||||
|             if _hbp_data['MODE'] == 'MASTER': | ||||
|                 _stats_table['MASTERS'][_hbp] = {} | ||||
|                 _stats_table['MASTERS'][_hbp]['REPEAT'] = _hbp_data['REPEAT'] | ||||
|                 _stats_table['MASTERS'][_hbp]['CLIENTS'] = {} | ||||
|                 for _client in _hbp_data['CLIENTS']: | ||||
|                     _stats_table['MASTERS'][_hbp]['CLIENTS'][int_id(_client)] = {} | ||||
|                     _stats_table['MASTERS'][_hbp]['CLIENTS'][int_id(_client)]['CALLSIGN'] = _hbp_data['CLIENTS'][_client]['CALLSIGN'] | ||||
|                     _stats_table['MASTERS'][_hbp]['CLIENTS'][int_id(_client)]['CONNECTION'] = _hbp_data['CLIENTS'][_client]['CONNECTION'] | ||||
|                     _stats_table['MASTERS'][_hbp]['CLIENTS'][int_id(_client)]['IP'] = _hbp_data['CLIENTS'][_client]['IP'] | ||||
|                     _stats_table['MASTERS'][_hbp]['CLIENTS'][int_id(_client)]['PINGS_RECEIVED'] = _hbp_data['CLIENTS'][_client]['PINGS_RECEIVED'] | ||||
|                     _stats_table['MASTERS'][_hbp]['CLIENTS'][int_id(_client)]['LAST_PING'] = _hbp_data['CLIENTS'][_client]['LAST_PING'] | ||||
|                     _stats_table['MASTERS'][_hbp]['CLIENTS'][int_id(_client)]['PORT'] = _hbp_data['CLIENTS'][_client]['PORT'] | ||||
|             elif _hbp_data['MODE'] == 'CLIENT': | ||||
|                 _stats_table['CLIENTS'][_hbp] = {} | ||||
|                 _stats_table['CLIENTS'][_hbp]['CALLSIGN'] = _hbp_data['CALLSIGN'] | ||||
|                 _stats_table['CLIENTS'][_hbp]['RADIO_ID'] = int_id(_hbp_data['RADIO_ID']) | ||||
|                 _stats_table['CLIENTS'][_hbp]['MASTER_IP'] = _hbp_data['MASTER_IP'] | ||||
|                 _stats_table['CLIENTS'][_hbp]['STATS'] = _hbp_data['STATS'] | ||||
|                  | ||||
|     return(_stats_table) | ||||
|   | ||||
| 
 | ||||
| # | ||||
| # 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 _bridges.iteritems(): | ||||
|         _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'] = GREEN | ||||
|             elif system['ACTIVE'] == False: | ||||
|                 _stats_table[_bridge][system['SYSTEM']]['ACTIVE'] = 'Disconnected' | ||||
|                 _stats_table[_bridge][system['SYSTEM']]['COLOR'] = 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 | ||||
| 
 | ||||
| # | ||||
| # PROCESS IN COMING MESSAGES AND TAKE THE CORRECT ACTION DEPENING ON THE OPCODE | ||||
| # | ||||
| def process_message(_message): | ||||
|     global CTABLE, CONFIG, BRIDGES, CONFIG_RX, BRIDGES_RX | ||||
|     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(_message) | ||||
|         CONFIG_RX = strftime('%Y-%m-%d %H:%M:%S', localtime(time())) | ||||
|         CTABLE = build_hblink_table(CONFIG) | ||||
|      | ||||
|     elif opcode == OPCODE['BRIDGE_SND']: | ||||
|         logging.debug('got BRIDGE_SND opcode') | ||||
|         BRIDGES = load_dictionary(_message) | ||||
|         BRIDGES_RX = strftime('%Y-%m-%d %H:%M:%S', localtime(time())) | ||||
|         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(",") | ||||
|         if p[0] == 'GROUP VOICE': | ||||
|             if p[1] == 'END': | ||||
|                 log_message = '{}: {} {}:   System: {}; IPSC Peer: {} - {}; Subscriber: {} - {}; TS: {}; TGID: {}; Duration: {}s'.format(_now, p[0], p[1], p[2], p[4], alias_string(int(p[4]), peer_ids), p[5], alias_string(int(p[5]), subscriber_ids), p[6], p[7], p[8]) | ||||
|             elif p[1] == 'START': | ||||
|                 log_message = '{}: {} {}: System: {}; IPSC Peer: {} - {}; Subscriber: {} - {}; TS: {}; TGID: {}'.format(_now, p[0], p[1], p[2], p[4], alias_string(int(p[4]), peer_ids), p[5], alias_string(int(p[5]), subscriber_ids), p[6], p[7]) | ||||
|             elif p[1] == 'END WITHOUT MATCHING START': | ||||
|                 log_message = '{}: {} {} on IPSC System {}: IPSC Peer: {} - {}; Subscriber: {} - {}; TS: {}; TGID: {}'.format(_now, p[0], p[1], p[2], p[4], alias_string(int(p[4]), peer_ids), p[5], alias_string(int(p[5]), subscriber_ids), p[6], p[7]) | ||||
|             else: | ||||
|                 log_message = '{}: UNKNOWN GROUP VOICE LOG MESSAGE'.format(_now) | ||||
|         else: | ||||
|             log_message = '{}: UNKNOWN LOG MESSAGE'.format(_now) | ||||
|              | ||||
|         dashboard_server.broadcast('l' + log_message) | ||||
|         LOGBUF.append(log_message) | ||||
|     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): | ||||
|         pass | ||||
|          | ||||
|     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' + str(dtemplate.render(_table=CTABLE))) | ||||
|         self.sendMessage('b' + str(btemplate.render(_table=BTABLE['BRIDGES']))) | ||||
|         for _message in LOGBUF: | ||||
|             if _message: | ||||
|                 self.sendMessage('l' + _message) | ||||
| 
 | ||||
|     def onMessage(self, payload, isBinary): | ||||
|         if isBinary: | ||||
|             logging.info('Binary message received: %s bytes', len(payload)) | ||||
|         else: | ||||
|             logging.info('Text message received: %s', payload.decode('utf8')) | ||||
| 
 | ||||
|     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.append(client) | ||||
| 
 | ||||
|     def unregister(self, client): | ||||
|         if client in self.clients: | ||||
|             logging.info('unregistered client %s', client.peer) | ||||
|             self.clients.remove(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) | ||||
|         if request.uri == '/': | ||||
|             return index_html | ||||
|         else: | ||||
|             return 'Bad request' | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     logging.basicConfig(level=logging.INFO,handlers=[logging.FileHandler(PATH + 'logfile.log'),logging.StreamHandler()]) | ||||
| 
 | ||||
|     logging.info('web_tables.py starting up') | ||||
|      | ||||
|     # Download alias files | ||||
|     result = try_download(PATH, 'peer_ids.csv', PEER_URL, (FILE_RELOAD * 86400)) | ||||
|     logging.info(result) | ||||
|     | ||||
|     result = try_download(PATH, 'subscriber_ids.csv', 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('web_tables', '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) | ||||
|      | ||||
|     # Start update loop | ||||
|     update_stats = task.LoopingCall(build_stats) | ||||
|     update_stats.start(FREQUENCY) | ||||
|      | ||||
|     # 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() | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user