Compare commits

...

41 Commits

Author SHA1 Message Date
Cort Buffington a55f73d059 RadioID changes 2019-05-09 09:31:47 -05:00
Steve Zingman 1c684486ca
Merge pull request #27 from lorenzocipriani/master
Commented some cp commands on moved files
2019-04-15 09:42:59 -04:00
Lorenzo Cipriani 67fd6d62a5 Updated install script to work on RedHat and Debian derivates 2019-04-14 23:39:23 +00:00
Lorenzo Cipriani 785f44e7e6 Commented some cp commands on moved files 2019-04-14 18:35:58 +01:00
Cort Buffington c023d4a565 Change ID file format to JSON 2019-03-02 13:26:10 -06:00
Cort Buffington ad399792c9 Update dmrlink_config.py 2018-11-26 10:19:35 -06:00
Cort Buffington 198278c288 Update dmrlink_SAMPLE.cfg
because we can't just have one place to get config files that doens't change constantly
2018-10-12 22:58:06 -05:00
Cort Buffington 2520f394ed update 2018-10-08 22:12:56 -05:00
Cort Buffington bc59c75eb0 Added IPv6 Support
Someone made a comment… I added it.
2018-06-15 12:14:10 -05:00
Cort Buffington 01a3fff754
Fixing DMR-MARC database changes
....again... and again...
2018-04-16 09:09:47 -05:00
Cort Buffington 2e1a4c3a58 Revert "Accommodate DMR-MARC JSON only database changes"
This reverts commit 59a59e4100.
2018-04-16 08:46:42 -05:00
Cort Buffington 59a59e4100 Accommodate DMR-MARC JSON only database changes
Talkgroup files are samples – pick one format or the other if you want
those aliases. this changes the type of dump you do with DMR-MARC and
the way you save files locally. CSV still works, but DMR-MARC will stop
supporting it!!! DO NOT mix .csv filenames and .json downloads. The
extension must match the file data type.
2018-03-16 20:29:46 -05:00
Cort Buffington 81c3467ec0 Clean up formatting 2018-03-12 21:29:05 -05:00
Cort Buffington 25d6bc08d0 cleaned up superfluous spaces 2018-03-12 21:26:33 -05:00
Cort Buffington e8311b1f54 Last commit was MESSED UP! 2018-03-12 19:32:20 -05:00
Cort Buffington cd11397416 Improve IPSC/DMR Re-Write 2018-03-11 17:37:22 -05:00
Cort Buffington cdd65d8edf Added support for IPSC "Trunks"
A “TRUNK” is a variant on IPSC unique to this project. They are used to
pass traffic between DMRlink instances. They do no contention handling
regarding TS/TGID, they take any traffic sent to them.
2018-02-05 12:27:44 -06:00
Cort Buffington 2b63b5c111 Fix Bug where confbridge.py would not start if there was not an ACL defined. 2018-02-05 11:20:42 -06:00
Cort Buffington 3fc0bdc63d
Update playback_config_SAMPLE.py 2018-01-26 07:49:35 -06:00
Cort Buffington f5bc547d4d
Update DO_NOT_README.md 2018-01-08 13:29:34 -06:00
Cort Buffington 0acc6042e8
Update DO_NOT_README.md 2018-01-08 13:29:02 -06:00
Cort Buffington b4ab2d31a3
Update DO_NOT_README.md 2018-01-08 13:27:55 -06:00
Cort Buffington 5950240787
Update DO_NOT_README.md 2018-01-08 13:24:37 -06:00
Steve Zingman 63611f2c6c
Fix typo 2017-12-13 12:42:08 -05:00
Steve Zingman eb2ffe4ecb
Create README.MD 2017-12-09 07:58:53 -05:00
Cort Buffington bf699dcfbc Move retired file 2017-08-29 07:42:34 -05:00
Steve N4IRS c6a543527f Move retired applications to a seperate directory 2017-08-25 15:27:53 -04:00
Steve Zingman 3279aeb527 bitstring and bitarray moved to dmr_utils setup.py 2017-08-22 09:24:40 -04:00
Cort Buffington f216300539 Logic error where RESET TGID caused unintended action 2017-07-30 11:02:37 -05:00
Cort Buffington 8e858e48a2 Missed building hex values for RESET TGID Addition (see last) 2017-07-30 10:52:48 -05:00
Cort Buffington c16d549e94 Added additional way to reset running timers
Added a list of RESET TGIDs used, additionally, to reset running
timers. All previous actions still work the same way, this is only
ANOTHER way to reset timers.
2017-07-30 10:29:52 -05:00
Cort Buffington ae9f71d715 Merge branch 'socket-reporting' 2017-06-29 13:03:33 -05:00
Cort Buffington 4ac862b93c update ACL format and action to add "ranges" 2017-06-29 13:02:53 -05:00
Cort Buffington 8fbd7ccf33 more formatting.... UGH! 2017-06-28 16:35:51 -05:00
Cort Buffington b8d1449d2f format fix from last update. 2017-06-28 16:32:23 -05:00
Cort Buffington c8804d7231 Network logging format update
Make it a format that webtables.py can more easily re-format/manipulate.
2017-06-28 16:30:15 -05:00
Cort Buffington 7fde9d1ce8 Fixing syntax problems in network logging. Incremental 2017-06-28 16:22:38 -05:00
Cort Buffington d1143ddb7c bug fix - last update 2017-06-28 16:11:23 -05:00
Cort Buffington 0079ad1baa Change Network Logging Format 2017-06-28 16:07:29 -05:00
Cort Buffington 547d6e23ed Merge branch 'master' into socket-reporting 2017-06-28 16:03:42 -05:00
Cort Buffington 62fc209b4f fix TS parsing during trigger action 2017-06-23 16:04:45 -05:00
39 changed files with 314 additions and 222 deletions

