diff --git a/hb_config.py b/hb_config.py index 110b493..50fb570 100755 --- a/hb_config.py +++ b/hb_config.py @@ -48,6 +48,7 @@ def build_config(_config_file): CONFIG = {} CONFIG['GLOBAL'] = {} + CONFIG['REPORTS'] = {} CONFIG['LOGGER'] = {} CONFIG['ALIASES'] = {} CONFIG['AMBE'] = {} @@ -62,6 +63,14 @@ def build_config(_config_file): 'MAX_MISSED': config.getint(section, 'MAX_MISSED') }) + elif section == 'REPORTS': + CONFIG['REPORTS'].update({ + 'REPORT': config.getboolean(section, 'REPORT'), + 'REPORT_INTERVAL': config.getint(section, 'REPORT_INTERVAL'), + 'REPORT_PORT': config.getint(section, 'REPORT_PORT'), + 'REPORT_CLIENTS': config.get(section, 'REPORT_CLIENTS').split(',') + }) + elif section == 'LOGGER': CONFIG['LOGGER'].update({ 'LOG_FILE': config.get(section, 'LOG_FILE'), diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index 7e90e3d..201c102 100644 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -9,6 +9,26 @@ PATH: ./ PING_TIME: 5 MAX_MISSED: 3 + +# NOT YET WORKING: NETWORK REPORTING CONFIGURATION +# Enabling "REPORT" will configure a socket-based reporting +# system that will send the configuration and other items +# to a another process (local or remote) that may process +# the information for some useful purpose, like a web dashboard. +# +# REPORT - True to enable, False to disable +# REPORT_INTERVAL - Seconds between reports +# REPORT_PORT - TCP port to listen on if "REPORT_NETWORKS" = NETWORK +# REPORT_CLIENTS - comma separated list of IPs you will allow clients +# to connect on. Entering a * will allow all. +# +[REPORTS] +REPORT: False +REPORT_INTERVAL: 60 +REPORT_PORT: 4321 +REPORT_CLIENTS: 127.0.0.1 + + # SYSTEM LOGGER CONFIGURAITON # This allows the logger to be configured without chaning the individual # python logger stuff. LOG_FILE should be a complete path/filename for *your* diff --git a/hblink.py b/hblink.py index 6260241..bdb859b 100755 --- a/hblink.py +++ b/hblink.py @@ -39,15 +39,19 @@ from bitstring import BitArray import socket # Twisted is pretty important, so I keep it separate -from twisted.internet.protocol import DatagramProtocol -from twisted.internet import reactor -from twisted.internet import task +from twisted.internet.protocol import DatagramProtocol, Factory, Protocol +from twisted.protocols.basic import NetstringReceiver +from twisted.internet import reactor, task # Other files we pull from -- this is mostly for readability and segmentation import hb_log import hb_config from dmr_utils.utils import int_id, hex_str_4 +# Imports for the reporting server +import cPickle as pickle +from reporting_const import * + # Does anybody read this stuff? There's a PEP somewhere that says I should do this. __author__ = 'Cortney T. Buffington, N0MJS' __copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group' @@ -60,6 +64,25 @@ __email__ = 'n0mjs@me.com' # Global variables used whether we are a module or __main__ systems = {} +# Timed loop used for reporting IPSC status +# +# REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE +def config_reports(_config, _logger, _factory): + if _config['REPORTS']['REPORT']: + def reporting_loop(_logger, _server): + _logger.debug('Periodic reporting loop started') + _server.send_config() + + _logger.info('HBlink TCP reporting server configured') + + report_server = _factory(_config, _logger) + report_server.clients = [] + reactor.listenTCP(_config['REPORTS']['REPORT_PORT'], report_server) + + reporting = task.LoopingCall(reporting_loop, _logger, report_server) + reporting.start(_config['REPORTS']['REPORT_INTERVAL']) + + # Shut ourselves down gracefully by disconnecting from the masters and clients. def hblink_handler(_signal, _frame, _logger): for system in systems: @@ -469,6 +492,53 @@ class HBSYSTEM(DatagramProtocol): else: self._logger.error('(%s) Received an invalid command in packet: %s', self._system, ahex(_data)) +# +# Socket-based reporting section +# +class report(NetstringReceiver): + def __init__(self, factory): + self._factory = factory + + def connectionMade(self): + self._factory.clients.append(self) + self._factory._logger.info('HBlink reporting client connected: %s', self.transport.getPeer()) + + def connectionLost(self, reason): + self._factory._logger.info('HBlink reporting client disconnected: %s', self.transport.getPeer()) + self._factory.clients.remove(self) + + def stringReceived(self, data): + self.process_message(data) + + def process_message(self, _message): + opcode = _message[:1] + if opcode == REPORT_OPCODES['CONFIG_REQ']: + self._factory._logger.info('HBlink reporting client sent \'CONFIG_REQ\': %s', self.transport.getPeer()) + self.send_config() + else: + self._factory._logger.error('got unknown opcode') + +class reportFactory(Factory): + def __init__(self, config, logger): + self._config = config + self._logger = logger + + def buildProtocol(self, addr): + if (addr.host) in self._config['REPORTS']['REPORT_CLIENTS'] or '*' in self._config['REPORTS']['REPORT_CLIENTS']: + self._logger.debug('Permitting report server connection attempt from: %s:%s', addr.host, addr.port) + return report(self) + else: + self._logger.error('Invalid report server connection attempt from: %s:%s', addr.host, addr.port) + return None + + def send_clients(self, _message): + for client in self.clients: + client.sendString(_message) + + def send_config(self): + serialized = pickle.dumps(self._config['SYSTEMS'], protocol=pickle.HIGHEST_PROTOCOL) + self.send_clients(REPORT_OPCODES['CONFIG_SND']+serialized) + #************************************************ # MAIN PROGRAM LOOP STARTS HERE @@ -514,6 +584,9 @@ if __name__ == '__main__': # Set signal handers so that we can gracefully exit if need be for sig in [signal.SIGTERM, signal.SIGINT]: signal.signal(sig, sig_handler) + + # INITIALIZE THE REPORTING LOOP + config_reports(CONFIG, logger, reportFactory) # HBlink instance creation logger.info('HBlink \'HBlink.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...') diff --git a/reporting_const.py b/reporting_const.py new file mode 100644 index 0000000..bd161a5 --- /dev/null +++ b/reporting_const.py @@ -0,0 +1,30 @@ +############################################################################### +# Copyright (C) 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 +############################################################################### + +# Opcodes for the network-based reporting protocol + +REPORT_OPCODES = { + 'CONFIG_REQ': '\x00', + 'CONFIG_SND': '\x01', + 'BRIDGE_REQ': '\x02', + 'BRIDGE_SND': '\x03', + 'CONFIG_UPD': '\x04', + 'BRIDGE_UPD': '\x05', + 'LINK_EVENT': '\x06', + 'BRDG_EVENT': '\x07', + } \ No newline at end of file