Compare commits
41 Commits
IPSC_Bridg
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
a55f73d059 | ||
|
1c684486ca | ||
|
67fd6d62a5 | ||
|
785f44e7e6 | ||
|
c023d4a565 | ||
|
ad399792c9 | ||
|
198278c288 | ||
|
2520f394ed | ||
|
bc59c75eb0 | ||
|
01a3fff754 | ||
|
2e1a4c3a58 | ||
|
59a59e4100 | ||
|
81c3467ec0 | ||
|
25d6bc08d0 | ||
|
e8311b1f54 | ||
|
cd11397416 | ||
|
cdd65d8edf | ||
|
2b63b5c111 | ||
|
3fc0bdc63d | ||
|
f5bc547d4d | ||
|
0acc6042e8 | ||
|
b4ab2d31a3 | ||
|
5950240787 | ||
|
63611f2c6c | ||
|
eb2ffe4ecb | ||
|
bf699dcfbc | ||
|
c6a543527f | ||
|
3279aeb527 | ||
|
f216300539 | ||
|
8e858e48a2 | ||
|
c16d549e94 | ||
|
ae9f71d715 | ||
|
4ac862b93c | ||
|
8fbd7ccf33 | ||
|
b8d1449d2f | ||
|
c8804d7231 | ||
|
7fde9d1ce8 | ||
|
d1143ddb7c | ||
|
0079ad1baa | ||
|
547d6e23ed | ||
|
62fc209b4f |
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
2
DO_NOT_README.md
Normal file → Executable file
2
DO_NOT_README.md
Normal file → Executable file
@ -81,7 +81,7 @@ The following illustrates the communication that a peer (us, for example) has wi
|
||||
^ v | v |
|
||||
| +--------------+ ++-------------+ +---------+ |
|
||||
| NO |Did The Master| YES |Set Keep Alive| |Peer List| NO |
|
||||
+-------------+ Respond ? +---->| Counter To 0 | |Received?+----------+
|
||||
+---------+ Respond ? +---->| Counter To 0 | |Received?+----------+
|
||||
+--------------+ +--------------+ +---------+
|
||||
|
||||
*COMMUNICATION WITH PEERS:*
|
||||
|
0
LICENSE.txt
Normal file → Executable file
0
LICENSE.txt
Normal file → Executable file
5
Retired/README.MD
Executable file
5
Retired/README.MD
Executable file
@ -0,0 +1,5 @@
|
||||
|
||||
**Retired files:**
|
||||
|
||||
The files in this directory are being kept for reference ONLY. They contain routines that may have been of use to someone.
|
||||
Do not try to use these programs as is. They will not work!
|
0
ambe_audio.cfg → Retired/ambe_audio.cfg
Normal file → Executable file
0
ambe_audio.cfg → Retired/ambe_audio.cfg
Normal file → Executable file
0
ambe_audio_commands.txt → Retired/ambe_audio_commands.txt
Normal file → Executable file
0
ambe_audio_commands.txt → Retired/ambe_audio_commands.txt
Normal file → Executable file
@ -127,16 +127,28 @@ def build_bridges(_known_bridges):
|
||||
# are not yet implemented.
|
||||
def build_acl(_sub_acl):
|
||||
try:
|
||||
logger.info('ACL file found, importing entries. This will take about 1.5 seconds per 1 million IDs')
|
||||
acl_file = import_module(_sub_acl)
|
||||
for i, e in enumerate(acl_file.ACL):
|
||||
acl_file.ACL[i] = hex_str_3(acl_file.ACL[i])
|
||||
logger.info('ACL file found and ACL entries imported')
|
||||
ACL_ACTION = acl_file.ACL_ACTION
|
||||
ACL = acl_file.ACL
|
||||
sections = acl_file.ACL.split(':')
|
||||
ACL_ACTION = sections[0]
|
||||
entries_str = sections[1]
|
||||
ACL = set()
|
||||
|
||||
for entry in entries_str.split(','):
|
||||
if '-' in entry:
|
||||
start,end = entry.split('-')
|
||||
start,end = int(start), int(end)
|
||||
for id in range(start, end+1):
|
||||
ACL.add(hex_str_3(id))
|
||||
else:
|
||||
id = int(entry)
|
||||
ACL.add(hex_str_3(id))
|
||||
|
||||
logger.info('ACL loaded: action "{}" for {:,} radio IDs'.format(ACL_ACTION, len(ACL)))
|
||||
|
||||
except ImportError:
|
||||
logger.info('ACL file not found or invalid - all subscriber IDs are valid')
|
||||
ACL_ACTION = 'NONE'
|
||||
ACL = []
|
||||
|
||||
# Depending on which type of ACL is used (PERMIT, DENY... or there isn't one)
|
||||
# define a differnet function to be used to check the ACL
|
0
bridge_rules_SAMPLE.py → Retired/bridge_rules_SAMPLE.py
Normal file → Executable file
0
bridge_rules_SAMPLE.py → Retired/bridge_rules_SAMPLE.py
Normal file → Executable file
0
known_bridges_SAMPLE.py → Retired/known_bridges_SAMPLE.py
Normal file → Executable file
0
known_bridges_SAMPLE.py → Retired/known_bridges_SAMPLE.py
Normal file → Executable file
0
template.py → Retired/template.py
Normal file → Executable file
0
template.py → Retired/template.py
Normal file → Executable file
@ -137,10 +137,12 @@ def make_bridge_config(_confbridge_rules):
|
||||
_system['ON'][i] = hex_str_3(_system['ON'][i])
|
||||
for i, e in enumerate(_system['OFF']):
|
||||
_system['OFF'][i] = hex_str_3(_system['OFF'][i])
|
||||
for i, e in enumerate(_system['RESET']):
|
||||
_system['RESET'][i] = hex_str_3(_system['RESET'][i])
|
||||
_system['TIMEOUT'] = _system['TIMEOUT']*60
|
||||
_system['TIMER'] = time()
|
||||
|
||||
return {'BRIDGE_CONF': bridge_file.BRIDGE_CONF, 'BRIDGES': bridge_file.BRIDGES}
|
||||
return {'BRIDGE_CONF': bridge_file.BRIDGE_CONF, 'BRIDGES': bridge_file.BRIDGES, 'TRUNKS': bridge_file.TRUNKS}
|
||||
|
||||
|
||||
# Import subscriber ACL
|
||||
@ -148,17 +150,28 @@ def make_bridge_config(_confbridge_rules):
|
||||
# Global action is to allow or deny them. Multiple lists with different actions and ranges
|
||||
# are not yet implemented.
|
||||
def build_acl(_sub_acl):
|
||||
ACL = set()
|
||||
try:
|
||||
logger.info('ACL file found, importing entries. This will take about 1.5 seconds per 1 million IDs')
|
||||
acl_file = import_module(_sub_acl)
|
||||
for i, e in enumerate(acl_file.ACL):
|
||||
acl_file.ACL[i] = hex_str_3(acl_file.ACL[i])
|
||||
logger.info('ACL file found and ACL entries imported')
|
||||
ACL_ACTION = acl_file.ACL_ACTION
|
||||
ACL = acl_file.ACL_ACTION
|
||||
sections = acl_file.ACL.split(':')
|
||||
ACL_ACTION = sections[0]
|
||||
entries_str = sections[1]
|
||||
for entry in entries_str.split(','):
|
||||
if '-' in entry:
|
||||
start,end = entry.split('-')
|
||||
start,end = int(start), int(end)
|
||||
for id in range(start, end+1):
|
||||
ACL.add(hex_str_3(id))
|
||||
else:
|
||||
id = int(entry)
|
||||
ACL.add(hex_str_3(id))
|
||||
|
||||
logger.info('ACL loaded: action "{}" for {:,} radio IDs'.format(ACL_ACTION, len(ACL)))
|
||||
|
||||
except ImportError:
|
||||
logger.info('ACL file not found or invalid - all subscriber IDs are valid')
|
||||
ACL_ACTION = 'NONE'
|
||||
ACL = []
|
||||
|
||||
# Depending on which type of ACL is used (PERMIT, DENY... or there isn't one)
|
||||
# define a differnet function to be used to check the ACL
|
||||
@ -240,7 +253,7 @@ class confbridgeIPSC(IPSC):
|
||||
return
|
||||
|
||||
# Process the packet
|
||||
self._logger.debug('(%s) Group Voice Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
|
||||
#self._logger.debug('(%s) Group Voice Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
|
||||
_burst_data_type = _data[30] # Determine the type of voice packet this is (see top of file for possible types)
|
||||
_seq_id = _data[5]
|
||||
|
||||
@ -259,6 +272,8 @@ class confbridgeIPSC(IPSC):
|
||||
|
||||
# BEGIN CONTENTION HANDLING
|
||||
#
|
||||
# If the system is listed as a "TRUNK", there will be no contention handling. All traffic is forwarded to it
|
||||
#
|
||||
# The rules for each of the 4 "ifs" below are listed here for readability. The Frame To Send is:
|
||||
# From a different group than last RX from this IPSC, but it has been less than Group Hangtime
|
||||
# From a different group than last TX to this IPSC, but it has been less than Group Hangtime
|
||||
@ -266,6 +281,7 @@ class confbridgeIPSC(IPSC):
|
||||
# From the same group as the last TX to this IPSC, but from a different subscriber, and it has been less than TS Clear Time
|
||||
# The "continue" at the end of each means the next iteration of the for loop that tests for matching rules
|
||||
#
|
||||
if _target not in TRUNKS:
|
||||
if ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((now - _target_status[_target['TS']]['RX_TIME']) < _target_system['LOCAL']['GROUP_HANGTIME'])):
|
||||
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||
self._logger.info('(%s) Call not bridged to TGID%s, target active or in group hangtime: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID']))
|
||||
@ -291,12 +307,14 @@ class confbridgeIPSC(IPSC):
|
||||
#
|
||||
# Make a copy of the payload
|
||||
_tmp_data = _data
|
||||
# Re-Write the PEER ID in the IPSC Header:
|
||||
_tmp_data = _tmp_data.replace(_peerid, _target_system['LOCAL']['RADIO_ID'], 1)
|
||||
|
||||
# Re-Write the IPSC SRC to match the target network's ID
|
||||
_tmp_data = _tmp_data.replace(_peerid, _target_system['LOCAL']['RADIO_ID'])
|
||||
# Re-Write the IPSC SRC + DST GROUP in IPSC Headers:
|
||||
_tmp_data = _tmp_data.replace(_src_sub + _dst_group, _src_sub + _target['TGID'], 1)
|
||||
|
||||
# Re-Write the destination Group ID
|
||||
_tmp_data = _tmp_data.replace(_dst_group, _target['TGID'])
|
||||
# Re-Write the DST GROUP + IPSC SRC in DMR LC (Header, Terminator and Voice Burst E):
|
||||
_tmp_data = _tmp_data.replace(_dst_group + _src_sub, _target['TGID'] + _src_sub, 1)
|
||||
|
||||
# Re-Write IPSC timeslot value
|
||||
_call_info = int_id(_data[17:18])
|
||||
@ -327,7 +345,6 @@ class confbridgeIPSC(IPSC):
|
||||
# END FRAME FORWARDING
|
||||
#
|
||||
|
||||
|
||||
# Set values for the contention handler to test next time there is a frame to forward
|
||||
_target_status[_target['TS']]['TX_TGID'] = _target['TGID']
|
||||
_target_status[_target['TS']]['TX_TIME'] = now
|
||||
@ -352,7 +369,7 @@ class confbridgeIPSC(IPSC):
|
||||
self.call_start = now
|
||||
self._logger.info('(%s) GROUP VOICE START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group))
|
||||
if self._CONFIG['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
|
||||
self._report.send_bridgeEvent('({}) GROUP VOICE START: CallID: {} PEER: {}, SUB: {}, TS: {}, TGID: {}'.format(self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group)))
|
||||
self._report.send_bridgeEvent('GROUP VOICE,START,{},{},{},{},{},{}'.format(self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group)))
|
||||
|
||||
# Action happens on un-key
|
||||
if _burst_data_type == BURST_DATA_TYPE['VOICE_TERM']:
|
||||
@ -360,11 +377,11 @@ class confbridgeIPSC(IPSC):
|
||||
self.call_duration = now - self.call_start
|
||||
self._logger.info('(%s) GROUP VOICE END: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s Duration: %.2fs', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration)
|
||||
if self._CONFIG['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
|
||||
self._report.send_bridgeEvent('({}) GROUP VOICE END: CallID: {} PEER: {}, SUB: {}, TS: {}, TGID: {} Duration: {:.2f}s'.format(self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration))
|
||||
self._report.send_bridgeEvent('GROUP VOICE,END,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration))
|
||||
else:
|
||||
self._logger.warning('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group))
|
||||
if self._CONFIG['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
|
||||
self._report.send_bridgeEvent('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s'.format(self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group)))
|
||||
self._report.send_bridgeEvent('GROUP VOICE,UNMATCHED END,{},{},{},{},{},{}'.format(self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group)))
|
||||
|
||||
|
||||
# Iterate the rules dictionary
|
||||
@ -373,8 +390,9 @@ class confbridgeIPSC(IPSC):
|
||||
if _system['SYSTEM'] == self._system:
|
||||
|
||||
# TGID matches an ACTIVATION trigger
|
||||
if _dst_group in _system['ON']:
|
||||
if (_dst_group in _system['ON'] or _dst_group in _system['RESET']) and _ts == _system['TS']:
|
||||
# Set the matching rule as ACTIVE
|
||||
if _dst_group in _system['ON']:
|
||||
if _system['ACTIVE'] == False:
|
||||
_system['ACTIVE'] = True
|
||||
self._logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE'])
|
||||
@ -388,8 +406,9 @@ class confbridgeIPSC(IPSC):
|
||||
self._logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - now)
|
||||
|
||||
# TGID matches an DE-ACTIVATION trigger
|
||||
if _dst_group in _system['OFF']:
|
||||
if (_dst_group in _system['OFF'] or _dst_group in _system['RESET']) and _ts == _system['TS']:
|
||||
# Set the matching rule as ACTIVE
|
||||
if _dst_group in _system['OFF']:
|
||||
if _system['ACTIVE'] == True:
|
||||
_system['ACTIVE'] = False
|
||||
self._logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE'])
|
||||
@ -397,12 +416,12 @@ class confbridgeIPSC(IPSC):
|
||||
if _system['TO_TYPE'] == 'ON':
|
||||
_system['TIMER'] = now
|
||||
self._logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge)
|
||||
# Reset tge timer for the rule
|
||||
# Reset the timer for the rule
|
||||
if _system['ACTIVE'] == False and _system['TO_TYPE'] == 'OFF':
|
||||
_system['TIMER'] = now + _system['TIMEOUT']
|
||||
self._logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - now)
|
||||
# Cancel the timer if we've enabled an "ON" type timeout
|
||||
if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON':
|
||||
if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON' and _dst_group in _system['OFF']:
|
||||
_system['TIMER'] = now
|
||||
self._logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge)
|
||||
|
||||
@ -479,6 +498,7 @@ if __name__ == '__main__':
|
||||
# Build the routing rules and other configuration
|
||||
CONFIG_DICT = make_bridge_config('confbridge_rules')
|
||||
BRIDGE_CONF = CONFIG_DICT['BRIDGE_CONF']
|
||||
TRUNKS = CONFIG_DICT['TRUNKS']
|
||||
BRIDGES = CONFIG_DICT['BRIDGES']
|
||||
|
||||
# Build the Access Control List
|
||||
|
22
confbridge_rules_SAMPLE.py
Normal file → Executable file
22
confbridge_rules_SAMPLE.py
Normal file → Executable file
@ -19,6 +19,9 @@ configuration file.
|
||||
* ON and OFF are LISTS of Talkgroup IDs used to trigger this system off and on. Even if you
|
||||
only want one (as shown in the ON example), it has to be in list format. None can be
|
||||
handled with an empty list, such as " 'ON': [] ".
|
||||
* RESET is a list of Talkgroup IDs that, in addition to the ON and OFF lists will cause a running
|
||||
timer to be reset. This is useful if you are using different TGIDs for voice traffic than
|
||||
triggering. If you are not, there is NO NEED to use this feature.
|
||||
* TO_TYPE is timeout type. If you want to use timers, ON means when it's turned on, it will
|
||||
turn off afer the timout period and OFF means it will turn back on after the timout
|
||||
period. If you don't want to use timers, set it to anything else, but 'NONE' might be
|
||||
@ -38,18 +41,25 @@ BRIDGE_CONF = {
|
||||
'REPORT': True,
|
||||
}
|
||||
|
||||
# TRUNK IPSC Systems -- trunk bypasses the contention handler and always transmits traffic
|
||||
#
|
||||
# This is a python LIST data type. It needs to be here, but just leave it empty if not used.
|
||||
# The contents are a quoted, comma separated list of IPSC systems that are traffic trunks.
|
||||
# Example: TRUNKS = ['MASTER-1', 'CLIENT-2']
|
||||
TRUNKS = []
|
||||
|
||||
BRIDGES = {
|
||||
'WORLDWIDE': [
|
||||
{'SYSTEM': 'MASTER-1', 'TS': 1, 'TGID': 1, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'ON', 'ON': [2,], 'OFF': [9,10]},
|
||||
{'SYSTEM': 'CLIENT-1', 'TS': 1, 'TGID': 3100, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'ON', 'ON': [2,], 'OFF': [9,10]},
|
||||
{'SYSTEM': 'MASTER-1', 'TS': 1, 'TGID': 1, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'ON', 'ON': [2,], 'OFF': [9,10], 'RESET': []},
|
||||
{'SYSTEM': 'CLIENT-1', 'TS': 1, 'TGID': 3100, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'ON', 'ON': [2,], 'OFF': [9,10], 'RESET': []}
|
||||
],
|
||||
'ENGLISH': [
|
||||
{'SYSTEM': 'MASTER-1', 'TS': 1, 'TGID': 13, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [3,], 'OFF': [8,10]},
|
||||
{'SYSTEM': 'CLIENT-2', 'TS': 1, 'TGID': 13, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [3,], 'OFF': [8,10]},
|
||||
{'SYSTEM': 'MASTER-1', 'TS': 1, 'TGID': 13, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [3,], 'OFF': [8,10], 'RESET': []},
|
||||
{'SYSTEM': 'CLIENT-2', 'TS': 1, 'TGID': 13, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [3,], 'OFF': [8,10], 'RESET': []}
|
||||
],
|
||||
'STATEWIDE': [
|
||||
{'SYSTEM': 'MASTER-1', 'TS': 2, 'TGID': 3129, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [4,], 'OFF': [7,10]},
|
||||
{'SYSTEM': 'CLIENT-2', 'TS': 2, 'TGID': 3129, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [4,], 'OFF': [7,10]},
|
||||
{'SYSTEM': 'MASTER-1', 'TS': 2, 'TGID': 3129, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [4,], 'OFF': [7,10], 'RESET': []},
|
||||
{'SYSTEM': 'CLIENT-2', 'TS': 2, 'TGID': 3129, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [4,], 'OFF': [7,10], 'RESET': []}
|
||||
]
|
||||
}
|
||||
|
||||
|
10
dmrlink_SAMPLE.cfg
Normal file → Executable file
10
dmrlink_SAMPLE.cfg
Normal file → Executable file
@ -86,11 +86,11 @@ LOG_NAME: DMRlink
|
||||
TRY_DOWNLOAD: True
|
||||
LOCAL_FILE: False
|
||||
PATH: ./
|
||||
PEER_FILE: peer_ids.csv
|
||||
SUBSCRIBER_FILE: subscriber_ids.csv
|
||||
TGID_FILE: talkgroup_ids.csv
|
||||
PEER_URL: http://www.dmr-marc.net/cgi-bin/trbo-database/datadump.cgi?table=repeaters&format=csv&header=0
|
||||
SUBSCRIBER_URL: http://www.dmr-marc.net/cgi-bin/trbo-database/datadump.cgi?table=users&format=csv&header=0
|
||||
PEER_FILE: peer_ids.json
|
||||
SUBSCRIBER_FILE: subscriber_ids.json
|
||||
TGID_FILE: talkgroup_ids.json
|
||||
PEER_URL: https://www.radioid.net/static/rptrs.json
|
||||
SUBSCRIBER_URL: https://www.radioid.net/static/users.json
|
||||
STALE_DAYS: 7
|
||||
|
||||
|
||||
|
0
documents/FAQ.md
Normal file → Executable file
0
documents/FAQ.md
Normal file → Executable file
0
documents/internal_data_decode.txt
Normal file → Executable file
0
documents/internal_data_decode.txt
Normal file → Executable file
0
documents/voice_burst_decoding.txt
Normal file → Executable file
0
documents/voice_burst_decoding.txt
Normal file → Executable file
0
documents/voice_packets.txt
Normal file → Executable file
0
documents/voice_packets.txt
Normal file → Executable file
0
ipsc/.gitignore
vendored
Normal file → Executable file
0
ipsc/.gitignore
vendored
Normal file → Executable file
0
ipsc/__init__.py
Normal file → Executable file
0
ipsc/__init__.py
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
###############################################################################
|
||||
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
|
||||
# Copyright (C) 2016-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
|
||||
@ -21,16 +21,32 @@
|
||||
import ConfigParser
|
||||
import sys
|
||||
|
||||
from socket import gethostbyname
|
||||
from socket import getaddrinfo, IPPROTO_UDP
|
||||
|
||||
# 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'
|
||||
__copyright__ = 'Copyright (c) 2016-2018 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
__license__ = 'GNU GPLv3'
|
||||
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||
__email__ = 'n0mjs@me.com'
|
||||
|
||||
|
||||
def get_address(_config):
|
||||
ipv4 = ''
|
||||
ipv6 = ''
|
||||
socket_info = getaddrinfo(_config, None, 0, 0, IPPROTO_UDP)
|
||||
for item in socket_info:
|
||||
if item[0] == 2:
|
||||
ipv4 = item[4][0]
|
||||
elif item[0] == 30:
|
||||
ipv6 = item[4][0]
|
||||
|
||||
if ipv4:
|
||||
return ipv4
|
||||
if ipv6:
|
||||
return ipv6
|
||||
return 'invalid address'
|
||||
|
||||
def build_config(_config_file):
|
||||
config = ConfigParser.ConfigParser()
|
||||
|
||||
@ -115,7 +131,7 @@ def build_config(_config_file):
|
||||
|
||||
# Things we need to know to connect and be a peer in this IPSC
|
||||
'RADIO_ID': hex(int(config.get(section, 'RADIO_ID')))[2:].rjust(8,'0').decode('hex'),
|
||||
'IP': gethostbyname(config.get(section, 'IP')),
|
||||
'IP': config.get(section, 'IP'),
|
||||
'PORT': config.getint(section, 'PORT'),
|
||||
'ALIVE_TIMER': config.getint(section, 'ALIVE_TIMER'),
|
||||
'MAX_MISSED': config.getint(section, 'MAX_MISSED'),
|
||||
@ -144,7 +160,7 @@ def build_config(_config_file):
|
||||
})
|
||||
if not CONFIG['SYSTEMS'][section]['LOCAL']['MASTER_PEER']:
|
||||
CONFIG['SYSTEMS'][section]['MASTER'].update({
|
||||
'IP': gethostbyname(config.get(section, 'MASTER_IP')),
|
||||
'IP': get_address(config.get(section, 'MASTER_IP')),
|
||||
'PORT': config.getint(section, 'MASTER_PORT')
|
||||
})
|
||||
|
||||
@ -219,7 +235,7 @@ if __name__ == '__main__':
|
||||
|
||||
# Ensure we have a path for the config file, if one wasn't specified, then use the execution directory
|
||||
if not cli_args.CONFIG_FILE:
|
||||
cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||
cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/../dmrlink.cfg'
|
||||
|
||||
|
||||
pprint(build_config(cli_args.CONFIG_FILE))
|
||||
|
0
ipsc/ipsc_const.py
Normal file → Executable file
0
ipsc/ipsc_const.py
Normal file → Executable file
0
ipsc/ipsc_mask.py
Normal file → Executable file
0
ipsc/ipsc_mask.py
Normal file → Executable file
0
ipsc/reporting_const.py
Normal file → Executable file
0
ipsc/reporting_const.py
Normal file → Executable file
183
mk-dmrlink
183
mk-dmrlink
@ -1,8 +1,10 @@
|
||||
#! /bin/bash
|
||||
|
||||
currentdir=`pwd`
|
||||
PREFIX=/opt/dmrlink
|
||||
echo "DMRlink will be installed in: $PREFIX"
|
||||
|
||||
echo "Current working directory is" $currentdir
|
||||
currentdir=`pwd`
|
||||
echo "Current working directory is: $currentdir"
|
||||
|
||||
echo ""
|
||||
|
||||
@ -13,17 +15,44 @@ echo ""
|
||||
#################################################
|
||||
|
||||
# Install the required support programs
|
||||
apt-get install unzip -y
|
||||
apt-get install python-dev -y
|
||||
apt-get install python-pip -y
|
||||
apt-get install python-twisted -y
|
||||
pip install bitstring
|
||||
pip install bitarray
|
||||
|
||||
cd /opt
|
||||
git clone https://github.com/n0mjs710/dmr_utils.git
|
||||
cd dmr_utils/
|
||||
pip install .
|
||||
distro=$(lsb_release -i | awk -F":" '{ gsub(/^[ \t]+/, "", $2); print $2 }')
|
||||
release=$(lsb_release -r | awk -F":" '{ gsub(/^[ \t]+/, "", $2); print $2 }')
|
||||
echo "Current Linux distribution is: $distro $release"
|
||||
|
||||
if [[ "$distro" =~ ^(CentOS|Fedora|openSUSE|)$ ]]; then
|
||||
echo "$distro uses yum"
|
||||
yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-$(echo $release | awk -F"." '{print $1}').noarch.rpm
|
||||
yum install -y gcc gcc-c++ glibc-devel make
|
||||
yum install -y unzip
|
||||
yum install -y python-devel
|
||||
yum install -y python-pip
|
||||
yum install -y python-twisted
|
||||
# pip install bitstring
|
||||
# pip install bitarray
|
||||
else
|
||||
echo "$distro uses apt"
|
||||
apt-get install -y build-essential
|
||||
apt-get install -y unzip
|
||||
apt-get install -y python-dev
|
||||
apt-get install -y python-pip
|
||||
apt-get install -y python-twisted
|
||||
# pip install bitstring
|
||||
# pip install bitarray
|
||||
fi
|
||||
|
||||
# Install dmr_utils with pip install
|
||||
pip install dmr_utils
|
||||
###############################################################################
|
||||
# Following lines should be removed due to the pip install method for dmr_utils
|
||||
#cd /opt
|
||||
#if [ ! -d /opt/dmr_utils ]; then
|
||||
# git clone https://github.com/n0mjs710/dmr_utils.git
|
||||
#fi
|
||||
#cd dmr_utils/
|
||||
#git pull
|
||||
#pip install .
|
||||
###############################################################################
|
||||
|
||||
echo "Required programs installed, continuing"
|
||||
|
||||
@ -32,19 +61,13 @@ echo "Required programs installed, continuing"
|
||||
# The needed files are copied to /opt/dmrlink
|
||||
|
||||
# Make needed directories
|
||||
mkdir -p /opt/dmrlink/ambe_audio/
|
||||
mkdir -p /opt/dmrlink/bridge/
|
||||
mkdir -p /opt/dmrlink/confbridge/
|
||||
mkdir -p /opt/dmrlink/log/
|
||||
mkdir -p /opt/dmrlink/playback/
|
||||
mkdir -p /opt/dmrlink/play_group/
|
||||
mkdir -p /opt/dmrlink/proxy/
|
||||
mkdir -p /opt/dmrlink/rcm/
|
||||
mkdir -p /opt/dmrlink/record/
|
||||
mkdir -p /opt/dmrlink/samples
|
||||
mkdir -p /var/log/dmrlink
|
||||
mkdir -p $PREFIX/confbridge/
|
||||
mkdir -p $PREFIX/playback/
|
||||
mkdir -p $PREFIX/proxy/
|
||||
mkdir -p $PREFIX/samples
|
||||
mkdir -p /var/log/dmrlink
|
||||
|
||||
cd /opt/dmrlink
|
||||
cd $PREFIX
|
||||
|
||||
# Put common files in /opt/dmrlink
|
||||
# cp $currentdir/peer_ids.csv /opt/dmrlink
|
||||
@ -52,95 +75,89 @@ cd /opt/dmrlink
|
||||
# cp $currentdir/talkgroup_ids.csv /opt/dmrlink
|
||||
|
||||
# Copy ipsc directory into each app directory
|
||||
cp -rf $currentdir/ipsc/ /opt/dmrlink/ambe_audio/
|
||||
cp -rf $currentdir/ipsc/ /opt/dmrlink/bridge/
|
||||
cp -rf $currentdir/ipsc/ /opt/dmrlink/confbridge/
|
||||
cp -rf $currentdir/ipsc/ /opt/dmrlink/log/
|
||||
cp -rf $currentdir/ipsc/ /opt/dmrlink/playback/
|
||||
cp -rf $currentdir/ipsc/ /opt/dmrlink/play_group/
|
||||
cp -rf $currentdir/ipsc/ /opt/dmrlink/proxy/
|
||||
cp -rf $currentdir/ipsc/ /opt/dmrlink/rcm/
|
||||
cp -rf $currentdir/ipsc/ /opt/dmrlink/record/
|
||||
cp -rf $currentdir/ipsc/ $PREFIX/confbridge/
|
||||
cp -rf $currentdir/ipsc/ $PREFIX/playback/
|
||||
cp -rf $currentdir/ipsc/ $PREFIX/proxy/
|
||||
|
||||
# Put a copy of the samples together for easy reference
|
||||
cp $currentdir/bridge_rules_SAMPLE.py /opt/dmrlink/samples
|
||||
cp $currentdir/confbridge_rules_SAMPLE.py /opt/dmrlink/samples
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/samples
|
||||
cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/samples
|
||||
cp $currentdir/playback_config_SAMPLE.py /opt/dmrlink/samples
|
||||
cp $currentdir/ambe_audio.cfg /opt/dmrlink/samples
|
||||
#cp $currentdir/bridge_rules_SAMPLE.py /opt/dmrlink/samples
|
||||
cp $currentdir/confbridge_rules_SAMPLE.py $PREFIX/samples
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg $PREFIX/samples
|
||||
#cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/samples
|
||||
cp $currentdir/playback_config_SAMPLE.py $PREFIX/samples
|
||||
#cp $currentdir/ambe_audio.cfg /opt/dmrlink/samples
|
||||
cp $currentdir/sub_acl_SAMPLE.py /opt/dmrlink/samples
|
||||
|
||||
# Put the doc together for easy reference
|
||||
cp -rf $currentdir/documents /opt/dmrlink
|
||||
cp $currentdir/LICENSE.txt /opt/dmrlink/documents
|
||||
cp $currentdir/requirements.txt /opt/dmrlink/documents
|
||||
cp $currentdir/ambe_audio_commands.txt /opt/dmrlink/documents
|
||||
cp -rf $currentdir/documents $PREFIX
|
||||
cp $currentdir/LICENSE.txt $PREFIX/documents
|
||||
cp $currentdir/requirements.txt $PREFIX/documents
|
||||
#cp $currentdir/ambe_audio_commands.txt /opt/dmrlink/documents
|
||||
|
||||
# ambe_audio
|
||||
cp $currentdir/dmrlink.py /opt/dmrlink/ambe_audio/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/ambe_audio/
|
||||
#cp $currentdir/dmrlink.py /opt/dmrlink/ambe_audio/
|
||||
#cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/ambe_audio/
|
||||
#
|
||||
cp $currentdir/ambe_audio.cfg /opt/dmrlink/ambe_audio/
|
||||
cp $currentdir/ambe_audio.py /opt/dmrlink/ambe_audio/
|
||||
cp $currentdir/ambe_audio_commands.txt /opt/dmrlink/ambe_audio/
|
||||
cp $currentdir/template.bin /opt/dmrlink/ambe_audio/
|
||||
#cp $currentdir/ambe_audio.cfg /opt/dmrlink/ambe_audio/
|
||||
#cp $currentdir/ambe_audio.py /opt/dmrlink/ambe_audio/
|
||||
#cp $currentdir/ambe_audio_commands.txt /opt/dmrlink/ambe_audio/
|
||||
#cp $currentdir/template.bin /opt/dmrlink/ambe_audio/
|
||||
|
||||
# Bridge app
|
||||
cp $currentdir/dmrlink.py /opt/dmrlink/bridge/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/bridge/
|
||||
#cp $currentdir/dmrlink.py /opt/dmrlink/bridge/
|
||||
#cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/bridge/
|
||||
#
|
||||
cp $currentdir/bridge.py /opt/dmrlink/bridge/
|
||||
cp $currentdir/bridge_rules_SAMPLE.py /opt/dmrlink/bridge/
|
||||
cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/bridge/
|
||||
cp $currentdir/sub_acl_SAMPLE.py /opt/dmrlink/bridge/
|
||||
#cp $currentdir/bridge.py /opt/dmrlink/bridge/
|
||||
#cp $currentdir/bridge_rules_SAMPLE.py /opt/dmrlink/bridge/
|
||||
#cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/bridge/
|
||||
#cp $currentdir/sub_acl_SAMPLE.py /opt/dmrlink/bridge/
|
||||
|
||||
# ConfBridge app
|
||||
cp $currentdir/dmrlink.py /opt/dmrlink/confbridge/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/confbridge/
|
||||
cp $currentdir/dmrlink.py $PREFIX/confbridge/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg $PREFIX/confbridge/
|
||||
#
|
||||
cp $currentdir/confbridge.py /opt/dmrlink/confbridge/
|
||||
cp $currentdir/confbridge_rules_SAMPLE.py /opt/dmrlink/confbridge/
|
||||
cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/confbridge/
|
||||
cp $currentdir/sub_acl_SAMPLE.py /opt/dmrlink/confbridge/
|
||||
cp $currentdir/confbridge.py $PREFIX/confbridge/
|
||||
cp $currentdir/confbridge_rules_SAMPLE.py $PREFIX/confbridge/
|
||||
#cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/confbridge/
|
||||
cp $currentdir/sub_acl_SAMPLE.py $PREFIX/confbridge/
|
||||
|
||||
# Log app
|
||||
cp $currentdir/dmrlink.py /opt/dmrlink/log/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/log/
|
||||
#cp $currentdir/dmrlink.py /opt/dmrlink/log/
|
||||
#cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/log/
|
||||
#
|
||||
cp $currentdir/log.py /opt/dmrlink/log/
|
||||
#cp $currentdir/log.py /opt/dmrlink/log/
|
||||
|
||||
# Playback (Parrot)
|
||||
cp $currentdir/dmrlink.py /opt/dmrlink/playback/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/playback/
|
||||
cp $currentdir/dmrlink.py $PREFIX/playback/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg $PREFIX/playback/
|
||||
#
|
||||
cp $currentdir/playback.py /opt/dmrlink/playback/
|
||||
cp $currentdir/playback_config_SAMPLE.py /opt/dmrlink/playback/
|
||||
cp $currentdir/playback.py $PREFIX/playback/
|
||||
cp $currentdir/playback_config_SAMPLE.py $PREFIX/playback/
|
||||
|
||||
# Play Group app
|
||||
cp $currentdir/dmrlink.py /opt/dmrlink/play_group/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/play_group/
|
||||
#cp $currentdir/dmrlink.py /opt/dmrlink/play_group/
|
||||
#cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/play_group/
|
||||
#
|
||||
cp $currentdir/play_group.py /opt/dmrlink/play_group/
|
||||
#cp $currentdir/play_group.py /opt/dmrlink/play_group/
|
||||
|
||||
# proxy app
|
||||
cp $currentdir/dmrlink.py /opt/dmrlink/proxy/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/proxy/
|
||||
cp $currentdir/dmrlink.py $PREFIX/proxy/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg $PREFIX/proxy/
|
||||
#
|
||||
cp $currentdir/proxy.py /opt/dmrlink/proxy/
|
||||
cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/proxy/
|
||||
cp $currentdir/sub_acl_SAMPLE.py /opt/dmrlink/proxy/
|
||||
cp $currentdir/proxy.py $PREFIX/proxy/
|
||||
#cp $currentdir/known_bridges_SAMPLE.py $PREFIX/proxy/
|
||||
cp $currentdir/sub_acl_SAMPLE.py $PREFIX/proxy/
|
||||
|
||||
# rcm app
|
||||
cp $currentdir/dmrlink.py /opt/dmrlink/rcm/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/rcm/
|
||||
#cp $currentdir/dmrlink.py /opt/dmrlink/rcm/
|
||||
#cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/rcm/
|
||||
#
|
||||
cp $currentdir/rcm_db_log.py /opt/dmrlink/rcm/
|
||||
cp $currentdir/rcm.py /opt/dmrlink/rcm/
|
||||
#cp $currentdir/rcm_db_log.py /opt/dmrlink/rcm/
|
||||
#cp $currentdir/rcm.py /opt/dmrlink/rcm/
|
||||
|
||||
# record app
|
||||
cp $currentdir/dmrlink.py /opt/dmrlink/record/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/record/
|
||||
#cp $currentdir/dmrlink.py /opt/dmrlink/record/
|
||||
#cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/record/
|
||||
#
|
||||
cp $currentdir/record.py /opt/dmrlink/record/
|
||||
#cp $currentdir/record.py /opt/dmrlink/record/
|
||||
|
||||
|
@ -11,8 +11,8 @@ TGID = 12345
|
||||
# TIMESLOT TO LISTEN FOR GROUP VOICE AND REPEAT
|
||||
# This is a tuple of timeslots to listen to. Note, if there's only
|
||||
# one, you still have to use the parenthesis and comma. Just
|
||||
# deal with it, or make it better. TS1 = 0, TS2 = 1.
|
||||
GROUP_TS = (1,)
|
||||
# deal with it, or make it better. TS1 = 1, TS2 = 2.
|
||||
GROUP_TS = (2,)
|
||||
# ALTERNATE SOURCE SUBSCRIBER ID FOR REPEATED TRANSMISSION
|
||||
# Some folks have radios that don't respond to their own subscriber
|
||||
# IDs. Some just don't want to have the playback come from the same
|
||||
@ -31,5 +31,5 @@ SUB = 12345
|
||||
# TIMESLOT TO LISTEN FOR PRIVATE VOICE AND REPEAT
|
||||
# This is a tuple of timeslots to listen to. Note, if there's only
|
||||
# one, you still have to use the parenthesis and comma. Just
|
||||
# deal with it, or make it better. TS1 = 0, TS2 = 1.
|
||||
PRIVATE_TS = (0,1)
|
||||
# deal with it, or make it better. TS1 = 1, TS2 = 2.
|
||||
PRIVATE_TS = (1,2)
|
||||
|
24
proxy.py
24
proxy.py
@ -72,16 +72,28 @@ __email__ = 'n0mjs@me.com'
|
||||
# are not yet implemented.
|
||||
def build_acl(_sub_acl):
|
||||
try:
|
||||
logger.info('ACL file found, importing entries. This will take about 1.5 seconds per 1 million IDs')
|
||||
acl_file = import_module(_sub_acl)
|
||||
for i, e in enumerate(acl_file.ACL):
|
||||
acl_file.ACL[i] = hex_str_3(acl_file.ACL[i])
|
||||
logger.info('ACL file found and ACL entries imported')
|
||||
ACL_ACTION = acl_file.ACL_ACTION
|
||||
ACL = acl_file.ACL_ACTION
|
||||
sections = acl_file.ACL.split(':')
|
||||
ACL_ACTION = sections[0]
|
||||
entries_str = sections[1]
|
||||
ACL = set()
|
||||
|
||||
for entry in entries_str.split(','):
|
||||
if '-' in entry:
|
||||
start,end = entry.split('-')
|
||||
start,end = int(start), int(end)
|
||||
for id in range(start, end+1):
|
||||
ACL.add(hex_str_3(id))
|
||||
else:
|
||||
id = int(entry)
|
||||
ACL.add(hex_str_3(id))
|
||||
|
||||
logger.info('ACL loaded: action "{}" for {:,} radio IDs'.format(ACL_ACTION, len(ACL)))
|
||||
|
||||
except ImportError:
|
||||
logger.info('ACL file not found or invalid - all subscriber IDs are valid')
|
||||
ACL_ACTION = 'NONE'
|
||||
ACL = []
|
||||
|
||||
# Depending on which type of ACL is used (PERMIT, DENY... or there isn't one)
|
||||
# define a differnet function to be used to check the ACL
|
||||
|
0
requirements.txt
Normal file → Executable file
0
requirements.txt
Normal file → Executable file
12
sub_acl_SAMPLE.py
Normal file → Executable file
12
sub_acl_SAMPLE.py
Normal file → Executable file
@ -1,6 +1,6 @@
|
||||
ACL_ACTION = "DENY" # May be PERMIT|DENY
|
||||
ACL = [
|
||||
1234001,
|
||||
1234002,
|
||||
1234003
|
||||
]
|
||||
# The 'action' May be PERMIT|DENY
|
||||
# Each entry may be a single radio id, or a hypenated range (e.g. 1-2999)
|
||||
# Format:
|
||||
# ACL = 'action:id|start-end|,id|start-end,....'
|
||||
|
||||
ACL = 'DENY:1-2999,16777215'
|
0
systemd/ambe_audio.service
Normal file → Executable file
0
systemd/ambe_audio.service
Normal file → Executable file
0
systemd/bridge.service
Normal file → Executable file
0
systemd/bridge.service
Normal file → Executable file
0
systemd/playback.service
Normal file → Executable file
0
systemd/playback.service
Normal file → Executable file
0
template.bin
Normal file → Executable file
0
template.bin
Normal file → Executable file
Loading…
Reference in New Issue
Block a user