0
.gitignore vendored Normal file → Executable file
View File

80
DO_NOT_README.md Normal file → Executable file
View File

@ -15,19 +15,19 @@ Each peer will send keep-alives to each other peer in the IPSC network at an int
The following sections of this document will include various packet types. This is a list of currently known types and their meanings. Note: The names are arbitrarily chosen with the intention of being descriptive, and each is defined by what they've been "observed" to do in the wild.
CALL_CONFIRMATION = 0x05 Confirmation FROM the recipient of a confirmed call.
CALL_MON_ORIGIN = 0x61 Sent to Repeater Call Monitor Peers from repeater originating a call
CALL_MON_ORIGIN = 0x61 Sent to Repeater Call Monitor Peers from repeater originating a call
CALL_MON_RPT = 0x62 Sent to Repeater Call Monitor Peers from all repeaters repeating a call
CALL_MON_NACK = 0x63 Sent to Repeater Call Monitor Peers from repeaters that cannot transmit a call (ie. ID in progress)
XCMP_XNL = 0x70 Control protocol messages
GROUP_VOICE = 0x80 This is a group voice call
XCMP_XNL = 0x70 Control protocol messages
GROUP_VOICE = 0x80 This is a group voice call
PVT_VOICE = 0x81 This is a private voice call
GROUP_DATA = 0x83 This is a group data call
PVT_DATA = 0x84 This is a private data call
GROUP_DATA = 0x83 This is a group data call
PVT_DATA = 0x84 This is a private data call
RPT_WAKE_UP = 0x85 Wakes up all repeaters on the IPSC
MASTER_REG_REQ = 0x90 Request registration with master (from peer, to master)
MASTER_REG_REQ = 0x90 Request registration with master (from peer, to master)
MASTER_REG_REPLY = 0x91 Master registration request reply (from master, to peer)
PEER_LIST_REQ = 0x92 Request peer list from master
PEER_LIST_REPLY = 0x93 Master peer list reply
PEER_LIST_REQ = 0x92 Request peer list from master
PEER_LIST_REPLY = 0x93 Master peer list reply
PEER_REG_REQ = 0x94 Peer registration request
PEER_REG_REPLY = 0x95 Peer registration response
MASTER_ALIVE_REQ = 0x96 Master keep alive request (to master)
@ -54,8 +54,8 @@ There are various states, timers and counters associated with each. When peers o
*COMMUNICATION WITH MASTER:*
The following illustrates the communication that a peer (us, for example) has with the master. The peer must register, then send keep-alives at an arbitrary interval (usually 5 - 30 seconds). If more than some arbitrary number of keep-alives are missed, we should return to the beginning and attempt to register again -- but do NOT elimiate the peers list, as peers may still be active. The only additional communcation with the master is if the master sends an unsolicited peer list. In this case, we should update our peer list as appropriate and continue.
+-----------------+
|Send Registration|
+-----------------+
|Send Registration|
+---------------------------->|Request To Master|<-------------+
| +--------+--------+ |
| | |
@ -72,32 +72,32 @@ The following illustrates the communication that a peer (us, for example) has wi
| | Counter +---->| Alive Request | +------------>| > 1 ? |
| +-------------+ +-------+--------+ +------+------+
| ^ | ^ | YES
YES| | NO v | v
+---+---------+--+ +------------+ | +-----------------+
| Is The Missed | |Wait FW Open| | |Request Peer List|
| Keep-Alive | | Timer | | | From Master |<-----+
|Count Exceeded ?| +-----+------+ | +-------+---------+ |
+----------------+ | | | |
^ v | v |
| +--------------+ ++-------------+ +---------+ |
| NO |Did The Master| YES |Set Keep Alive| |Peer List| NO |
+-------------+ Respond ? +---->| Counter To 0 | |Received?+----------+
+--------------+ +--------------+ +---------+
YES| | NO v | v
+---+---------+--+ +------------+ | +-----------------+
| Is The Missed | |Wait FW Open| | |Request Peer List|
| Keep-Alive | | Timer | | | From Master |<-----+
|Count Exceeded ?| +-----+------+ | +-------+---------+ |
+----------------+ | | | |
^ v | v |
| +--------------+ ++-------------+ +---------+ |
| NO |Did The Master| YES |Set Keep Alive| |Peer List| NO |
+---------+ Respond ? +---->| Counter To 0 | |Received?+----------+
+--------------+ +--------------+ +---------+
*COMMUNICATION WITH PEERS:*
Once we have registered with the master, it will send a peer list update to any existing peers. Those peers will **immediately** respond by sending peer registrations to us, and then keep alives once we answer. We should send responses to any such requests as long as we have the peer in our own peer list -- which means we may miss one while waiting for receipt of our own peer list from the master. Even though we receive registration requests and keep-alives from the peers, we should send the same to them, even though this is redundant, it is how we ensure that firewall UDP sessions remain open. A bit wonky, but elegant. For example, a peer may not have a firewall, so it only sends keep-alives every 30 seconds, but we may need to every 5; which we achieve by sending our own keep-alives based on our own timer. The diagram only shows the action for the *initial* peer list reply from the master. Unsolicited peer lists from the master should update the list, and take appropriate action: De-register peers not in the new list, or begin registration for new peers.
+-----------------+ +-------------+
|Recieve Peer List| |Received Peer|
| From Master | |Leave Notice?|
+------+----------+ +------+------+
| |
v FOR EACH PEER |
+----------------------+ v
|Send Peer Registration| +-----------+
+------------------->| Request |<-----------+ |Remove Peer|
| +----------+-----------+ | | From List |
| | | +-----------+
+-----------------+ +-------------+
|Recieve Peer List| |Received Peer|
| From Master | |Leave Notice?|
+------+----------+ +------+------+
| |
v FOR EACH PEER |
+----------------------+ v
|Send Peer Registration| +-----------+
+------------------->| Request |<-----------+ |Remove Peer|
| +----------+-----------+ | | From List |
| | | +-----------+
| v |
| +---------------------+ +------+------+
| +---------+ |Registration Response| NO |Wait Firewall|
@ -108,18 +108,18 @@ Once we have registered with the master, it will send a peer list update to any
| | | +----------+
| | +--------------->|Send Peer |
| | +-------->|Keep Alive|
| | | +----+-----+
|YES |NO | |
| | | +----+-----+
|YES |NO | |
+---+---------+--+ +-----+------+ |
| Keep Alive | | Set Missed | |
| Count Exceeded?| |Counter to 0| |
+----------------+ +------------+ |
NO ^ ^ YES |
| | v
+---+------+----+ +-------------+
| Peer Keep | |Wait Firewall|
|Alive Received?|<------+ Open Timer |
+---------------+ +-------------+
NO ^ ^ YES |
| | v
+---+------+----+ +-------------+
| Peer Keep | |Wait Firewall|
|Alive Received?|<------+ Open Timer |
+---------------+ +-------------+
**PACKET FORMATS:**

