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
80
DO_NOT_README.md
Normal file → Executable file
80
DO_NOT_README.md
Normal file → Executable 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
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,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
|
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
138
confbridge.py
138
confbridge.py
@ -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
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