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. 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_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_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) 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 XCMP_XNL = 0x70 Control protocol messages
GROUP_VOICE = 0x80 This is a group voice call GROUP_VOICE = 0x80 This is a group voice call
PVT_VOICE = 0x81 This is a private voice call PVT_VOICE = 0x81 This is a private voice call
GROUP_DATA = 0x83 This is a group data call GROUP_DATA = 0x83 This is a group data call
PVT_DATA = 0x84 This is a private data call PVT_DATA = 0x84 This is a private data call
RPT_WAKE_UP = 0x85 Wakes up all repeaters on the IPSC 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) MASTER_REG_REPLY = 0x91 Master registration request reply (from master, to peer)
PEER_LIST_REQ = 0x92 Request peer list from master PEER_LIST_REQ = 0x92 Request peer list from master
PEER_LIST_REPLY = 0x93 Master peer list reply PEER_LIST_REPLY = 0x93 Master peer list reply
PEER_REG_REQ = 0x94 Peer registration request PEER_REG_REQ = 0x94 Peer registration request
PEER_REG_REPLY = 0x95 Peer registration response PEER_REG_REPLY = 0x95 Peer registration response
MASTER_ALIVE_REQ = 0x96 Master keep alive request (to master) 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:* *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. 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|<-------------+ +---------------------------->|Request To Master|<-------------+
| +--------+--------+ | | +--------+--------+ |
| | | | | |
@ -72,32 +72,32 @@ The following illustrates the communication that a peer (us, for example) has wi
| | Counter +---->| Alive Request | +------------>| > 1 ? | | | Counter +---->| Alive Request | +------------>| > 1 ? |
| +-------------+ +-------+--------+ +------+------+ | +-------------+ +-------+--------+ +------+------+
| ^ | ^ | YES | ^ | ^ | YES
YES| | NO v | v YES| | NO v | v
+---+---------+--+ +------------+ | +-----------------+ +---+---------+--+ +------------+ | +-----------------+
| Is The Missed | |Wait FW Open| | |Request Peer List| | Is The Missed | |Wait FW Open| | |Request Peer List|
| Keep-Alive | | Timer | | | From Master |<-----+ | Keep-Alive | | Timer | | | From Master |<-----+
|Count Exceeded ?| +-----+------+ | +-------+---------+ | |Count Exceeded ?| +-----+------+ | +-------+---------+ |
+----------------+ | | | | +----------------+ | | | |
^ v | v | ^ v | v |
| +--------------+ ++-------------+ +---------+ | | +--------------+ ++-------------+ +---------+ |
| NO |Did The Master| YES |Set Keep Alive| |Peer List| NO | | NO |Did The Master| YES |Set Keep Alive| |Peer List| NO |
+-------------+ Respond ? +---->| Counter To 0 | |Received?+----------+ +---------+ Respond ? +---->| Counter To 0 | |Received?+----------+
+--------------+ +--------------+ +---------+ +--------------+ +--------------+ +---------+
*COMMUNICATION WITH PEERS:* *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. 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| |Recieve Peer List| |Received Peer|
| From Master | |Leave Notice?| | From Master | |Leave Notice?|
+------+----------+ +------+------+ +------+----------+ +------+------+
| | | |
v FOR EACH PEER | v FOR EACH PEER |
+----------------------+ v +----------------------+ v
|Send Peer Registration| +-----------+ |Send Peer Registration| +-----------+
+------------------->| Request |<-----------+ |Remove Peer| +------------------->| Request |<-----------+ |Remove Peer|
| +----------+-----------+ | | From List | | +----------+-----------+ | | From List |
| | | +-----------+ | | | +-----------+
| v | | v |
| +---------------------+ +------+------+ | +---------------------+ +------+------+
| +---------+ |Registration Response| NO |Wait Firewall| | +---------+ |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 | | | +--------------->|Send Peer |
| | +-------->|Keep Alive| | | +-------->|Keep Alive|
| | | +----+-----+ | | | +----+-----+
|YES |NO | | |YES |NO | |
+---+---------+--+ +-----+------+ | +---+---------+--+ +-----+------+ |
| Keep Alive | | Set Missed | | | Keep Alive | | Set Missed | |
| Count Exceeded?| |Counter to 0| | | Count Exceeded?| |Counter to 0| |
+----------------+ +------------+ | +----------------+ +------------+ |
NO ^ ^ YES | NO ^ ^ YES |
| | v | | v
+---+------+----+ +-------------+ +---+------+----+ +-------------+
| Peer Keep | |Wait Firewall| | Peer Keep | |Wait Firewall|
|Alive Received?|<------+ Open Timer | |Alive Received?|<------+ Open Timer |
+---------------+ +-------------+ +---------------+ +-------------+
**PACKET FORMATS:** **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,16 +127,28 @@ def build_bridges(_known_bridges):
# are not yet implemented. # are not yet implemented.
def build_acl(_sub_acl): def build_acl(_sub_acl):
try: 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) acl_file = import_module(_sub_acl)
for i, e in enumerate(acl_file.ACL): sections = acl_file.ACL.split(':')
acl_file.ACL[i] = hex_str_3(acl_file.ACL[i]) ACL_ACTION = sections[0]
logger.info('ACL file found and ACL entries imported') entries_str = sections[1]
ACL_ACTION = acl_file.ACL_ACTION ACL = set()
ACL = acl_file.ACL
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: except ImportError:
logger.info('ACL file not found or invalid - all subscriber IDs are valid') logger.info('ACL file not found or invalid - all subscriber IDs are valid')
ACL_ACTION = 'NONE' ACL_ACTION = 'NONE'
ACL = []
# Depending on which type of ACL is used (PERMIT, DENY... or there isn't one) # 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 # define a differnet function to be used to check the ACL

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]) _system['ON'][i] = hex_str_3(_system['ON'][i])
for i, e in enumerate(_system['OFF']): for i, e in enumerate(_system['OFF']):
_system['OFF'][i] = hex_str_3(_system['OFF'][i]) _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['TIMEOUT'] = _system['TIMEOUT']*60
_system['TIMER'] = time() _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 # 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 # Global action is to allow or deny them. Multiple lists with different actions and ranges
# are not yet implemented. # are not yet implemented.
def build_acl(_sub_acl): def build_acl(_sub_acl):
ACL = set()
try: 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) acl_file = import_module(_sub_acl)
for i, e in enumerate(acl_file.ACL): sections = acl_file.ACL.split(':')
acl_file.ACL[i] = hex_str_3(acl_file.ACL[i]) ACL_ACTION = sections[0]
logger.info('ACL file found and ACL entries imported') entries_str = sections[1]
ACL_ACTION = acl_file.ACL_ACTION for entry in entries_str.split(','):
ACL = acl_file.ACL_ACTION 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: except ImportError:
logger.info('ACL file not found or invalid - all subscriber IDs are valid') logger.info('ACL file not found or invalid - all subscriber IDs are valid')
ACL_ACTION = 'NONE' ACL_ACTION = 'NONE'
ACL = []
# Depending on which type of ACL is used (PERMIT, DENY... or there isn't one) # 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 # define a differnet function to be used to check the ACL
@ -240,7 +253,7 @@ class confbridgeIPSC(IPSC):
return return
# Process the packet # 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) _burst_data_type = _data[30] # Determine the type of voice packet this is (see top of file for possible types)
_seq_id = _data[5] _seq_id = _data[5]
@ -259,6 +272,8 @@ class confbridgeIPSC(IPSC):
# BEGIN CONTENTION HANDLING # 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: # The rules for each of the 4 "ifs" below are listed here for readability. The Frame To Send is:
# From a different group than last RX from this IPSC, but it has been less than Group Hangtime # From a different group than last RX from this IPSC, but it has been less than Group Hangtime
# From a different group than last TX to this IPSC, but it has been less than Group Hangtime # From a different group than last TX to this IPSC, but it has been less than Group Hangtime
@ -266,22 +281,23 @@ class confbridgeIPSC(IPSC):
# From the same group as the last TX to this IPSC, but from a different subscriber, and it has been less than TS Clear Time # 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 # 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 _target not in TRUNKS:
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']: if ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((now - _target_status[_target['TS']]['RX_TIME']) < _target_system['LOCAL']['GROUP_HANGTIME'])):
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'])) if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
continue 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']))
if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((now - _target_status[_target['TS']]['TX_TIME']) < _target_system['LOCAL']['GROUP_HANGTIME'])): continue
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']: if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((now - _target_status[_target['TS']]['TX_TIME']) < _target_system['LOCAL']['GROUP_HANGTIME'])):
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'])) if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
continue 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']))
if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((now - _target_status[_target['TS']]['RX_TIME']) < TS_CLEAR_TIME): continue
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']: if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((now - _target_status[_target['TS']]['RX_TIME']) < TS_CLEAR_TIME):
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'])) if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
continue 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']))
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): continue
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']: 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):
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'])) if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
continue 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 # END CONTENTION HANDLING
# #
@ -291,12 +307,14 @@ class confbridgeIPSC(IPSC):
# #
# Make a copy of the payload # Make a copy of the payload
_tmp_data = _data _tmp_data = _data
# Re-Write the PEER ID in the IPSC Header:
_tmp_data = _tmp_data.replace(_peerid, _target_system['LOCAL']['RADIO_ID'], 1)
# Re-Write the IPSC SRC to match the target network's ID # Re-Write the IPSC SRC + DST GROUP in IPSC Headers:
_tmp_data = _tmp_data.replace(_peerid, _target_system['LOCAL']['RADIO_ID']) _tmp_data = _tmp_data.replace(_src_sub + _dst_group, _src_sub + _target['TGID'], 1)
# Re-Write the destination Group ID # Re-Write the DST GROUP + IPSC SRC in DMR LC (Header, Terminator and Voice Burst E):
_tmp_data = _tmp_data.replace(_dst_group, _target['TGID']) _tmp_data = _tmp_data.replace(_dst_group + _src_sub, _target['TGID'] + _src_sub, 1)
# Re-Write IPSC timeslot value # Re-Write IPSC timeslot value
_call_info = int_id(_data[17:18]) _call_info = int_id(_data[17:18])
@ -327,7 +345,6 @@ class confbridgeIPSC(IPSC):
# END FRAME FORWARDING # END FRAME FORWARDING
# #
# Set values for the contention handler to test next time there is a frame to forward # 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_TGID'] = _target['TGID']
_target_status[_target['TS']]['TX_TIME'] = now _target_status[_target['TS']]['TX_TIME'] = now
@ -352,7 +369,7 @@ class confbridgeIPSC(IPSC):
self.call_start = now 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)) 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': 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 # Action happens on un-key
if _burst_data_type == BURST_DATA_TYPE['VOICE_TERM']: if _burst_data_type == BURST_DATA_TYPE['VOICE_TERM']:
@ -360,11 +377,11 @@ class confbridgeIPSC(IPSC):
self.call_duration = now - self.call_start 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) 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': 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: 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)) 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': 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 # Iterate the rules dictionary
@ -373,36 +390,38 @@ class confbridgeIPSC(IPSC):
if _system['SYSTEM'] == self._system: if _system['SYSTEM'] == self._system:
# TGID matches an ACTIVATION trigger # 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 # Set the matching rule as ACTIVE
if _system['ACTIVE'] == False: if _dst_group in _system['ON']:
_system['ACTIVE'] = True if _system['ACTIVE'] == False:
self._logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE']) _system['ACTIVE'] = True
# Cancel the timer if we've enabled an "OFF" type timeout self._logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE'])
if _system['TO_TYPE'] == 'OFF': # Cancel the timer if we've enabled an "OFF" type timeout
_system['TIMER'] = now if _system['TO_TYPE'] == 'OFF':
self._logger.info('(%s) Bridge: %s set to "OFF" with an on timer rule: timeout timer cancelled', self._system, _bridge) _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 # Reset the timer for the rule
if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON': if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON':
_system['TIMER'] = now + _system['TIMEOUT'] _system['TIMER'] = now + _system['TIMEOUT']
self._logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - now) self._logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - now)
# TGID matches an DE-ACTIVATION trigger # 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 # Set the matching rule as ACTIVE
if _system['ACTIVE'] == True: if _dst_group in _system['OFF']:
_system['ACTIVE'] = False if _system['ACTIVE'] == True:
self._logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE']) _system['ACTIVE'] = False
# Cancel the timer if we've enabled an "ON" type timeout self._logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE'])
if _system['TO_TYPE'] == 'ON': # Cancel the timer if we've enabled an "ON" type timeout
_system['TIMER'] = now if _system['TO_TYPE'] == 'ON':
self._logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge) _system['TIMER'] = now
# Reset tge timer for the rule 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': if _system['ACTIVE'] == False and _system['TO_TYPE'] == 'OFF':
_system['TIMER'] = now + _system['TIMEOUT'] _system['TIMER'] = now + _system['TIMEOUT']
self._logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - now) 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 # 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 _system['TIMER'] = now
self._logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge) 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 # Build the routing rules and other configuration
CONFIG_DICT = make_bridge_config('confbridge_rules') CONFIG_DICT = make_bridge_config('confbridge_rules')
BRIDGE_CONF = CONFIG_DICT['BRIDGE_CONF'] BRIDGE_CONF = CONFIG_DICT['BRIDGE_CONF']
BRIDGES = CONFIG_DICT['BRIDGES'] TRUNKS = CONFIG_DICT['TRUNKS']
BRIDGES = CONFIG_DICT['BRIDGES']
# Build the Access Control List # Build the Access Control List
ACL = build_acl('sub_acl') 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 * 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 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': [] ". 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 * 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 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 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, '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 = { BRIDGES = {
'WORLDWIDE': [ 'WORLDWIDE': [
{'SYSTEM': 'MASTER-1', 'TS': 1, 'TGID': 1, '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]}, {'SYSTEM': 'CLIENT-1', 'TS': 1, 'TGID': 3100, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'ON', 'ON': [2,], 'OFF': [9,10], 'RESET': []}
], ],
'ENGLISH': [ 'ENGLISH': [
{'SYSTEM': 'MASTER-1', '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]}, {'SYSTEM': 'CLIENT-2', 'TS': 1, 'TGID': 13, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [3,], 'OFF': [8,10], 'RESET': []}
], ],
'STATEWIDE': [ 'STATEWIDE': [
{'SYSTEM': 'MASTER-1', '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]}, {'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 TRY_DOWNLOAD: True
LOCAL_FILE: False LOCAL_FILE: False
PATH: ./ PATH: ./
PEER_FILE: peer_ids.csv PEER_FILE: peer_ids.json
SUBSCRIBER_FILE: subscriber_ids.csv SUBSCRIBER_FILE: subscriber_ids.json
TGID_FILE: talkgroup_ids.csv TGID_FILE: talkgroup_ids.json
PEER_URL: http://www.dmr-marc.net/cgi-bin/trbo-database/datadump.cgi?table=repeaters&format=csv&header=0 PEER_URL: https://www.radioid.net/static/rptrs.json
SUBSCRIBER_URL: http://www.dmr-marc.net/cgi-bin/trbo-database/datadump.cgi?table=users&format=csv&header=0 SUBSCRIBER_URL: https://www.radioid.net/static/users.json
STALE_DAYS: 7 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 #!/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 # 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 # it under the terms of the GNU General Public License as published by
@ -21,16 +21,32 @@
import ConfigParser import ConfigParser
import sys 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. # Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = 'Cortney T. Buffington, N0MJS' __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' __license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS' __maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com' __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): def build_config(_config_file):
config = ConfigParser.ConfigParser() 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 # 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'), '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'), 'PORT': config.getint(section, 'PORT'),
'ALIVE_TIMER': config.getint(section, 'ALIVE_TIMER'), 'ALIVE_TIMER': config.getint(section, 'ALIVE_TIMER'),
'MAX_MISSED': config.getint(section, 'MAX_MISSED'), 'MAX_MISSED': config.getint(section, 'MAX_MISSED'),
@ -144,7 +160,7 @@ def build_config(_config_file):
}) })
if not CONFIG['SYSTEMS'][section]['LOCAL']['MASTER_PEER']: if not CONFIG['SYSTEMS'][section]['LOCAL']['MASTER_PEER']:
CONFIG['SYSTEMS'][section]['MASTER'].update({ 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') '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 # 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: 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)) 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 #! /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 "" echo ""
@ -13,17 +15,44 @@ echo ""
################################################# #################################################
# Install the required support programs # 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 distro=$(lsb_release -i | awk -F":" '{ gsub(/^[ \t]+/, "", $2); print $2 }')
git clone https://github.com/n0mjs710/dmr_utils.git release=$(lsb_release -r | awk -F":" '{ gsub(/^[ \t]+/, "", $2); print $2 }')
cd dmr_utils/ echo "Current Linux distribution is: $distro $release"
pip install .
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" echo "Required programs installed, continuing"
@ -32,19 +61,13 @@ echo "Required programs installed, continuing"
# The needed files are copied to /opt/dmrlink # The needed files are copied to /opt/dmrlink
# Make needed directories # Make needed directories
mkdir -p /opt/dmrlink/ambe_audio/ mkdir -p $PREFIX/confbridge/
mkdir -p /opt/dmrlink/bridge/ mkdir -p $PREFIX/playback/
mkdir -p /opt/dmrlink/confbridge/ mkdir -p $PREFIX/proxy/
mkdir -p /opt/dmrlink/log/ mkdir -p $PREFIX/samples
mkdir -p /opt/dmrlink/playback/ mkdir -p /var/log/dmrlink
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
cd /opt/dmrlink cd $PREFIX
# Put common files in /opt/dmrlink # Put common files in /opt/dmrlink
# cp $currentdir/peer_ids.csv /opt/dmrlink # cp $currentdir/peer_ids.csv /opt/dmrlink
@ -52,95 +75,89 @@ cd /opt/dmrlink
# cp $currentdir/talkgroup_ids.csv /opt/dmrlink # cp $currentdir/talkgroup_ids.csv /opt/dmrlink
# Copy ipsc directory into each app directory # Copy ipsc directory into each app directory
cp -rf $currentdir/ipsc/ /opt/dmrlink/ambe_audio/ cp -rf $currentdir/ipsc/ $PREFIX/confbridge/
cp -rf $currentdir/ipsc/ /opt/dmrlink/bridge/ cp -rf $currentdir/ipsc/ $PREFIX/playback/
cp -rf $currentdir/ipsc/ /opt/dmrlink/confbridge/ cp -rf $currentdir/ipsc/ $PREFIX/proxy/
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/
# Put a copy of the samples together for easy reference # Put a copy of the samples together for easy reference
cp $currentdir/bridge_rules_SAMPLE.py /opt/dmrlink/samples #cp $currentdir/bridge_rules_SAMPLE.py /opt/dmrlink/samples
cp $currentdir/confbridge_rules_SAMPLE.py /opt/dmrlink/samples cp $currentdir/confbridge_rules_SAMPLE.py $PREFIX/samples
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/samples cp $currentdir/dmrlink_SAMPLE.cfg $PREFIX/samples
cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/samples #cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/samples
cp $currentdir/playback_config_SAMPLE.py /opt/dmrlink/samples cp $currentdir/playback_config_SAMPLE.py $PREFIX/samples
cp $currentdir/ambe_audio.cfg /opt/dmrlink/samples #cp $currentdir/ambe_audio.cfg /opt/dmrlink/samples
cp $currentdir/sub_acl_SAMPLE.py /opt/dmrlink/samples cp $currentdir/sub_acl_SAMPLE.py /opt/dmrlink/samples
# Put the doc together for easy reference # Put the doc together for easy reference
cp -rf $currentdir/documents /opt/dmrlink cp -rf $currentdir/documents $PREFIX
cp $currentdir/LICENSE.txt /opt/dmrlink/documents cp $currentdir/LICENSE.txt $PREFIX/documents
cp $currentdir/requirements.txt /opt/dmrlink/documents cp $currentdir/requirements.txt $PREFIX/documents
cp $currentdir/ambe_audio_commands.txt /opt/dmrlink/documents #cp $currentdir/ambe_audio_commands.txt /opt/dmrlink/documents
# ambe_audio # ambe_audio
cp $currentdir/dmrlink.py /opt/dmrlink/ambe_audio/ #cp $currentdir/dmrlink.py /opt/dmrlink/ambe_audio/
cp $currentdir/dmrlink_SAMPLE.cfg /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.cfg /opt/dmrlink/ambe_audio/
cp $currentdir/ambe_audio.py /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/ambe_audio_commands.txt /opt/dmrlink/ambe_audio/
cp $currentdir/template.bin /opt/dmrlink/ambe_audio/ #cp $currentdir/template.bin /opt/dmrlink/ambe_audio/
# Bridge app # Bridge app
cp $currentdir/dmrlink.py /opt/dmrlink/bridge/ #cp $currentdir/dmrlink.py /opt/dmrlink/bridge/
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/bridge/ #cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/bridge/
# #
cp $currentdir/bridge.py /opt/dmrlink/bridge/ #cp $currentdir/bridge.py /opt/dmrlink/bridge/
cp $currentdir/bridge_rules_SAMPLE.py /opt/dmrlink/bridge/ #cp $currentdir/bridge_rules_SAMPLE.py /opt/dmrlink/bridge/
cp $currentdir/known_bridges_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/sub_acl_SAMPLE.py /opt/dmrlink/bridge/
# ConfBridge app # ConfBridge app
cp $currentdir/dmrlink.py /opt/dmrlink/confbridge/ cp $currentdir/dmrlink.py $PREFIX/confbridge/
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/confbridge/ cp $currentdir/dmrlink_SAMPLE.cfg $PREFIX/confbridge/
# #
cp $currentdir/confbridge.py /opt/dmrlink/confbridge/ cp $currentdir/confbridge.py $PREFIX/confbridge/
cp $currentdir/confbridge_rules_SAMPLE.py /opt/dmrlink/confbridge/ cp $currentdir/confbridge_rules_SAMPLE.py $PREFIX/confbridge/
cp $currentdir/known_bridges_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/sub_acl_SAMPLE.py $PREFIX/confbridge/
# Log app # Log app
cp $currentdir/dmrlink.py /opt/dmrlink/log/ #cp $currentdir/dmrlink.py /opt/dmrlink/log/
cp $currentdir/dmrlink_SAMPLE.cfg /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) # Playback (Parrot)
cp $currentdir/dmrlink.py /opt/dmrlink/playback/ cp $currentdir/dmrlink.py $PREFIX/playback/
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/playback/ cp $currentdir/dmrlink_SAMPLE.cfg $PREFIX/playback/
# #
cp $currentdir/playback.py /opt/dmrlink/playback/ cp $currentdir/playback.py $PREFIX/playback/
cp $currentdir/playback_config_SAMPLE.py /opt/dmrlink/playback/ cp $currentdir/playback_config_SAMPLE.py $PREFIX/playback/
# Play Group app # Play Group app
cp $currentdir/dmrlink.py /opt/dmrlink/play_group/ #cp $currentdir/dmrlink.py /opt/dmrlink/play_group/
cp $currentdir/dmrlink_SAMPLE.cfg /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 # proxy app
cp $currentdir/dmrlink.py /opt/dmrlink/proxy/ cp $currentdir/dmrlink.py $PREFIX/proxy/
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/proxy/ cp $currentdir/dmrlink_SAMPLE.cfg $PREFIX/proxy/
# #
cp $currentdir/proxy.py /opt/dmrlink/proxy/ cp $currentdir/proxy.py $PREFIX/proxy/
cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/proxy/ #cp $currentdir/known_bridges_SAMPLE.py $PREFIX/proxy/
cp $currentdir/sub_acl_SAMPLE.py /opt/dmrlink/proxy/ cp $currentdir/sub_acl_SAMPLE.py $PREFIX/proxy/
# rcm app # rcm app
cp $currentdir/dmrlink.py /opt/dmrlink/rcm/ #cp $currentdir/dmrlink.py /opt/dmrlink/rcm/
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/rcm/ #cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/rcm/
# #
cp $currentdir/rcm_db_log.py /opt/dmrlink/rcm/ #cp $currentdir/rcm_db_log.py /opt/dmrlink/rcm/
cp $currentdir/rcm.py /opt/dmrlink/rcm/ #cp $currentdir/rcm.py /opt/dmrlink/rcm/
# record app # record app
cp $currentdir/dmrlink.py /opt/dmrlink/record/ #cp $currentdir/dmrlink.py /opt/dmrlink/record/
cp $currentdir/dmrlink_SAMPLE.cfg /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 # TIMESLOT TO LISTEN FOR GROUP VOICE AND REPEAT
# This is a tuple of timeslots to listen to. Note, if there's only # 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 # one, you still have to use the parenthesis and comma. Just
# deal with it, or make it better. TS1 = 0, TS2 = 1. # deal with it, or make it better. TS1 = 1, TS2 = 2.
GROUP_TS = (1,) GROUP_TS = (2,)
# ALTERNATE SOURCE SUBSCRIBER ID FOR REPEATED TRANSMISSION # ALTERNATE SOURCE SUBSCRIBER ID FOR REPEATED TRANSMISSION
# Some folks have radios that don't respond to their own subscriber # 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 # 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 # TIMESLOT TO LISTEN FOR PRIVATE VOICE AND REPEAT
# This is a tuple of timeslots to listen to. Note, if there's only # 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 # one, you still have to use the parenthesis and comma. Just
# deal with it, or make it better. TS1 = 0, TS2 = 1. # deal with it, or make it better. TS1 = 1, TS2 = 2.
PRIVATE_TS = (0,1) PRIVATE_TS = (1,2)

View File

@ -72,16 +72,28 @@ __email__ = 'n0mjs@me.com'
# are not yet implemented. # are not yet implemented.
def build_acl(_sub_acl): def build_acl(_sub_acl):
try: 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) acl_file = import_module(_sub_acl)
for i, e in enumerate(acl_file.ACL): sections = acl_file.ACL.split(':')
acl_file.ACL[i] = hex_str_3(acl_file.ACL[i]) ACL_ACTION = sections[0]
logger.info('ACL file found and ACL entries imported') entries_str = sections[1]
ACL_ACTION = acl_file.ACL_ACTION ACL = set()
ACL = acl_file.ACL_ACTION
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: except ImportError:
logger.info('ACL file not found or invalid - all subscriber IDs are valid') logger.info('ACL file not found or invalid - all subscriber IDs are valid')
ACL_ACTION = 'NONE' ACL_ACTION = 'NONE'
ACL = []
# Depending on which type of ACL is used (PERMIT, DENY... or there isn't one) # 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 # 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 # The 'action' May be PERMIT|DENY
ACL = [ # Each entry may be a single radio id, or a hypenated range (e.g. 1-2999)
1234001, # Format:
1234002, # ACL = 'action:id|start-end|,id|start-end,....'
1234003
] 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