0
LICENSE.txt Normal file → Executable file
View File

0
README.md Normal file → Executable file
View File

5
Retired/README.MD Executable file
View 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
View File

View File

View File

@ -127,17 +127,29 @@ 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
global allow_sub

View File

View File

0
template.py → Retired/template.py Normal file → Executable file
View File

View 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]
@ -253,11 +266,13 @@ class confbridgeIPSC(IPSC):
for _target in BRIDGES[_bridge]:
if _target['SYSTEM'] != self._system:
if _target['ACTIVE']:
if _target['ACTIVE']:
_target_status = systems[_target['SYSTEM']].STATUS
_target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']]
# 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
@ -265,39 +280,42 @@ class confbridgeIPSC(IPSC):
# From the same group as the last RX from this IPSC, but from a different subscriber, and it has been less than TS Clear Time
# 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['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']))
continue
if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((now - _target_status[_target['TS']]['TX_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 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']]['TX_TGID']))
continue
if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((now - _target_status[_target['TS']]['RX_TIME']) < TS_CLEAR_TIME):
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
self._logger.info('(%s) Call not bridged to TGID%s, matching call already active on target: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID']))
continue
if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_src_sub != _target_status[_target['TS']]['TX_SRC_SUB']) and ((now - _target_status[_target['TS']]['TX_TIME']) < TS_CLEAR_TIME):
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
self._logger.info('(%s) Call not bridged for subscriber %s, call bridge in progress on target: IPSC: %s, TS: %s, TGID: %s SUB: %s', self._system, int_id(_src_sub), _target['SYSTEM'], _target['TGID'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_SRC_SUB']))
continue
#
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']))
continue
if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((now - _target_status[_target['TS']]['TX_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 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']]['TX_TGID']))
continue
if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((now - _target_status[_target['TS']]['RX_TIME']) < TS_CLEAR_TIME):
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
self._logger.info('(%s) Call not bridged to TGID%s, matching call already active on target: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID']))
continue
if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_src_sub != _target_status[_target['TS']]['TX_SRC_SUB']) and ((now - _target_status[_target['TS']]['TX_TIME']) < TS_CLEAR_TIME):
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
self._logger.info('(%s) Call not bridged for subscriber %s, call bridge in progress on target: IPSC: %s, TS: %s, TGID: %s SUB: %s', self._system, int_id(_src_sub), _target['SYSTEM'], _target['TGID'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_SRC_SUB']))
continue
#
# END CONTENTION HANDLING
#
#
# BEGIN FRAME FORWARDING
#
# Make a copy of the payload
_tmp_data = _data
# 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 destination Group ID
_tmp_data = _tmp_data.replace(_dst_group, _target['TGID'])
# 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 + DST GROUP in IPSC Headers:
_tmp_data = _tmp_data.replace(_src_sub + _dst_group, _src_sub + _target['TGID'], 1)
# 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])
if _target['TS'] == 1:
@ -306,7 +324,7 @@ class confbridgeIPSC(IPSC):
_call_info |= 1 << 5
_call_info = chr(_call_info)
_tmp_data = _tmp_data[:17] + _call_info + _tmp_data[18:]
# Re-Write DMR timeslot value
# Determine if the slot is present, so we can translate if need be
if _burst_data_type == BURST_DATA_TYPE['SLOT1_VOICE'] or _burst_data_type == BURST_DATA_TYPE['SLOT2_VOICE']:
@ -326,8 +344,7 @@ 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,36 +390,38 @@ 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 _system['ACTIVE'] == False:
_system['ACTIVE'] = True
self._logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE'])
# Cancel the timer if we've enabled an "OFF" type timeout
if _system['TO_TYPE'] == 'OFF':
_system['TIMER'] = now
self._logger.info('(%s) Bridge: %s set to "OFF" with an on timer rule: timeout timer cancelled', self._system, _bridge)
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'])
# Cancel the timer if we've enabled an "OFF" type timeout
if _system['TO_TYPE'] == 'OFF':
_system['TIMER'] = now
self._logger.info('(%s) Bridge: %s set to "OFF" with an on timer rule: timeout timer cancelled', self._system, _bridge)
# Reset the timer for the rule
if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON':
_system['TIMER'] = now + _system['TIMEOUT']
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 _system['ACTIVE'] == True:
_system['ACTIVE'] = False
self._logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE'])
# Cancel the timer if we've enabled an "ON" type timeout
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
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'])
# Cancel the timer if we've enabled an "ON" type timeout
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 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,7 +498,8 @@ if __name__ == '__main__':
# Build the routing rules and other configuration
CONFIG_DICT = make_bridge_config('confbridge_rules')
BRIDGE_CONF = CONFIG_DICT['BRIDGE_CONF']
BRIDGES = CONFIG_DICT['BRIDGES']
TRUNKS = CONFIG_DICT['TRUNKS']
BRIDGES = CONFIG_DICT['BRIDGES']
# Build the Access Control List
ACL = build_acl('sub_acl')

22
confbridge_rules_SAMPLE.py Normal file → Executable file
View 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
View 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
View File

0
documents/internal_data_decode.txt Normal file → Executable file
View File

0
documents/voice_burst_decoding.txt Normal file → Executable file
View File

0
documents/voice_packets.txt Normal file → Executable file
View File

0
ipsc/.gitignore vendored Normal file → Executable file
View File

0
ipsc/__init__.py Normal file → Executable file
View File

View 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
View File

0
ipsc/ipsc_mask.py Normal file → Executable file
View File

0
ipsc/reporting_const.py Normal file → Executable file
View File

View File

@ -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/

View File

@ -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)

View File

@ -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
View File

12
sub_acl_SAMPLE.py Normal file → Executable file
View 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
View File

0
systemd/bridge.service Normal file → Executable file
View File

0
systemd/playback.service Normal file → Executable file
View File

0
template.bin Normal file → Executable file
View File