From d689a876aa06971afb6b38b5293c9f9dd1ac1e06 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Thu, 28 Nov 2019 10:49:32 -0600 Subject: [PATCH 01/68] Separate UNIT from GROUP Calls Initial framework for processing UNIT calls --- bridge.py | 866 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 452 insertions(+), 414 deletions(-) diff --git a/bridge.py b/bridge.py index e4e2605..934eab4 100755 --- a/bridge.py +++ b/bridge.py @@ -199,199 +199,215 @@ class routerOBP(OPENBRIDGE): def __init__(self, _name, _config, _report): OPENBRIDGE.__init__(self, _name, _config, _report) self.STATUS = {} + + def group_recieved(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): + pkt_time = time() + dmrpkt = _data[20:53] + _bits = _data[15] + + # Is this a new call stream? + if (_stream_id not in self.STATUS): + # This is a new call stream + self.STATUS[_stream_id] = { + 'START': pkt_time, + 'CONTENTION':False, + 'RFS': _rf_src, + 'TGID': _dst_id, + } + + # If we can, use the LC from the voice header as to keep all options intact + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: + decoded = decode.voice_head_term(dmrpkt) + self.STATUS[_stream_id]['LC'] = decoded['LC'] + + # If we don't have a voice header then don't wait to decode the Embedded LC + # just make a new one from the HBP header. This is good enough, and it saves lots of time + else: + self.STATUS[_stream_id]['LC'] = LC_OPT + _dst_id + _rf_src - def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('GROUP VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) + + self.STATUS[_stream_id]['LAST'] = pkt_time + + + for _bridge in BRIDGES: + for _system in BRIDGES[_bridge]: + + if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): + + for _target in BRIDGES[_bridge]: + if (_target['SYSTEM'] != self._system) and (_target['ACTIVE']): + _target_status = systems[_target['SYSTEM']].STATUS + _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] + if _target_system['MODE'] == 'OPENBRIDGE': + # Is this a new call stream on the target? + if (_stream_id not in _target_status): + # This is a new call stream on the target + _target_status[_stream_id] = { + 'START': pkt_time, + 'CONTENTION':False, + 'RFS': _rf_src, + 'TGID': _dst_id, + } + # Generate LCs (full and EMB) for the TX stream + dst_lc = b''.join([self.STATUS[_stream_id]['LC'][0:3], _target['TGID'], _rf_src]) + _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(dst_lc) + _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(dst_lc) + _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(dst_lc) + + logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + if CONFIG['REPORTS']['REPORT']: + systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) + + # Record the time of this packet so we can later identify a stale stream + _target_status[_stream_id]['LAST'] = pkt_time + # Clear the TS bit -- all OpenBridge streams are effectively on TS1 + _tmp_bits = _bits & ~(1 << 7) + + # Assemble transmit HBP packet header + _tmp_data = b''.join([_data[:8], _target['TGID'], _data[11:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET + # MUST RE-WRITE DESTINATION TGID IF DIFFERENT + # if _dst_id != rule['DST_GROUP']: + dmrbits = bitarray(endian='big') + dmrbits.frombytes(dmrpkt) + # Create a voice header packet (FULL LC) + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: + dmrbits = _target_status[_stream_id]['H_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['H_LC'][98:197] + # Create a voice terminator packet (FULL LC) + elif _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VTERM: + dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] + if CONFIG['REPORTS']['REPORT']: + call_duration = pkt_time - _target_status[_stream_id]['START'] + systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) + # Create a Burst B-E packet (Embedded LC) + elif _dtype_vseq in [1,2,3,4]: + dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] + dmrpkt = dmrbits.tobytes() + _tmp_data = b''.join([_tmp_data, dmrpkt]) + + else: + # BEGIN CONTENTION HANDLING + # + # 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 HBSystem, but it has been less than Group Hangtime + # From a different group than last TX to this HBSystem, but it has been less than Group Hangtime + # From the same group as the last RX from this HBSystem, but from a different subscriber, and it has been less than stream timeout + # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout + # 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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %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 ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %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 (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) + continue + + # Is this a new call stream? + if (_target_status[_target['TS']]['TX_STREAM_ID'] != _stream_id): + # Record the DST TGID and Stream ID + _target_status[_target['TS']]['TX_START'] = pkt_time + _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] + _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id + _target_status[_target['TS']]['TX_RFS'] = _rf_src + _target_status[_target['TS']]['TX_PEER'] = _peer_id + # Generate LCs (full and EMB) for the TX stream + dst_lc = b''.join([self.STATUS[_stream_id]['LC'][0:3], _target['TGID'], _rf_src]) + _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) + _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) + _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) + logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + if CONFIG['REPORTS']['REPORT']: + systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) + + # Set other values for the contention handler to test next time there is a frame to forward + _target_status[_target['TS']]['TX_TIME'] = pkt_time + _target_status[_target['TS']]['TX_TYPE'] = _dtype_vseq + + # Handle any necessary re-writes for the destination + if _system['TS'] != _target['TS']: + _tmp_bits = _bits ^ 1 << 7 + else: + _tmp_bits = _bits + + # Assemble transmit HBP packet header + _tmp_data = b''.join([_data[:8], _target['TGID'], _data[11:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET + # MUST RE-WRITE DESTINATION TGID IF DIFFERENT + # if _dst_id != rule['DST_GROUP']: + dmrbits = bitarray(endian='big') + dmrbits.frombytes(dmrpkt) + # Create a voice header packet (FULL LC) + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: + dmrbits = _target_status[_target['TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_H_LC'][98:197] + # Create a voice terminator packet (FULL LC) + elif _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VTERM: + dmrbits = _target_status[_target['TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_T_LC'][98:197] + if CONFIG['REPORTS']['REPORT']: + call_duration = pkt_time - _target_status[_target['TS']]['TX_START'] + systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) + # Create a Burst B-E packet (Embedded LC) + elif _dtype_vseq in [1,2,3,4]: + dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] + dmrpkt = dmrbits.tobytes() + _tmp_data = b''.join([_tmp_data, dmrpkt, b'\x00\x00']) # Add two bytes of nothing since OBP doesn't include BER & RSSI bytes #_data[53:55] + + # Transmit the packet to the destination system + systems[_target['SYSTEM']].send_system(_tmp_data) + #logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + + + + # Final actions - Is this a voice terminator? + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): + call_duration = pkt_time - self.STATUS[_stream_id]['START'] + logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) + removed = self.STATUS.pop(_stream_id) + logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) + if not removed: + selflogger.error('(%s) *CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) + + + def unit_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + if _call_type == 'group': - # Is this a new call stream? - if (_stream_id not in self.STATUS): - # This is a new call stream - self.STATUS[_stream_id] = { - 'START': pkt_time, - 'CONTENTION':False, - 'RFS': _rf_src, - 'TGID': _dst_id, - } - - # If we can, use the LC from the voice header as to keep all options intact - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: - decoded = decode.voice_head_term(dmrpkt) - self.STATUS[_stream_id]['LC'] = decoded['LC'] - - # If we don't have a voice header then don't wait to decode the Embedded LC - # just make a new one from the HBP header. This is good enough, and it saves lots of time - else: - self.STATUS[_stream_id]['LC'] = LC_OPT + _dst_id + _rf_src - - - logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) - if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('GROUP VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) - - self.STATUS[_stream_id]['LAST'] = pkt_time - - - for _bridge in BRIDGES: - for _system in BRIDGES[_bridge]: - - if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): - - for _target in BRIDGES[_bridge]: - if (_target['SYSTEM'] != self._system) and (_target['ACTIVE']): - _target_status = systems[_target['SYSTEM']].STATUS - _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] - if _target_system['MODE'] == 'OPENBRIDGE': - # Is this a new call stream on the target? - if (_stream_id not in _target_status): - # This is a new call stream on the target - _target_status[_stream_id] = { - 'START': pkt_time, - 'CONTENTION':False, - 'RFS': _rf_src, - 'TGID': _dst_id, - } - # Generate LCs (full and EMB) for the TX stream - dst_lc = b''.join([self.STATUS[_stream_id]['LC'][0:3], _target['TGID'], _rf_src]) - _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(dst_lc) - _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(dst_lc) - _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(dst_lc) - - logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - if CONFIG['REPORTS']['REPORT']: - systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) - - # Record the time of this packet so we can later identify a stale stream - _target_status[_stream_id]['LAST'] = pkt_time - # Clear the TS bit -- all OpenBridge streams are effectively on TS1 - _tmp_bits = _bits & ~(1 << 7) - - # Assemble transmit HBP packet header - _tmp_data = b''.join([_data[:8], _target['TGID'], _data[11:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - - # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET - # MUST RE-WRITE DESTINATION TGID IF DIFFERENT - # if _dst_id != rule['DST_GROUP']: - dmrbits = bitarray(endian='big') - dmrbits.frombytes(dmrpkt) - # Create a voice header packet (FULL LC) - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: - dmrbits = _target_status[_stream_id]['H_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['H_LC'][98:197] - # Create a voice terminator packet (FULL LC) - elif _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VTERM: - dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] - if CONFIG['REPORTS']['REPORT']: - call_duration = pkt_time - _target_status[_stream_id]['START'] - systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) - # Create a Burst B-E packet (Embedded LC) - elif _dtype_vseq in [1,2,3,4]: - dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] - dmrpkt = dmrbits.tobytes() - _tmp_data = b''.join([_tmp_data, dmrpkt]) - - else: - # BEGIN CONTENTION HANDLING - # - # 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 HBSystem, but it has been less than Group Hangtime - # From a different group than last TX to this HBSystem, but it has been less than Group Hangtime - # From the same group as the last RX from this HBSystem, but from a different subscriber, and it has been less than stream timeout - # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout - # 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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %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 ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %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 (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) - continue - - # Is this a new call stream? - if (_target_status[_target['TS']]['TX_STREAM_ID'] != _stream_id): - # Record the DST TGID and Stream ID - _target_status[_target['TS']]['TX_START'] = pkt_time - _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] - _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id - _target_status[_target['TS']]['TX_RFS'] = _rf_src - _target_status[_target['TS']]['TX_PEER'] = _peer_id - # Generate LCs (full and EMB) for the TX stream - dst_lc = b''.join([self.STATUS[_stream_id]['LC'][0:3], _target['TGID'], _rf_src]) - _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) - _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) - _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) - logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - if CONFIG['REPORTS']['REPORT']: - systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) - - # Set other values for the contention handler to test next time there is a frame to forward - _target_status[_target['TS']]['TX_TIME'] = pkt_time - _target_status[_target['TS']]['TX_TYPE'] = _dtype_vseq - - # Handle any necessary re-writes for the destination - if _system['TS'] != _target['TS']: - _tmp_bits = _bits ^ 1 << 7 - else: - _tmp_bits = _bits - - # Assemble transmit HBP packet header - _tmp_data = b''.join([_data[:8], _target['TGID'], _data[11:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - - # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET - # MUST RE-WRITE DESTINATION TGID IF DIFFERENT - # if _dst_id != rule['DST_GROUP']: - dmrbits = bitarray(endian='big') - dmrbits.frombytes(dmrpkt) - # Create a voice header packet (FULL LC) - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: - dmrbits = _target_status[_target['TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_H_LC'][98:197] - # Create a voice terminator packet (FULL LC) - elif _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VTERM: - dmrbits = _target_status[_target['TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_T_LC'][98:197] - if CONFIG['REPORTS']['REPORT']: - call_duration = pkt_time - _target_status[_target['TS']]['TX_START'] - systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) - # Create a Burst B-E packet (Embedded LC) - elif _dtype_vseq in [1,2,3,4]: - dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] - dmrpkt = dmrbits.tobytes() - _tmp_data = b''.join([_tmp_data, dmrpkt, b'\x00\x00']) # Add two bytes of nothing since OBP doesn't include BER & RSSI bytes #_data[53:55] - - # Transmit the packet to the destination system - systems[_target['SYSTEM']].send_system(_tmp_data) - #logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - - - - # Final actions - Is this a voice terminator? - if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): - call_duration = pkt_time - self.STATUS[_stream_id]['START'] - logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) - if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) - removed = self.STATUS.pop(_stream_id) - logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) - if not removed: - selflogger.error('(%s) *CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) + self.group_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) + elif _call_type == 'unit': + self.unit_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) + elif _call_type == 'vscsbk': + logger.debug('CSBK recieved, but HBlink does not process them currently') + else: + logger.error('Unknown call type recieved -- not processed') + class routerHBP(HBSYSTEM): @@ -455,253 +471,275 @@ class routerHBP(HBSYSTEM): } } } + + # Dictionary for dynamically mapping unit (subscriber) to a system. + # This is for pruning unit-to-uint calls to not broadcast once the + # target system for a unit is identified + # format 'unit_id': ('SYSTEM', time) + self.UNIT_MAP = {} + - def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + + def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] - if _call_type == 'group': + # Is this a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): + logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s TGID %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) + return - # Is this a new call stream? - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): - if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): - logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s TGID %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) - return + # This is a new call stream + self.STATUS[_slot]['RX_START'] = pkt_time + logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('GROUP VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) - # This is a new call stream - self.STATUS[_slot]['RX_START'] = pkt_time - logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) - if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('GROUP VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) + # If we can, use the LC from the voice header as to keep all options intact + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: + decoded = decode.voice_head_term(dmrpkt) + self.STATUS[_slot]['RX_LC'] = decoded['LC'] - # If we can, use the LC from the voice header as to keep all options intact - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: - decoded = decode.voice_head_term(dmrpkt) - self.STATUS[_slot]['RX_LC'] = decoded['LC'] + # If we don't have a voice header then don't wait to decode it from the Embedded LC + # just make a new one from the HBP header. This is good enough, and it saves lots of time + else: + self.STATUS[_slot]['RX_LC'] = LC_OPT + _dst_id + _rf_src - # If we don't have a voice header then don't wait to decode it from the Embedded LC - # just make a new one from the HBP header. This is good enough, and it saves lots of time - else: - self.STATUS[_slot]['RX_LC'] = LC_OPT + _dst_id + _rf_src + for _bridge in BRIDGES: + for _system in BRIDGES[_bridge]: + + if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): + + for _target in BRIDGES[_bridge]: + if _target['SYSTEM'] != self._system: + if _target['ACTIVE']: + _target_status = systems[_target['SYSTEM']].STATUS + _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] + + if _target_system['MODE'] == 'OPENBRIDGE': + # Is this a new call stream on the target? + if (_stream_id not in _target_status): + # This is a new call stream on the target + _target_status[_stream_id] = { + 'START': pkt_time, + 'CONTENTION':False, + 'RFS': _rf_src, + 'TGID': _dst_id, + } + # Generate LCs (full and EMB) for the TX stream + dst_lc = b''.join([self.STATUS[_slot]['RX_LC'][0:3], _target['TGID'], _rf_src]) + _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(dst_lc) + _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(dst_lc) + _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(dst_lc) + + logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + if CONFIG['REPORTS']['REPORT']: + systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) + + # Record the time of this packet so we can later identify a stale stream + _target_status[_stream_id]['LAST'] = pkt_time + # Clear the TS bit -- all OpenBridge streams are effectively on TS1 + _tmp_bits = _bits & ~(1 << 7) + + # Assemble transmit HBP packet header + _tmp_data = b''.join([_data[:8], _target['TGID'], _data[11:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET + # MUST RE-WRITE DESTINATION TGID IF DIFFERENT + # if _dst_id != rule['DST_GROUP']: + dmrbits = bitarray(endian='big') + dmrbits.frombytes(dmrpkt) + # Create a voice header packet (FULL LC) + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: + dmrbits = _target_status[_stream_id]['H_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['H_LC'][98:197] + # Create a voice terminator packet (FULL LC) + elif _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VTERM: + dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] + if CONFIG['REPORTS']['REPORT']: + call_duration = pkt_time - _target_status[_stream_id]['START'] + systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) + # Create a Burst B-E packet (Embedded LC) + elif _dtype_vseq in [1,2,3,4]: + dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] + dmrpkt = dmrbits.tobytes() + _tmp_data = b''.join([_tmp_data, dmrpkt]) + + else: + # BEGIN STANDARD CONTENTION HANDLING + # + # 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 HBSystem, but it has been less than Group Hangtime + # From a different group than last TX to this HBSystem, but it has been less than Group Hangtime + # From the same group as the last RX from this HBSystem, but from a different subscriber, and it has been less than stream timeout + # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout + # 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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %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 ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %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 (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) + continue + + # Is this a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + # Record the DST TGID and Stream ID + _target_status[_target['TS']]['TX_START'] = pkt_time + _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] + _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id + _target_status[_target['TS']]['TX_RFS'] = _rf_src + _target_status[_target['TS']]['TX_PEER'] = _peer_id + # Generate LCs (full and EMB) for the TX stream + dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + _target['TGID'] + _rf_src + _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) + _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) + _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) + logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + if CONFIG['REPORTS']['REPORT']: + systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) + + # Set other values for the contention handler to test next time there is a frame to forward + _target_status[_target['TS']]['TX_TIME'] = pkt_time + _target_status[_target['TS']]['TX_TYPE'] = _dtype_vseq + + # Handle any necessary re-writes for the destination + if _system['TS'] != _target['TS']: + _tmp_bits = _bits ^ 1 << 7 + else: + _tmp_bits = _bits + + # Assemble transmit HBP packet header + _tmp_data = b''.join([_data[:8], _target['TGID'], _data[11:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET + # MUST RE-WRITE DESTINATION TGID IF DIFFERENT + # if _dst_id != rule['DST_GROUP']: + dmrbits = bitarray(endian='big') + dmrbits.frombytes(dmrpkt) + # Create a voice header packet (FULL LC) + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: + dmrbits = _target_status[_target['TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_H_LC'][98:197] + # Create a voice terminator packet (FULL LC) + elif _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VTERM: + dmrbits = _target_status[_target['TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_T_LC'][98:197] + if CONFIG['REPORTS']['REPORT']: + call_duration = pkt_time - _target_status[_target['TS']]['TX_START'] + systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) + # Create a Burst B-E packet (Embedded LC) + elif _dtype_vseq in [1,2,3,4]: + dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] + dmrpkt = dmrbits.tobytes() + _tmp_data = b''.join([_tmp_data, dmrpkt, _data[53:55]]) + + # Transmit the packet to the destination system + systems[_target['SYSTEM']].send_system(_tmp_data) + #logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + + + + # Final actions - Is this a voice terminator? + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): + call_duration = pkt_time - self.STATUS[_slot]['RX_START'] + logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) + + # + # Begin in-band signalling for call end. This has nothign to do with routing traffic directly. + # + + # Iterate the rules dictionary for _bridge in BRIDGES: for _system in BRIDGES[_bridge]: + if _system['SYSTEM'] == self._system: - if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): + # TGID matches a rule source, reset its timer + if _slot == _system['TS'] and _dst_id == _system['TGID'] and ((_system['TO_TYPE'] == 'ON' and (_system['ACTIVE'] == True)) or (_system['TO_TYPE'] == 'OFF' and _system['ACTIVE'] == False)): + _system['TIMER'] = pkt_time + _system['TIMEOUT'] + logger.info('(%s) Transmission match for Bridge: %s. Reset timeout to %s', self._system, _bridge, _system['TIMER']) - for _target in BRIDGES[_bridge]: - if _target['SYSTEM'] != self._system: - if _target['ACTIVE']: - _target_status = systems[_target['SYSTEM']].STATUS - _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] - - if _target_system['MODE'] == 'OPENBRIDGE': - # Is this a new call stream on the target? - if (_stream_id not in _target_status): - # This is a new call stream on the target - _target_status[_stream_id] = { - 'START': pkt_time, - 'CONTENTION':False, - 'RFS': _rf_src, - 'TGID': _dst_id, - } - # Generate LCs (full and EMB) for the TX stream - dst_lc = b''.join([self.STATUS[_slot]['RX_LC'][0:3], _target['TGID'], _rf_src]) - _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(dst_lc) - _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(dst_lc) - _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(dst_lc) - - logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - if CONFIG['REPORTS']['REPORT']: - systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) - - # Record the time of this packet so we can later identify a stale stream - _target_status[_stream_id]['LAST'] = pkt_time - # Clear the TS bit -- all OpenBridge streams are effectively on TS1 - _tmp_bits = _bits & ~(1 << 7) - - # Assemble transmit HBP packet header - _tmp_data = b''.join([_data[:8], _target['TGID'], _data[11:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - - # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET - # MUST RE-WRITE DESTINATION TGID IF DIFFERENT - # if _dst_id != rule['DST_GROUP']: - dmrbits = bitarray(endian='big') - dmrbits.frombytes(dmrpkt) - # Create a voice header packet (FULL LC) - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: - dmrbits = _target_status[_stream_id]['H_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['H_LC'][98:197] - # Create a voice terminator packet (FULL LC) - elif _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VTERM: - dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] - if CONFIG['REPORTS']['REPORT']: - call_duration = pkt_time - _target_status[_stream_id]['START'] - systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) - # Create a Burst B-E packet (Embedded LC) - elif _dtype_vseq in [1,2,3,4]: - dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] - dmrpkt = dmrbits.tobytes() - _tmp_data = b''.join([_tmp_data, dmrpkt]) - - else: - # BEGIN STANDARD CONTENTION HANDLING - # - # 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 HBSystem, but it has been less than Group Hangtime - # From a different group than last TX to this HBSystem, but it has been less than Group Hangtime - # From the same group as the last RX from this HBSystem, but from a different subscriber, and it has been less than stream timeout - # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout - # 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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %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 ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %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 (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) - continue - - # Is this a new call stream? - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): - # Record the DST TGID and Stream ID - _target_status[_target['TS']]['TX_START'] = pkt_time - _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] - _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id - _target_status[_target['TS']]['TX_RFS'] = _rf_src - _target_status[_target['TS']]['TX_PEER'] = _peer_id - # Generate LCs (full and EMB) for the TX stream - dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + _target['TGID'] + _rf_src - _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) - _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) - _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) - logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - if CONFIG['REPORTS']['REPORT']: - systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) - - # Set other values for the contention handler to test next time there is a frame to forward - _target_status[_target['TS']]['TX_TIME'] = pkt_time - _target_status[_target['TS']]['TX_TYPE'] = _dtype_vseq - - # Handle any necessary re-writes for the destination - if _system['TS'] != _target['TS']: - _tmp_bits = _bits ^ 1 << 7 - else: - _tmp_bits = _bits - - # Assemble transmit HBP packet header - _tmp_data = b''.join([_data[:8], _target['TGID'], _data[11:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - - # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET - # MUST RE-WRITE DESTINATION TGID IF DIFFERENT - # if _dst_id != rule['DST_GROUP']: - dmrbits = bitarray(endian='big') - dmrbits.frombytes(dmrpkt) - # Create a voice header packet (FULL LC) - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: - dmrbits = _target_status[_target['TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_H_LC'][98:197] - # Create a voice terminator packet (FULL LC) - elif _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VTERM: - dmrbits = _target_status[_target['TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_T_LC'][98:197] - if CONFIG['REPORTS']['REPORT']: - call_duration = pkt_time - _target_status[_target['TS']]['TX_START'] - systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) - # Create a Burst B-E packet (Embedded LC) - elif _dtype_vseq in [1,2,3,4]: - dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] - dmrpkt = dmrbits.tobytes() - _tmp_data = b''.join([_tmp_data, dmrpkt, _data[53:55]]) - - # Transmit the packet to the destination system - systems[_target['SYSTEM']].send_system(_tmp_data) - #logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - - - - # Final actions - Is this a voice terminator? - if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): - call_duration = pkt_time - self.STATUS[_slot]['RX_START'] - logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) - if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) - - # - # Begin in-band signalling for call end. This has nothign to do with routing traffic directly. - # - - # Iterate the rules dictionary - - for _bridge in BRIDGES: - for _system in BRIDGES[_bridge]: - if _system['SYSTEM'] == self._system: - - # TGID matches a rule source, reset its timer - if _slot == _system['TS'] and _dst_id == _system['TGID'] and ((_system['TO_TYPE'] == 'ON' and (_system['ACTIVE'] == True)) or (_system['TO_TYPE'] == 'OFF' and _system['ACTIVE'] == False)): + # TGID matches an ACTIVATION trigger + if (_dst_id in _system['ON'] or _dst_id in _system['RESET']) and _slot == _system['TS']: + # Set the matching rule as ACTIVE + if _dst_id in _system['ON']: + if _system['ACTIVE'] == False: + _system['ACTIVE'] = True + _system['TIMER'] = pkt_time + _system['TIMEOUT'] + 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'] = pkt_time + 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'] = pkt_time + _system['TIMEOUT'] - logger.info('(%s) Transmission match for Bridge: %s. Reset timeout to %s', self._system, _bridge, _system['TIMER']) + logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) - # TGID matches an ACTIVATION trigger - if (_dst_id in _system['ON'] or _dst_id in _system['RESET']) and _slot == _system['TS']: - # Set the matching rule as ACTIVE - if _dst_id in _system['ON']: - if _system['ACTIVE'] == False: - _system['ACTIVE'] = True - _system['TIMER'] = pkt_time + _system['TIMEOUT'] - 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'] = pkt_time - 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'] = pkt_time + _system['TIMEOUT'] - logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) + # TGID matches an DE-ACTIVATION trigger + if (_dst_id in _system['OFF'] or _dst_id in _system['RESET']) and _slot == _system['TS']: + # Set the matching rule as ACTIVE + if _dst_id in _system['OFF']: + if _system['ACTIVE'] == True: + _system['ACTIVE'] = False + 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'] = pkt_time + 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'] = pkt_time + _system['TIMEOUT'] + logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) + # Cancel the timer if we've enabled an "ON" type timeout + if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON' and _dst_group in _system['OFF']: + _system['TIMER'] = pkt_time + logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge) - # TGID matches an DE-ACTIVATION trigger - if (_dst_id in _system['OFF'] or _dst_id in _system['RESET']) and _slot == _system['TS']: - # Set the matching rule as ACTIVE - if _dst_id in _system['OFF']: - if _system['ACTIVE'] == True: - _system['ACTIVE'] = False - 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'] = pkt_time - 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'] = pkt_time + _system['TIMEOUT'] - logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) - # Cancel the timer if we've enabled an "ON" type timeout - if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON' and _dst_group in _system['OFF']: - _system['TIMER'] = pkt_time - logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge) - - # - # END IN-BAND SIGNALLING - # + # + # END IN-BAND SIGNALLING + # + # Mark status variables for use later + self.STATUS[_slot]['RX_PEER'] = _peer_id + self.STATUS[_slot]['RX_SEQ'] = _seq + self.STATUS[_slot]['RX_RFS'] = _rf_src + self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq + self.STATUS[_slot]['RX_TGID'] = _dst_id + self.STATUS[_slot]['RX_TIME'] = pkt_time + self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id - # Mark status variables for use later - self.STATUS[_slot]['RX_PEER'] = _peer_id - self.STATUS[_slot]['RX_SEQ'] = _seq - self.STATUS[_slot]['RX_RFS'] = _rf_src - self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq - self.STATUS[_slot]['RX_TGID'] = _dst_id - self.STATUS[_slot]['RX_TIME'] = pkt_time - self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id + def unit_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): + pkt_time = time() + dmrpkt = _data[20:53] + _bits = _data[15] + print('UNIT CALL') + + + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + if _call_type == 'group': + self.group_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) + elif _call_type == 'unit': + self.unit_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) + elif _call_type == 'vscsbk': + logger.debug('CSBK recieved, but HBlink does not process them currently') + else: + logger.error('Unknown call type recieved -- not processed') # # Socket-based reporting section From d8bfdc7068ec37f450b00417c939828898a692d3 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 29 Nov 2019 10:35:27 -0600 Subject: [PATCH 02/68] Track subscribers this is not yet really useable, just getting the mechanics started --- bridge.py | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/bridge.py b/bridge.py index 934eab4..8beb76a 100755 --- a/bridge.py +++ b/bridge.py @@ -67,6 +67,13 @@ __email__ = 'n0mjs@me.com' # Module gobal varaibles +# Dictionary for dynamically mapping unit (subscriber) to a system. +# This is for pruning unit-to-uint calls to not broadcast once the +# target system for a unit is identified +# format 'unit_id': ('SYSTEM', time) +UNIT_MAP = {} + + # Timed loop used for reporting HBP status # # REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE @@ -116,6 +123,7 @@ def make_bridges(_rules): # Run this every minute for rule timer updates def rule_timer_loop(): + global UNIT_MAP logger.debug('(ROUTER) routerHBP Rule timer loop started') _now = time() @@ -143,6 +151,18 @@ def rule_timer_loop(): logger.debug('(ROUTER) Conference Bridge ACTIVE (no change): System: %s Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) else: logger.debug('(ROUTER) Conference Bridge NO ACTION: System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) + + _then = _now - 60 + remove_list = [] + for unit in UNIT_MAP: + if UNIT_MAP[unit][1] < (_then): + remove_list.append(unit) + + for unit in remove_list: + del UNIT_MAP[unit] + + logger.debug('Removed unit(s) %s from UNIT_MAP', remove_list) + if CONFIG['REPORTS']['REPORT']: report_server.send_clients(b'bridge updated') @@ -396,6 +416,7 @@ class routerOBP(OPENBRIDGE): pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): @@ -413,6 +434,7 @@ class routerHBP(HBSYSTEM): def __init__(self, _name, _config, _report): HBSYSTEM.__init__(self, _name, _config, _report) + self.name = _name # Status information for the system, TS1 & TS2 # 1 & 2 are "timeslot" @@ -471,14 +493,7 @@ class routerHBP(HBSYSTEM): } } } - - # Dictionary for dynamically mapping unit (subscriber) to a system. - # This is for pruning unit-to-uint calls to not broadcast once the - # target system for a unit is identified - # format 'unit_id': ('SYSTEM', time) - self.UNIT_MAP = {} - - + def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() @@ -725,10 +740,16 @@ class routerHBP(HBSYSTEM): def unit_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): + global UNIT_MAP pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] - print('UNIT CALL') + print(UNIT_MAP) + if _rf_src not in UNIT_MAP: + UNIT_MAP[_rf_src] = [self.name, pkt_time] + else: + UNIT_MAP[_rf_src][1] = pkt_time + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): From e5eafeec3efa751e2d2481f47f64bc1e3e915167 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 1 Dec 2019 10:06:08 -0600 Subject: [PATCH 03/68] incremental update - Not Working the program runs at ths point, logging is updated for group calls, no errors in unit sectinon, but no contention handler and send routines not yet built. --- bridge.py | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 9 deletions(-) diff --git a/bridge.py b/bridge.py index 8beb76a..4a5942d 100755 --- a/bridge.py +++ b/bridge.py @@ -246,7 +246,7 @@ class routerOBP(OPENBRIDGE): self.STATUS[_stream_id]['LC'] = LC_OPT + _dst_id + _rf_src - logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ + logger.info('(%s) *GROUP CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) @@ -402,14 +402,14 @@ class routerOBP(OPENBRIDGE): # Final actions - Is this a voice terminator? if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): call_duration = pkt_time - self.STATUS[_stream_id]['START'] - logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ + logger.info('(%s) *GROUP CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) removed = self.STATUS.pop(_stream_id) logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) if not removed: - selflogger.error('(%s) *CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) + selflogger.error('(%s) *GROUP CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) def unit_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): @@ -496,9 +496,16 @@ class routerHBP(HBSYSTEM): def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): + global UNIT_MAP pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] + + # Make an entry in the UNIT_MAP for this subscriber + if _rf_src not in UNIT_MAP: + UNIT_MAP[_rf_src] = [self.name, pkt_time] + else: + UNIT_MAP[_rf_src][1] = pkt_time # Is this a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): @@ -508,7 +515,7 @@ class routerHBP(HBSYSTEM): # This is a new call stream self.STATUS[_slot]['RX_START'] = pkt_time - logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ + logger.info('(%s) *GROUP CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) @@ -640,9 +647,6 @@ class routerHBP(HBSYSTEM): # Assemble transmit HBP packet header _tmp_data = b''.join([_data[:8], _target['TGID'], _data[11:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET - # MUST RE-WRITE DESTINATION TGID IF DIFFERENT - # if _dst_id != rule['DST_GROUP']: dmrbits = bitarray(endian='big') dmrbits.frombytes(dmrpkt) # Create a voice header packet (FULL LC) @@ -669,7 +673,7 @@ class routerHBP(HBSYSTEM): # Final actions - Is this a voice terminator? if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): call_duration = pkt_time - self.STATUS[_slot]['RX_START'] - logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ + logger.info('(%s) *GROUP CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) @@ -744,12 +748,88 @@ class routerHBP(HBSYSTEM): pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] - print(UNIT_MAP) + if _rf_src not in UNIT_MAP: UNIT_MAP[_rf_src] = [self.name, pkt_time] else: UNIT_MAP[_rf_src][1] = pkt_time + # Is this a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): + logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s UNIT %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) + return + + # This is a new call stream + self.STATUS[_slot]['RX_START'] = pkt_time + logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) + + # If we can, use the LC from the voice header as to keep all options intact + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: + decoded = decode.voice_head_term(dmrpkt) + self.STATUS[_slot]['RX_LC'] = decoded['LC'] + + # If we don't have a voice header then don't wait to decode it from the Embedded LC + # just make a new one from the HBP header. This is good enough, and it saves lots of time + else: + self.STATUS[_slot]['RX_LC'] = LC_OPT + _dst_id + _rf_src + + + #if _dst_id in UNIT_MAP: + if True: + _target = UNIT_MAP[_dst_id][0] + + #if _target != self._system: + if True: + _target_status = systems[_target].STATUS + _target_system = self._CONFIG['SYSTEMS'][_target] + + + # Is this a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + # Record the DST TGID and Stream ID + _target_status[_slot]['TX_START'] = pkt_time + _target_status[_slot]['TX_TGID'] = _dst_id + _target_status[_slot]['TX_STREAM_ID'] = _stream_id + _target_status[_slot]['TX_RFS'] = _rf_src + _target_status[_slot]['TX_PEER'] = _peer_id + + # Set other values for the contention handler to test next time there is a frame to forward + _target_status[_slot]['TX_TIME'] = pkt_time + _target_status[_slot]['TX_TYPE'] = _dtype_vseq + + #send the call: + + systems[_target].send_system(_data) + + + else: + pass + #for target in systems: + # systems[target].send_system(_data) + + + + # Final actions - Is this a voice terminator? + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): + call_duration = pkt_time - self.STATUS[_slot]['RX_START'] + logger.info('(%s) *UNIT CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s, Duration: %.2f', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('UNIT VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) + + # Mark status variables for use later + self.STATUS[_slot]['RX_PEER'] = _peer_id + self.STATUS[_slot]['RX_SEQ'] = _seq + self.STATUS[_slot]['RX_RFS'] = _rf_src + self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq + self.STATUS[_slot]['RX_TGID'] = _dst_id + self.STATUS[_slot]['RX_TIME'] = pkt_time + self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): From f98c12096c3c942b6a8fbe6d3e5e2e24d7e38935 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 1 Dec 2019 11:20:14 -0600 Subject: [PATCH 04/68] Works, but no contention handler Private call forwarding works, with destionation routing cache, but no contention handler yet. --- bridge.py | 80 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/bridge.py b/bridge.py index 4a5942d..1db34fa 100755 --- a/bridge.py +++ b/bridge.py @@ -436,6 +436,9 @@ class routerHBP(HBSYSTEM): HBSYSTEM.__init__(self, _name, _config, _report) self.name = _name + # list of targets for unit (subscriber, private) calls + self.targets = [] + # Status information for the system, TS1 & TS2 # 1 & 2 are "timeslot" # In TX_EMB_LC, 2-5 are burst B-E @@ -756,16 +759,29 @@ class routerHBP(HBSYSTEM): # Is this a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + # Create a destination list for the call: + if _dst_id in UNIT_MAP: + if UNIT_MAP[_dst_id][0] != self._system: + self._targets = [UNIT_MAP[_dst_id][0]] + _target_route = UNIT_MAP[_dst_id][0] + else: + self._targets = [] + logger.debug('UNIT call to a subscriber on the same system, send nothing') + else: + self._targets = list(systems) + self._targets.remove(self._system) + _target_route = 'FLOOD' + if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s UNIT %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) return # This is a new call stream self.STATUS[_slot]['RX_START'] = pkt_time - logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) + logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT: %s (%s), TS: %s, FORWARD: %s', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, _target_route) if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) + self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), _target_route).encode(encoding='utf-8', errors='ignore')) # If we can, use the LC from the voice header as to keep all options intact if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: @@ -776,45 +792,35 @@ class routerHBP(HBSYSTEM): # just make a new one from the HBP header. This is good enough, and it saves lots of time else: self.STATUS[_slot]['RX_LC'] = LC_OPT + _dst_id + _rf_src - - - #if _dst_id in UNIT_MAP: - if True: - _target = UNIT_MAP[_dst_id][0] + + for _target in self._targets: + if self._CONFIG['SYSTEMS'][_target]['MODE'] == 'OPENBRIDGE': + # THIS IS ONLY UNTIL OPENBRIDGE IS SUPPORTED + continue + + _target_status = systems[_target].STATUS + #_target_system = self._CONFIG['SYSTEMS'][_target] + + # Is this a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + # Record the DST TGID and Stream ID + _target_status[_slot]['TX_START'] = pkt_time + _target_status[_slot]['TX_TGID'] = _dst_id + _target_status[_slot]['TX_STREAM_ID'] = _stream_id + _target_status[_slot]['TX_RFS'] = _rf_src + _target_status[_slot]['TX_PEER'] = _peer_id - #if _target != self._system: - if True: - _target_status = systems[_target].STATUS - _target_system = self._CONFIG['SYSTEMS'][_target] - - - # Is this a new call stream? - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): - # Record the DST TGID and Stream ID - _target_status[_slot]['TX_START'] = pkt_time - _target_status[_slot]['TX_TGID'] = _dst_id - _target_status[_slot]['TX_STREAM_ID'] = _stream_id - _target_status[_slot]['TX_RFS'] = _rf_src - _target_status[_slot]['TX_PEER'] = _peer_id - - # Set other values for the contention handler to test next time there is a frame to forward - _target_status[_slot]['TX_TIME'] = pkt_time - _target_status[_slot]['TX_TYPE'] = _dtype_vseq - - #send the call: + # Set other values for the contention handler to test next time there is a frame to forward + _target_status[_slot]['TX_TIME'] = pkt_time + _target_status[_slot]['TX_TYPE'] = _dtype_vseq - systems[_target].send_system(_data) - - - else: - pass - #for target in systems: - # systems[target].send_system(_data) - - + #send the call: + systems[_target].send_system(_data) + # Final actions - Is this a voice terminator? if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): + self._targets = [] call_duration = pkt_time - self.STATUS[_slot]['RX_START'] logger.info('(%s) *UNIT CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s, Duration: %.2f', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) From 8a45e927ae847c38723d76335fe6a0c0c92c6541 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 1 Dec 2019 13:27:07 -0600 Subject: [PATCH 05/68] unit to unit calls, with contention working - HBSystems only This appears to work, but no openbridge support yet. --- bridge.py | 89 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 31 deletions(-) diff --git a/bridge.py b/bridge.py index 1db34fa..2de7ecb 100755 --- a/bridge.py +++ b/bridge.py @@ -504,11 +504,8 @@ class routerHBP(HBSYSTEM): dmrpkt = _data[20:53] _bits = _data[15] - # Make an entry in the UNIT_MAP for this subscriber - if _rf_src not in UNIT_MAP: - UNIT_MAP[_rf_src] = [self.name, pkt_time] - else: - UNIT_MAP[_rf_src][1] = pkt_time + # Make/update an entry in the UNIT_MAP for this subscriber + UNIT_MAP[_rf_src] = (self.name, pkt_time) # Is this a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): @@ -752,13 +749,18 @@ class routerHBP(HBSYSTEM): dmrpkt = _data[20:53] _bits = _data[15] - if _rf_src not in UNIT_MAP: - UNIT_MAP[_rf_src] = [self.name, pkt_time] - else: - UNIT_MAP[_rf_src][1] = pkt_time + # Make/update this unit in the UNIT_MAP cache + UNIT_MAP[_rf_src] = (self.name, pkt_time) + # Is this a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + + # Collision in progress, bail out! + if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): + logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s UNIT %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) + return + # Create a destination list for the call: if _dst_id in UNIT_MAP: if UNIT_MAP[_dst_id][0] != self._system: @@ -772,17 +774,14 @@ class routerHBP(HBSYSTEM): self._targets.remove(self._system) _target_route = 'FLOOD' - if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): - logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s UNIT %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) - return - - # This is a new call stream + # This is a new call stream, so log & report self.STATUS[_slot]['RX_START'] = pkt_time logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT: %s (%s), TS: %s, FORWARD: %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, _target_route) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), _target_route).encode(encoding='utf-8', errors='ignore')) - + + ''' LC CREATION NOT NECESSARY, SINCE WE'RE NOT TRANSCODING # If we can, use the LC from the voice header as to keep all options intact if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: decoded = decode.voice_head_term(dmrpkt) @@ -792,27 +791,55 @@ class routerHBP(HBSYSTEM): # just make a new one from the HBP header. This is good enough, and it saves lots of time else: self.STATUS[_slot]['RX_LC'] = LC_OPT + _dst_id + _rf_src - + ''' + for _target in self._targets: + _target_status = systems[_target].STATUS + _target_system = self._CONFIG['SYSTEMS'][_target] + if self._CONFIG['SYSTEMS'][_target]['MODE'] == 'OPENBRIDGE': # THIS IS ONLY UNTIL OPENBRIDGE IS SUPPORTED continue - - _target_status = systems[_target].STATUS - #_target_system = self._CONFIG['SYSTEMS'][_target] - - # Is this a new call stream? - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): - # Record the DST TGID and Stream ID - _target_status[_slot]['TX_START'] = pkt_time - _target_status[_slot]['TX_TGID'] = _dst_id - _target_status[_slot]['TX_STREAM_ID'] = _stream_id - _target_status[_slot]['TX_RFS'] = _rf_src - _target_status[_slot]['TX_PEER'] = _peer_id - # Set other values for the contention handler to test next time there is a frame to forward - _target_status[_slot]['TX_TIME'] = pkt_time - _target_status[_slot]['TX_TYPE'] = _dtype_vseq + else: + # BEGIN STANDARD CONTENTION HANDLING + # + # 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 HBSystem, but it has been less than Group Hangtime + # From a different group than last TX to this HBSystem, but it has been less than Group Hangtime + # From the same group as the last RX from this HBSystem, but from a different subscriber, and it has been less than stream timeout + # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout + # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules + # + if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, target active or in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + continue + if ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, target in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) + continue + if (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, matching call already active on target: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + continue + if (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, DEST: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) + continue + + # Record target information if this is a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + # Record the DST TGID and Stream ID + _target_status[_slot]['TX_START'] = pkt_time + _target_status[_slot]['TX_TGID'] = _dst_id + _target_status[_slot]['TX_STREAM_ID'] = _stream_id + _target_status[_slot]['TX_RFS'] = _rf_src + _target_status[_slot]['TX_PEER'] = _peer_id + + # Set other values for the contention handler to test next time there is a frame to forward + _target_status[_slot]['TX_TIME'] = pkt_time + _target_status[_slot]['TX_TYPE'] = _dtype_vseq #send the call: systems[_target].send_system(_data) From 3893e12fcacfe56127f88a831c9e25ae9337260d Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Mon, 2 Dec 2019 08:29:52 -0600 Subject: [PATCH 06/68] OBP Support Added -- CURRENTLY UNTESTED OpenBridge support added, but not yet tested. This push is so that I can pick up my updates on another computer to test. --- bridge.py | 74 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/bridge.py b/bridge.py index 2de7ecb..5be3f6d 100755 --- a/bridge.py +++ b/bridge.py @@ -151,18 +151,18 @@ def rule_timer_loop(): logger.debug('(ROUTER) Conference Bridge ACTIVE (no change): System: %s Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) else: logger.debug('(ROUTER) Conference Bridge NO ACTION: System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) - + _then = _now - 60 remove_list = [] for unit in UNIT_MAP: if UNIT_MAP[unit][1] < (_then): remove_list.append(unit) - + for unit in remove_list: del UNIT_MAP[unit] - + logger.debug('Removed unit(s) %s from UNIT_MAP', remove_list) - + if CONFIG['REPORTS']['REPORT']: report_server.send_clients(b'bridge updated') @@ -219,7 +219,7 @@ class routerOBP(OPENBRIDGE): def __init__(self, _name, _config, _report): OPENBRIDGE.__init__(self, _name, _config, _report) self.STATUS = {} - + def group_recieved(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() dmrpkt = _data[20:53] @@ -410,13 +410,13 @@ class routerOBP(OPENBRIDGE): logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) if not removed: selflogger.error('(%s) *GROUP CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) - - + + def unit_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] - + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): @@ -428,7 +428,7 @@ class routerOBP(OPENBRIDGE): logger.debug('CSBK recieved, but HBlink does not process them currently') else: logger.error('Unknown call type recieved -- not processed') - + class routerHBP(HBSYSTEM): @@ -438,7 +438,7 @@ class routerHBP(HBSYSTEM): # list of targets for unit (subscriber, private) calls self.targets = [] - + # Status information for the system, TS1 & TS2 # 1 & 2 are "timeslot" # In TX_EMB_LC, 2-5 are burst B-E @@ -496,7 +496,7 @@ class routerHBP(HBSYSTEM): } } } - + def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): global UNIT_MAP @@ -560,7 +560,7 @@ class routerHBP(HBSYSTEM): logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) if CONFIG['REPORTS']['REPORT']: systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) - + # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time # Clear the TS bit -- all OpenBridge streams are effectively on TS1 @@ -780,26 +780,34 @@ class routerHBP(HBSYSTEM): self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, _target_route) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), _target_route).encode(encoding='utf-8', errors='ignore')) - - ''' LC CREATION NOT NECESSARY, SINCE WE'RE NOT TRANSCODING - # If we can, use the LC from the voice header as to keep all options intact - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: - decoded = decode.voice_head_term(dmrpkt) - self.STATUS[_slot]['RX_LC'] = decoded['LC'] - # If we don't have a voice header then don't wait to decode it from the Embedded LC - # just make a new one from the HBP header. This is good enough, and it saves lots of time - else: - self.STATUS[_slot]['RX_LC'] = LC_OPT + _dst_id + _rf_src - ''' - for _target in self._targets: _target_status = systems[_target].STATUS _target_system = self._CONFIG['SYSTEMS'][_target] if self._CONFIG['SYSTEMS'][_target]['MODE'] == 'OPENBRIDGE': - # THIS IS ONLY UNTIL OPENBRIDGE IS SUPPORTED - continue + if (_stream_id not in _target_status): + # This is a new call stream on the target + _target_status[_stream_id] = { + 'START': pkt_time, + 'CONTENTION':False, + 'RFS': _rf_src, + 'TGID': _dst_id, + } + + logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _target['TS'], int_id(_target['TGID'])) + if CONFIG['REPORTS']['REPORT']: + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) + + # Record the time of this packet so we can later identify a stale stream + _target_status[_stream_id]['LAST'] = pkt_time + # Clear the TS bit -- all OpenBridge streams are effectively on TS1 + _tmp_bits = _bits & ~(1 << 7) + + # Assemble transmit HBP packet + _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + _tmp_data = b''.join([_tmp_data, dmrpkt]) + else: # BEGIN STANDARD CONTENTION HANDLING @@ -827,7 +835,7 @@ class routerHBP(HBSYSTEM): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, DEST: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) continue - + # Record target information if this is a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): # Record the DST TGID and Stream ID @@ -836,11 +844,11 @@ class routerHBP(HBSYSTEM): _target_status[_slot]['TX_STREAM_ID'] = _stream_id _target_status[_slot]['TX_RFS'] = _rf_src _target_status[_slot]['TX_PEER'] = _peer_id - + # Set other values for the contention handler to test next time there is a frame to forward _target_status[_slot]['TX_TIME'] = pkt_time _target_status[_slot]['TX_TYPE'] = _dtype_vseq - + #send the call: systems[_target].send_system(_data) @@ -853,7 +861,7 @@ class routerHBP(HBSYSTEM): self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('UNIT VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) - + # Mark status variables for use later self.STATUS[_slot]['RX_PEER'] = _peer_id self.STATUS[_slot]['RX_SEQ'] = _seq @@ -862,9 +870,9 @@ class routerHBP(HBSYSTEM): self.STATUS[_slot]['RX_TGID'] = _dst_id self.STATUS[_slot]['RX_TIME'] = pkt_time self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id - - - + + + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): if _call_type == 'group': self.group_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) From 9e2d2315da08ea5e2cffa048da18cbefe2c143a0 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Mon, 2 Dec 2019 08:30:25 -0600 Subject: [PATCH 07/68] untested OBP support added Push is just to sync with a test system. --- bridge.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bridge.py b/bridge.py index 5be3f6d..6d3510a 100755 --- a/bridge.py +++ b/bridge.py @@ -795,9 +795,9 @@ class routerHBP(HBSYSTEM): 'TGID': _dst_id, } - logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _target['TS'], int_id(_target['TGID'])) + logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot, int_id(_dst_id)) if CONFIG['REPORTS']['REPORT']: - systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id).encode(encoding='utf-8', errors='ignore')) # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time @@ -844,6 +844,10 @@ class routerHBP(HBSYSTEM): _target_status[_slot]['TX_STREAM_ID'] = _stream_id _target_status[_slot]['TX_RFS'] = _rf_src _target_status[_slot]['TX_PEER'] = _peer_id + + logger.info('(%s) Unit call bridged to HBP System: %s TS: %s, UNIT: %s', self._system, _target, _slot, int_id(_dst_id)) + if CONFIG['REPORTS']['REPORT']: + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) # Set other values for the contention handler to test next time there is a frame to forward _target_status[_slot]['TX_TIME'] = pkt_time From 1bc57b5ea46aeb5e0183c779be548b19853c579b Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Mon, 2 Dec 2019 19:49:37 -0600 Subject: [PATCH 08/68] add configuration to enable unit routing --- bridge.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index 2de7ecb..7aa19a7 100755 --- a/bridge.py +++ b/bridge.py @@ -869,7 +869,8 @@ class routerHBP(HBSYSTEM): if _call_type == 'group': self.group_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) elif _call_type == 'unit': - self.unit_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) + if UNIT: + self.unit_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) elif _call_type == 'vscsbk': logger.debug('CSBK recieved, but HBlink does not process them currently') else: @@ -954,6 +955,9 @@ if __name__ == '__main__': # Build the routing rules file BRIDGES = make_bridges(rules_module.BRIDGES) + + # Get rule parameter for private calls + UNIT = rules_module.BRIDGES # INITIALIZE THE REPORTING LOOP if CONFIG['REPORTS']['REPORT']: From 76bb7f114062cc9a51b922e84fdb942cbccaec94 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Mon, 2 Dec 2019 19:56:41 -0600 Subject: [PATCH 09/68] fixed missing ) on logging line --- bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index e3449ac..f9a75c0 100755 --- a/bridge.py +++ b/bridge.py @@ -797,7 +797,7 @@ class routerHBP(HBSYSTEM): logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot, int_id(_dst_id)) if CONFIG['REPORTS']['REPORT']: - systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id).encode(encoding='utf-8', errors='ignore')) + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id).encode(encoding='utf-8', errors='ignore'))) # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time From 999bbbf5f74b1cc0da5c449871ae0ddd78249101 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Tue, 3 Dec 2019 13:11:01 -0600 Subject: [PATCH 10/68] Working Code - REPORT ERRORS! --- bridge.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/bridge.py b/bridge.py index f9a75c0..7049554 100755 --- a/bridge.py +++ b/bridge.py @@ -220,7 +220,7 @@ class routerOBP(OPENBRIDGE): OPENBRIDGE.__init__(self, _name, _config, _report) self.STATUS = {} - def group_recieved(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): + def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] @@ -797,7 +797,7 @@ class routerHBP(HBSYSTEM): logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot, int_id(_dst_id)) if CONFIG['REPORTS']['REPORT']: - systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id).encode(encoding='utf-8', errors='ignore'))) + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time @@ -806,9 +806,8 @@ class routerHBP(HBSYSTEM): # Assemble transmit HBP packet _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - _tmp_data = b''.join([_tmp_data, dmrpkt]) - - + _data = b''.join([_tmp_data, dmrpkt]) + else: # BEGIN STANDARD CONTENTION HANDLING # @@ -847,7 +846,7 @@ class routerHBP(HBSYSTEM): logger.info('(%s) Unit call bridged to HBP System: %s TS: %s, UNIT: %s', self._system, _target, _slot, int_id(_dst_id)) if CONFIG['REPORTS']['REPORT']: - systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) # Set other values for the contention handler to test next time there is a frame to forward _target_status[_slot]['TX_TIME'] = pkt_time @@ -969,7 +968,7 @@ if __name__ == '__main__': BRIDGES = make_bridges(rules_module.BRIDGES) # Get rule parameter for private calls - UNIT = rules_module.BRIDGES + UNIT = rules_module.UNIT # INITIALIZE THE REPORTING LOOP if CONFIG['REPORTS']['REPORT']: From 8bbd9ee8504513783cb67e9d1c8754327ff1c018 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 17:12:26 -0600 Subject: [PATCH 11/68] Adding inbound OBP Processing --- bridge.py | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/bridge.py b/bridge.py index 7049554..ff79d72 100755 --- a/bridge.py +++ b/bridge.py @@ -413,9 +413,135 @@ class routerOBP(OPENBRIDGE): def unit_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): + global UNIT_MAP pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] + + # Make/update this unit in the UNIT_MAP cache + UNIT_MAP[_rf_src] = (self.name, pkt_time) + + + # Is this a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + + # Collision in progress, bail out! + if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): + logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s UNIT %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) + return + + # Create a destination list for the call: + if _dst_id in UNIT_MAP: + if UNIT_MAP[_dst_id][0] != self._system: + self._targets = [UNIT_MAP[_dst_id][0]] + _target_route = UNIT_MAP[_dst_id][0] + else: + self._targets = [] + logger.debug('UNIT call to a subscriber on the same system, send nothing') + else: + self._targets = list(systems) + self._targets.remove(self._system) + _target_route = 'FLOOD' + + # This is a new call stream, so log & report + self.STATUS[_slot]['RX_START'] = pkt_time + logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT: %s (%s), TS: %s, FORWARD: %s', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, _target_route) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), _target_route).encode(encoding='utf-8', errors='ignore')) + + for _target in self._targets: + _target_status = systems[_target].STATUS + _target_system = self._CONFIG['SYSTEMS'][_target] + + if self._CONFIG['SYSTEMS'][_target]['MODE'] == 'OPENBRIDGE': + if (_stream_id not in _target_status): + # This is a new call stream on the target + _target_status[_stream_id] = { + 'START': pkt_time, + 'CONTENTION':False, + 'RFS': _rf_src, + 'TGID': _dst_id, + } + + logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot, int_id(_dst_id)) + if CONFIG['REPORTS']['REPORT']: + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) + + # Record the time of this packet so we can later identify a stale stream + _target_status[_stream_id]['LAST'] = pkt_time + # Clear the TS bit -- all OpenBridge streams are effectively on TS1 + _tmp_bits = _bits & ~(1 << 7) + + # Assemble transmit HBP packet + _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + _data = b''.join([_tmp_data, dmrpkt]) + + else: + # BEGIN STANDARD CONTENTION HANDLING + # + # 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 HBSystem, but it has been less than Group Hangtime + # From a different group than last TX to this HBSystem, but it has been less than Group Hangtime + # From the same group as the last RX from this HBSystem, but from a different subscriber, and it has been less than stream timeout + # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout + # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules + # + if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, target active or in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + continue + if ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, target in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) + continue + if (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, matching call already active on target: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + continue + if (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, DEST: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) + continue + + # Record target information if this is a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + # Record the DST TGID and Stream ID + _target_status[_slot]['TX_START'] = pkt_time + _target_status[_slot]['TX_TGID'] = _dst_id + _target_status[_slot]['TX_STREAM_ID'] = _stream_id + _target_status[_slot]['TX_RFS'] = _rf_src + _target_status[_slot]['TX_PEER'] = _peer_id + + logger.info('(%s) Unit call bridged to HBP System: %s TS: %s, UNIT: %s', self._system, _target, _slot, int_id(_dst_id)) + if CONFIG['REPORTS']['REPORT']: + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) + + # Set other values for the contention handler to test next time there is a frame to forward + _target_status[_slot]['TX_TIME'] = pkt_time + _target_status[_slot]['TX_TYPE'] = _dtype_vseq + + #send the call: + systems[_target].send_system(_data) + + + # Final actions - Is this a voice terminator? + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): + self._targets = [] + call_duration = pkt_time - self.STATUS[_slot]['RX_START'] + logger.info('(%s) *UNIT CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s, Duration: %.2f', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('UNIT VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) + + # Mark status variables for use later + self.STATUS[_slot]['RX_PEER'] = _peer_id + self.STATUS[_slot]['RX_SEQ'] = _seq + self.STATUS[_slot]['RX_RFS'] = _rf_src + self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq + self.STATUS[_slot]['RX_TGID'] = _dst_id + self.STATUS[_slot]['RX_TIME'] = pkt_time + self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): From 83299312cce14e7fbe29395e6c3de5fdba9ca803 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 17:23:04 -0600 Subject: [PATCH 12/68] Inbound OBP Unit calls --- bridge.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bridge.py b/bridge.py index ff79d72..bd84a7b 100755 --- a/bridge.py +++ b/bridge.py @@ -218,7 +218,11 @@ class routerOBP(OPENBRIDGE): def __init__(self, _name, _config, _report): OPENBRIDGE.__init__(self, _name, _config, _report) + self.name = _name self.STATUS = {} + + # list of targets for unit (subscriber, private) calls + self.targets = [] def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() From da607cd542eb626a7ab1e6504bb2159933dcf7ae Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 17:25:50 -0600 Subject: [PATCH 13/68] inbound OBP Unit calls --- bridge.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index bd84a7b..ab4d82e 100755 --- a/bridge.py +++ b/bridge.py @@ -427,7 +427,14 @@ class routerOBP(OPENBRIDGE): # Is this a new call stream? - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + if (_stream_id not in self.STATUS): + # This is a new call stream + self.STATUS[_stream_id] = { + 'START': pkt_time, + 'CONTENTION':False, + 'RFS': _rf_src, + 'TGID': _dst_id, + } # Collision in progress, bail out! if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): From 9219b4b2ee02dd0f2553619a649cc70056696c77 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 18:45:22 -0600 Subject: [PATCH 14/68] Update bridge.py --- bridge.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bridge.py b/bridge.py index ab4d82e..2d47f6b 100755 --- a/bridge.py +++ b/bridge.py @@ -435,11 +435,6 @@ class routerOBP(OPENBRIDGE): 'RFS': _rf_src, 'TGID': _dst_id, } - - # Collision in progress, bail out! - if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): - logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s UNIT %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) - return # Create a destination list for the call: if _dst_id in UNIT_MAP: From e6d25534b5fec7e3c7d67bccae72e77bb8617ad8 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 18:48:51 -0600 Subject: [PATCH 15/68] Update bridge.py --- bridge.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/bridge.py b/bridge.py index 2d47f6b..66b02dc 100755 --- a/bridge.py +++ b/bridge.py @@ -450,7 +450,7 @@ class routerOBP(OPENBRIDGE): _target_route = 'FLOOD' # This is a new call stream, so log & report - self.STATUS[_slot]['RX_START'] = pkt_time + self.STATUS['START'] = pkt_time logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT: %s (%s), TS: %s, FORWARD: %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, _target_route) if CONFIG['REPORTS']['REPORT']: @@ -540,15 +540,6 @@ class routerOBP(OPENBRIDGE): if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('UNIT VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) - # Mark status variables for use later - self.STATUS[_slot]['RX_PEER'] = _peer_id - self.STATUS[_slot]['RX_SEQ'] = _seq - self.STATUS[_slot]['RX_RFS'] = _rf_src - self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq - self.STATUS[_slot]['RX_TGID'] = _dst_id - self.STATUS[_slot]['RX_TIME'] = pkt_time - self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id - def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): From ab1a53614759db657323241e75fd91d98925d295 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 18:51:42 -0600 Subject: [PATCH 16/68] Update bridge.py --- bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index 66b02dc..6bfacd6 100755 --- a/bridge.py +++ b/bridge.py @@ -511,7 +511,7 @@ class routerOBP(OPENBRIDGE): continue # Record target information if this is a new call stream? - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + if (_stream_id not in self.STATUS): # Record the DST TGID and Stream ID _target_status[_slot]['TX_START'] = pkt_time _target_status[_slot]['TX_TGID'] = _dst_id From 0a2edc09a53d748143dd8e55e38ac58208a3f6ef Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 18:57:44 -0600 Subject: [PATCH 17/68] Update bridge.py --- bridge.py | 49 +++++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/bridge.py b/bridge.py index 6bfacd6..04cb32f 100755 --- a/bridge.py +++ b/bridge.py @@ -484,7 +484,7 @@ class routerOBP(OPENBRIDGE): _data = b''.join([_tmp_data, dmrpkt]) else: - # BEGIN STANDARD CONTENTION HANDLING + # BEGIN CONTENTION HANDLING # # 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 HBSystem, but it has been less than Group Hangtime @@ -493,22 +493,27 @@ class routerOBP(OPENBRIDGE): # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules # - if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to destination %s, target active or in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + if ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %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 ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to destination %s, target in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) + if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %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 (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to destination %s, matching call already active on target: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) - continue - if (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, DEST: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) + if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %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 (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) + continue + # Record target information if this is a new call stream? if (_stream_id not in self.STATUS): @@ -722,21 +727,21 @@ class routerHBP(HBSYSTEM): # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout # 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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) continue - if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID'])) + logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) continue - if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): + if (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) continue - if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): + if (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) continue # Is this a new call stream? From 83d120c101f6835672a5f4a6fdfee5224410b0bb Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 18:59:55 -0600 Subject: [PATCH 18/68] Revert "Update bridge.py" This reverts commit 0a2edc09a53d748143dd8e55e38ac58208a3f6ef. --- bridge.py | 49 ++++++++++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/bridge.py b/bridge.py index 04cb32f..6bfacd6 100755 --- a/bridge.py +++ b/bridge.py @@ -484,7 +484,7 @@ class routerOBP(OPENBRIDGE): _data = b''.join([_tmp_data, dmrpkt]) else: - # BEGIN CONTENTION HANDLING + # BEGIN STANDARD CONTENTION HANDLING # # 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 HBSystem, but it has been less than Group Hangtime @@ -493,27 +493,22 @@ class routerOBP(OPENBRIDGE): # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout # 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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, target active or in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) continue - if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID'])) + if ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, target in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) continue - if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + if (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, matching call already active on target: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + continue + if (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, DEST: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) continue - if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) - continue - # Record target information if this is a new call stream? if (_stream_id not in self.STATUS): @@ -727,21 +722,21 @@ class routerHBP(HBSYSTEM): # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules # - if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %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 ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) + logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %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 (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): + if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %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 (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): + if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) continue # Is this a new call stream? From 76143f718cf83df57e7b272de523904e0e3dce30 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 19:00:13 -0600 Subject: [PATCH 19/68] Revert "Update bridge.py" This reverts commit ab1a53614759db657323241e75fd91d98925d295. --- bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index 6bfacd6..66b02dc 100755 --- a/bridge.py +++ b/bridge.py @@ -511,7 +511,7 @@ class routerOBP(OPENBRIDGE): continue # Record target information if this is a new call stream? - if (_stream_id not in self.STATUS): + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): # Record the DST TGID and Stream ID _target_status[_slot]['TX_START'] = pkt_time _target_status[_slot]['TX_TGID'] = _dst_id From 053914a68badfccbfa9f7b3bd3488a64ec20f796 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 19:02:24 -0600 Subject: [PATCH 20/68] Update bridge.py --- bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index 66b02dc..6bfacd6 100755 --- a/bridge.py +++ b/bridge.py @@ -511,7 +511,7 @@ class routerOBP(OPENBRIDGE): continue # Record target information if this is a new call stream? - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + if (_stream_id not in self.STATUS): # Record the DST TGID and Stream ID _target_status[_slot]['TX_START'] = pkt_time _target_status[_slot]['TX_TGID'] = _dst_id From e00a98f7d2589c5526ba499aa6d5345dc55bafcb Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 19:05:48 -0600 Subject: [PATCH 21/68] Update bridge.py --- bridge.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/bridge.py b/bridge.py index 6bfacd6..7c455c7 100755 --- a/bridge.py +++ b/bridge.py @@ -494,20 +494,24 @@ class routerOBP(OPENBRIDGE): # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules # if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to destination %s, target active or in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) continue if ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to destination %s, target in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) continue if (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to destination %s, matching call already active on target: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) continue if (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, DEST: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) continue # Record target information if this is a new call stream? From 3d5437e9a0a08b096c26fcc08a6cfaf78fef837f Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 19:06:57 -0600 Subject: [PATCH 22/68] Update bridge.py --- bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index 7c455c7..2ea79be 100755 --- a/bridge.py +++ b/bridge.py @@ -536,7 +536,7 @@ class routerOBP(OPENBRIDGE): # Final actions - Is this a voice terminator? - if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): self._targets = [] call_duration = pkt_time - self.STATUS[_slot]['RX_START'] logger.info('(%s) *UNIT CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s, Duration: %.2f', \ From d68d3582daf3a54896068f831ea4fa55aec8ea31 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 19:07:58 -0600 Subject: [PATCH 23/68] Update bridge.py --- bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index 2ea79be..81eadb9 100755 --- a/bridge.py +++ b/bridge.py @@ -538,7 +538,7 @@ class routerOBP(OPENBRIDGE): # Final actions - Is this a voice terminator? if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): self._targets = [] - call_duration = pkt_time - self.STATUS[_slot]['RX_START'] + call_duration = pkt_time - self.STATUS['START'] logger.info('(%s) *UNIT CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s, Duration: %.2f', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) if CONFIG['REPORTS']['REPORT']: From 3cb00bc5bdb6c969728853e5b0c4d9eedb137f7f Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 19:13:13 -0600 Subject: [PATCH 24/68] Update bridge.py --- bridge.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bridge.py b/bridge.py index 81eadb9..ddc43e9 100755 --- a/bridge.py +++ b/bridge.py @@ -174,6 +174,7 @@ def stream_trimmer_loop(): _now = time() for system in systems: + print(systems[system].STATUS) # HBP systems, master and peer if CONFIG['SYSTEMS'][system]['MODE'] != 'OPENBRIDGE': for slot in range(1,3): From 96514de5cce6fa4f827d6b46bbb5ab660ec30cf0 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 15 Dec 2019 16:52:14 +0000 Subject: [PATCH 25/68] Fixed unecessary type conversion in commended debug logging at line #355 --- hblink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hblink.py b/hblink.py index fa5aaba..bd2e94f 100755 --- a/hblink.py +++ b/hblink.py @@ -352,7 +352,7 @@ class HBSYSTEM(DatagramProtocol): _frame_type = (_bits & 0x30) >> 4 _dtype_vseq = (_bits & 0xF) # data, 1=voice header, 2=voice terminator; voice, 0=burst A ... 5=burst F _stream_id = _data[16:20] - #logger.debug('(%s) DMRD - Seqence: %s, RF Source: %s, Destination ID: %s', self._system, int_id(_seq), int_id(_rf_src), int_id(_dst_id)) + #logger.debug('(%s) DMRD - Seqence: %s, RF Source: %s, Destination ID: %s', self._system, _seq, int_id(_rf_src), int_id(_dst_id)) # ACL Processing if self._CONFIG['GLOBAL']['USE_ACL']: if not acl_check(_rf_src, self._CONFIG['GLOBAL']['SUB_ACL']): From 99c76b44dee8bde66c906e2a266b826d011f319f Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 9 Feb 2020 16:33:46 -0600 Subject: [PATCH 26/68] Update bridge.py --- bridge.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/bridge.py b/bridge.py index ddc43e9..75e5061 100755 --- a/bridge.py +++ b/bridge.py @@ -174,7 +174,6 @@ def stream_trimmer_loop(): _now = time() for system in systems: - print(systems[system].STATUS) # HBP systems, master and peer if CONFIG['SYSTEMS'][system]['MODE'] != 'OPENBRIDGE': for slot in range(1,3): @@ -746,20 +745,20 @@ class routerHBP(HBSYSTEM): # Is this a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): - # Record the DST TGID and Stream ID - _target_status[_target['TS']]['TX_START'] = pkt_time - _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] - _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id - _target_status[_target['TS']]['TX_RFS'] = _rf_src - _target_status[_target['TS']]['TX_PEER'] = _peer_id - # Generate LCs (full and EMB) for the TX stream - dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + _target['TGID'] + _rf_src - _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) - _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) - _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) - logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - if CONFIG['REPORTS']['REPORT']: + # Record the DST TGID and Stream ID + _target_status[_target['TS']]['TX_START'] = pkt_time + _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] + _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id + _target_status[_target['TS']]['TX_RFS'] = _rf_src + _target_status[_target['TS']]['TX_PEER'] = _peer_id + # Generate LCs (full and EMB) for the TX stream + dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + _target['TGID'] + _rf_src + _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) + _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) + _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) + logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + if CONFIG['REPORTS']['REPORT']: systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) # Set other values for the contention handler to test next time there is a frame to forward From 28f006b49381d80e12dfd8516338402a5a099bf8 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 9 Feb 2020 17:08:32 -0600 Subject: [PATCH 27/68] Update bridge.py --- bridge.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bridge.py b/bridge.py index 75e5061..2dacd6e 100755 --- a/bridge.py +++ b/bridge.py @@ -573,7 +573,7 @@ class routerHBP(HBSYSTEM): 1: { 'RX_START': time(), 'TX_START': time(), - 'RX_SEQ': b'\x00', + 'RX_SEQ': 0, 'RX_RFS': b'\x00', 'TX_RFS': b'\x00', 'RX_PEER': b'\x00', @@ -599,7 +599,7 @@ class routerHBP(HBSYSTEM): 2: { 'RX_START': time(), 'TX_START': time(), - 'RX_SEQ': b'\x00', + 'RX_SEQ': 0, 'RX_RFS': b'\x00', 'TX_RFS': b'\x00', 'RX_PEER': b'\x00', From 7bf0a77e167952b305c64ff73965fb1109458ec5 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Thu, 28 Nov 2019 10:49:32 -0600 Subject: [PATCH 28/68] Separate UNIT from GROUP Calls Initial framework for processing UNIT calls --- bridge.py | 744 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 391 insertions(+), 353 deletions(-) diff --git a/bridge.py b/bridge.py index ae526d6..8bcf2c8 100755 --- a/bridge.py +++ b/bridge.py @@ -199,52 +199,326 @@ class routerOBP(OPENBRIDGE): def __init__(self, _name, _config, _report): OPENBRIDGE.__init__(self, _name, _config, _report) self.STATUS = {} + + def group_recieved(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): + pkt_time = time() + dmrpkt = _data[20:53] + _bits = _data[15] + + # Is this a new call stream? + if (_stream_id not in self.STATUS): + # This is a new call stream + self.STATUS[_stream_id] = { + 'START': pkt_time, + 'CONTENTION':False, + 'RFS': _rf_src, + 'TGID': _dst_id, + } + + # If we can, use the LC from the voice header as to keep all options intact + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: + decoded = decode.voice_head_term(dmrpkt) + self.STATUS[_stream_id]['LC'] = decoded['LC'] + + # If we don't have a voice header then don't wait to decode the Embedded LC + # just make a new one from the HBP header. This is good enough, and it saves lots of time + else: + self.STATUS[_stream_id]['LC'] = LC_OPT + _dst_id + _rf_src - def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('GROUP VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) + + self.STATUS[_stream_id]['LAST'] = pkt_time + + + for _bridge in BRIDGES: + for _system in BRIDGES[_bridge]: + + if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): + + for _target in BRIDGES[_bridge]: + if (_target['SYSTEM'] != self._system) and (_target['ACTIVE']): + _target_status = systems[_target['SYSTEM']].STATUS + _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] + if _target_system['MODE'] == 'OPENBRIDGE': + # Is this a new call stream on the target? + if (_stream_id not in _target_status): + # This is a new call stream on the target + _target_status[_stream_id] = { + 'START': pkt_time, + 'CONTENTION':False, + 'RFS': _rf_src, + 'TGID': _dst_id, + } + # Generate LCs (full and EMB) for the TX stream + dst_lc = b''.join([self.STATUS[_stream_id]['LC'][0:3], _target['TGID'], _rf_src]) + _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(dst_lc) + _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(dst_lc) + _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(dst_lc) + + logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + if CONFIG['REPORTS']['REPORT']: + systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) + + # Record the time of this packet so we can later identify a stale stream + _target_status[_stream_id]['LAST'] = pkt_time + # Clear the TS bit -- all OpenBridge streams are effectively on TS1 + _tmp_bits = _bits & ~(1 << 7) + + # Assemble transmit HBP packet header + _tmp_data = b''.join([_data[:8], _target['TGID'], _data[11:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET + # MUST RE-WRITE DESTINATION TGID IF DIFFERENT + # if _dst_id != rule['DST_GROUP']: + dmrbits = bitarray(endian='big') + dmrbits.frombytes(dmrpkt) + # Create a voice header packet (FULL LC) + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: + dmrbits = _target_status[_stream_id]['H_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['H_LC'][98:197] + # Create a voice terminator packet (FULL LC) + elif _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VTERM: + dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] + if CONFIG['REPORTS']['REPORT']: + call_duration = pkt_time - _target_status[_stream_id]['START'] + systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) + # Create a Burst B-E packet (Embedded LC) + elif _dtype_vseq in [1,2,3,4]: + dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] + dmrpkt = dmrbits.tobytes() + _tmp_data = b''.join([_tmp_data, dmrpkt]) + + else: + # BEGIN CONTENTION HANDLING + # + # 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 HBSystem, but it has been less than Group Hangtime + # From a different group than last TX to this HBSystem, but it has been less than Group Hangtime + # From the same group as the last RX from this HBSystem, but from a different subscriber, and it has been less than stream timeout + # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout + # 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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %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 ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %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 (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) + continue + + # Is this a new call stream? + if (_target_status[_target['TS']]['TX_STREAM_ID'] != _stream_id): + # Record the DST TGID and Stream ID + _target_status[_target['TS']]['TX_START'] = pkt_time + _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] + _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id + _target_status[_target['TS']]['TX_RFS'] = _rf_src + _target_status[_target['TS']]['TX_PEER'] = _peer_id + # Generate LCs (full and EMB) for the TX stream + dst_lc = b''.join([self.STATUS[_stream_id]['LC'][0:3], _target['TGID'], _rf_src]) + _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) + _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) + _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) + logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + if CONFIG['REPORTS']['REPORT']: + systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) + + # Set other values for the contention handler to test next time there is a frame to forward + _target_status[_target['TS']]['TX_TIME'] = pkt_time + _target_status[_target['TS']]['TX_TYPE'] = _dtype_vseq + + # Handle any necessary re-writes for the destination + if _system['TS'] != _target['TS']: + _tmp_bits = _bits ^ 1 << 7 + else: + _tmp_bits = _bits + + # Assemble transmit HBP packet header + _tmp_data = b''.join([_data[:8], _target['TGID'], _data[11:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + + # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET + # MUST RE-WRITE DESTINATION TGID IF DIFFERENT + # if _dst_id != rule['DST_GROUP']: + dmrbits = bitarray(endian='big') + dmrbits.frombytes(dmrpkt) + # Create a voice header packet (FULL LC) + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: + dmrbits = _target_status[_target['TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_H_LC'][98:197] + # Create a voice terminator packet (FULL LC) + elif _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VTERM: + dmrbits = _target_status[_target['TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_T_LC'][98:197] + if CONFIG['REPORTS']['REPORT']: + call_duration = pkt_time - _target_status[_target['TS']]['TX_START'] + systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) + # Create a Burst B-E packet (Embedded LC) + elif _dtype_vseq in [1,2,3,4]: + dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] + dmrpkt = dmrbits.tobytes() + _tmp_data = b''.join([_tmp_data, dmrpkt, b'\x00\x00']) # Add two bytes of nothing since OBP doesn't include BER & RSSI bytes #_data[53:55] + + # Transmit the packet to the destination system + systems[_target['SYSTEM']].send_system(_tmp_data) + #logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) + + + + # Final actions - Is this a voice terminator? + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): + call_duration = pkt_time - self.STATUS[_stream_id]['START'] + logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) + removed = self.STATUS.pop(_stream_id) + logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) + if not removed: + selflogger.error('(%s) *CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) + + + def unit_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + if _call_type == 'group': - # Is this a new call stream? - if (_stream_id not in self.STATUS): - # This is a new call stream - self.STATUS[_stream_id] = { - 'START': pkt_time, - 'CONTENTION':False, - 'RFS': _rf_src, - 'TGID': _dst_id, + self.group_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) + elif _call_type == 'unit': + self.unit_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) + elif _call_type == 'vscsbk': + logger.debug('CSBK recieved, but HBlink does not process them currently') + else: + logger.error('Unknown call type recieved -- not processed') + + +class routerHBP(HBSYSTEM): + + def __init__(self, _name, _config, _report): + HBSYSTEM.__init__(self, _name, _config, _report) + + # Status information for the system, TS1 & TS2 + # 1 & 2 are "timeslot" + # In TX_EMB_LC, 2-5 are burst B-E + self.STATUS = { + 1: { + 'RX_START': time(), + 'TX_START': time(), + 'RX_SEQ': b'\x00', + 'RX_RFS': b'\x00', + 'TX_RFS': b'\x00', + 'RX_PEER': b'\x00', + 'TX_PEER': b'\x00', + 'RX_STREAM_ID': b'\x00', + 'TX_STREAM_ID': b'\x00', + 'RX_TGID': b'\x00\x00\x00', + 'TX_TGID': b'\x00\x00\x00', + 'RX_TIME': time(), + 'TX_TIME': time(), + 'RX_TYPE': HBPF_SLT_VTERM, + 'TX_TYPE': HBPF_SLT_VTERM, + 'RX_LC': b'\x00', + 'TX_H_LC': b'\x00', + 'TX_T_LC': b'\x00', + 'TX_EMB_LC': { + 1: b'\x00', + 2: b'\x00', + 3: b'\x00', + 4: b'\x00', + } + }, + 2: { + 'RX_START': time(), + 'TX_START': time(), + 'RX_SEQ': b'\x00', + 'RX_RFS': b'\x00', + 'TX_RFS': b'\x00', + 'RX_PEER': b'\x00', + 'TX_PEER': b'\x00', + 'RX_STREAM_ID': b'\x00', + 'TX_STREAM_ID': b'\x00', + 'RX_TGID': b'\x00\x00\x00', + 'TX_TGID': b'\x00\x00\x00', + 'RX_TIME': time(), + 'TX_TIME': time(), + 'RX_TYPE': HBPF_SLT_VTERM, + 'TX_TYPE': HBPF_SLT_VTERM, + 'RX_LC': b'\x00', + 'TX_H_LC': b'\x00', + 'TX_T_LC': b'\x00', + 'TX_EMB_LC': { + 1: b'\x00', + 2: b'\x00', + 3: b'\x00', + 4: b'\x00', + } } - - # If we can, use the LC from the voice header as to keep all options intact - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: - decoded = decode.voice_head_term(dmrpkt) - self.STATUS[_stream_id]['LC'] = decoded['LC'] - - # If we don't have a voice header then don't wait to decode the Embedded LC - # just make a new one from the HBP header. This is good enough, and it saves lots of time - else: - self.STATUS[_stream_id]['LC'] = LC_OPT + _dst_id + _rf_src + } + + # Dictionary for dynamically mapping unit (subscriber) to a system. + # This is for pruning unit-to-uint calls to not broadcast once the + # target system for a unit is identified + # format 'unit_id': ('SYSTEM', time) + self.UNIT_MAP = {} + - logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) - if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('GROUP VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) + def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): + pkt_time = time() + dmrpkt = _data[20:53] + _bits = _data[15] - self.STATUS[_stream_id]['LAST'] = pkt_time + # Is this a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): + logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s TGID %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) + return + # This is a new call stream + self.STATUS[_slot]['RX_START'] = pkt_time + logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('GROUP VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) - for _bridge in BRIDGES: - for _system in BRIDGES[_bridge]: + # If we can, use the LC from the voice header as to keep all options intact + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: + decoded = decode.voice_head_term(dmrpkt) + self.STATUS[_slot]['RX_LC'] = decoded['LC'] - if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): + # If we don't have a voice header then don't wait to decode it from the Embedded LC + # just make a new one from the HBP header. This is good enough, and it saves lots of time + else: + self.STATUS[_slot]['RX_LC'] = LC_OPT + _dst_id + _rf_src - for _target in BRIDGES[_bridge]: - if (_target['SYSTEM'] != self._system) and (_target['ACTIVE']): + for _bridge in BRIDGES: + for _system in BRIDGES[_bridge]: + + if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): + + for _target in BRIDGES[_bridge]: + if _target['SYSTEM'] != self._system: + if _target['ACTIVE']: _target_status = systems[_target['SYSTEM']].STATUS _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] + if _target_system['MODE'] == 'OPENBRIDGE': # Is this a new call stream on the target? if (_stream_id not in _target_status): @@ -256,7 +530,7 @@ class routerOBP(OPENBRIDGE): 'TGID': _dst_id, } # Generate LCs (full and EMB) for the TX stream - dst_lc = b''.join([self.STATUS[_stream_id]['LC'][0:3], _target['TGID'], _rf_src]) + dst_lc = b''.join([self.STATUS[_slot]['RX_LC'][0:3], _target['TGID'], _rf_src]) _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(dst_lc) _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(dst_lc) _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(dst_lc) @@ -264,7 +538,7 @@ class routerOBP(OPENBRIDGE): logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) if CONFIG['REPORTS']['REPORT']: systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) - + # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time # Clear the TS bit -- all OpenBridge streams are effectively on TS1 @@ -294,7 +568,7 @@ class routerOBP(OPENBRIDGE): _tmp_data = b''.join([_tmp_data, dmrpkt]) else: - # BEGIN CONTENTION HANDLING + # BEGIN STANDARD CONTENTION HANDLING # # 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 HBSystem, but it has been less than Group Hangtime @@ -304,28 +578,24 @@ class routerOBP(OPENBRIDGE): # 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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %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 ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %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 (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) continue # Is this a new call stream? - if (_target_status[_target['TS']]['TX_STREAM_ID'] != _stream_id): + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): # Record the DST TGID and Stream ID _target_status[_target['TS']]['TX_START'] = pkt_time _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] @@ -333,14 +603,14 @@ class routerOBP(OPENBRIDGE): _target_status[_target['TS']]['TX_RFS'] = _rf_src _target_status[_target['TS']]['TX_PEER'] = _peer_id # Generate LCs (full and EMB) for the TX stream - dst_lc = b''.join([self.STATUS[_stream_id]['LC'][0:3], _target['TGID'], _rf_src]) + dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + _target['TGID'] + _rf_src _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) if CONFIG['REPORTS']['REPORT']: - systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) + systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) # Set other values for the contention handler to test next time there is a frame to forward _target_status[_target['TS']]['TX_TIME'] = pkt_time @@ -373,7 +643,7 @@ class routerOBP(OPENBRIDGE): elif _dtype_vseq in [1,2,3,4]: dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] dmrpkt = dmrbits.tobytes() - _tmp_data = b''.join([_tmp_data, dmrpkt, b'\x00\x00']) # Add two bytes of nothing since OBP doesn't include BER & RSSI bytes #_data[53:55] + _tmp_data = b''.join([_tmp_data, dmrpkt, _data[53:55]]) # Transmit the packet to the destination system systems[_target['SYSTEM']].send_system(_tmp_data) @@ -381,327 +651,95 @@ class routerOBP(OPENBRIDGE): - # Final actions - Is this a voice terminator? - if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): - call_duration = pkt_time - self.STATUS[_stream_id]['START'] - logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) - if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) - removed = self.STATUS.pop(_stream_id) - logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) - if not removed: - selflogger.error('(%s) *CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) + # Final actions - Is this a voice terminator? + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): + call_duration = pkt_time - self.STATUS[_slot]['RX_START'] + logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) -class routerHBP(HBSYSTEM): + # + # Begin in-band signalling for call end. This has nothign to do with routing traffic directly. + # - def __init__(self, _name, _config, _report): - HBSYSTEM.__init__(self, _name, _config, _report) - - # Status information for the system, TS1 & TS2 - # 1 & 2 are "timeslot" - # In TX_EMB_LC, 2-5 are burst B-E - self.STATUS = { - 1: { - 'RX_START': time(), - 'TX_START': time(), - 'RX_SEQ': 0, - 'RX_RFS': b'\x00', - 'TX_RFS': b'\x00', - 'RX_PEER': b'\x00', - 'TX_PEER': b'\x00', - 'RX_STREAM_ID': b'\x00', - 'TX_STREAM_ID': b'\x00', - 'RX_TGID': b'\x00\x00\x00', - 'TX_TGID': b'\x00\x00\x00', - 'RX_TIME': time(), - 'TX_TIME': time(), - 'RX_TYPE': HBPF_SLT_VTERM, - 'TX_TYPE': HBPF_SLT_VTERM, - 'RX_LC': b'\x00', - 'TX_H_LC': b'\x00', - 'TX_T_LC': b'\x00', - 'TX_EMB_LC': { - 1: b'\x00', - 2: b'\x00', - 3: b'\x00', - 4: b'\x00', - } - }, - 2: { - 'RX_START': time(), - 'TX_START': time(), - 'RX_SEQ': 0, - 'RX_RFS': b'\x00', - 'TX_RFS': b'\x00', - 'RX_PEER': b'\x00', - 'TX_PEER': b'\x00', - 'RX_STREAM_ID': b'\x00', - 'TX_STREAM_ID': b'\x00', - 'RX_TGID': b'\x00\x00\x00', - 'TX_TGID': b'\x00\x00\x00', - 'RX_TIME': time(), - 'TX_TIME': time(), - 'RX_TYPE': HBPF_SLT_VTERM, - 'TX_TYPE': HBPF_SLT_VTERM, - 'RX_LC': b'\x00', - 'TX_H_LC': b'\x00', - 'TX_T_LC': b'\x00', - 'TX_EMB_LC': { - 1: b'\x00', - 2: b'\x00', - 3: b'\x00', - 4: b'\x00', - } - } - } - - def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): - pkt_time = time() - dmrpkt = _data[20:53] - _bits = _data[15] - - if _call_type == 'group': - - # Is this a new call stream? - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): - if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): - logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s TGID %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) - return - - # This is a new call stream - self.STATUS[_slot]['RX_START'] = pkt_time - logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) - if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('GROUP VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) - - # If we can, use the LC from the voice header as to keep all options intact - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: - decoded = decode.voice_head_term(dmrpkt) - self.STATUS[_slot]['RX_LC'] = decoded['LC'] - - # If we don't have a voice header then don't wait to decode it from the Embedded LC - # just make a new one from the HBP header. This is good enough, and it saves lots of time - else: - self.STATUS[_slot]['RX_LC'] = LC_OPT + _dst_id + _rf_src + # Iterate the rules dictionary for _bridge in BRIDGES: for _system in BRIDGES[_bridge]: + if _system['SYSTEM'] == self._system: - if (_system['SYSTEM'] == self._system and _system['TGID'] == _dst_id and _system['TS'] == _slot and _system['ACTIVE'] == True): + # TGID matches a rule source, reset its timer + if _slot == _system['TS'] and _dst_id == _system['TGID'] and ((_system['TO_TYPE'] == 'ON' and (_system['ACTIVE'] == True)) or (_system['TO_TYPE'] == 'OFF' and _system['ACTIVE'] == False)): + _system['TIMER'] = pkt_time + _system['TIMEOUT'] + logger.info('(%s) Transmission match for Bridge: %s. Reset timeout to %s', self._system, _bridge, _system['TIMER']) - for _target in BRIDGES[_bridge]: - if _target['SYSTEM'] != self._system: - if _target['ACTIVE']: - _target_status = systems[_target['SYSTEM']].STATUS - _target_system = self._CONFIG['SYSTEMS'][_target['SYSTEM']] - - if _target_system['MODE'] == 'OPENBRIDGE': - # Is this a new call stream on the target? - if (_stream_id not in _target_status): - # This is a new call stream on the target - _target_status[_stream_id] = { - 'START': pkt_time, - 'CONTENTION':False, - 'RFS': _rf_src, - 'TGID': _dst_id, - } - # Generate LCs (full and EMB) for the TX stream - dst_lc = b''.join([self.STATUS[_slot]['RX_LC'][0:3], _target['TGID'], _rf_src]) - _target_status[_stream_id]['H_LC'] = bptc.encode_header_lc(dst_lc) - _target_status[_stream_id]['T_LC'] = bptc.encode_terminator_lc(dst_lc) - _target_status[_stream_id]['EMB_LC'] = bptc.encode_emblc(dst_lc) - - logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - if CONFIG['REPORTS']['REPORT']: - systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) - - # Record the time of this packet so we can later identify a stale stream - _target_status[_stream_id]['LAST'] = pkt_time - # Clear the TS bit -- all OpenBridge streams are effectively on TS1 - _tmp_bits = _bits & ~(1 << 7) - - # Assemble transmit HBP packet header - _tmp_data = b''.join([_data[:8], _target['TGID'], _data[11:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - - # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET - # MUST RE-WRITE DESTINATION TGID IF DIFFERENT - # if _dst_id != rule['DST_GROUP']: - dmrbits = bitarray(endian='big') - dmrbits.frombytes(dmrpkt) - # Create a voice header packet (FULL LC) - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: - dmrbits = _target_status[_stream_id]['H_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['H_LC'][98:197] - # Create a voice terminator packet (FULL LC) - elif _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VTERM: - dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] - if CONFIG['REPORTS']['REPORT']: - call_duration = pkt_time - _target_status[_stream_id]['START'] - systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) - # Create a Burst B-E packet (Embedded LC) - elif _dtype_vseq in [1,2,3,4]: - dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] - dmrpkt = dmrbits.tobytes() - _tmp_data = b''.join([_tmp_data, dmrpkt]) - - else: - # BEGIN STANDARD CONTENTION HANDLING - # - # 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 HBSystem, but it has been less than Group Hangtime - # From a different group than last TX to this HBSystem, but it has been less than Group Hangtime - # From the same group as the last RX from this HBSystem, but from a different subscriber, and it has been less than stream timeout - # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout - # 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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %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 ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %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 (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) - continue - - # Is this a new call stream? - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): - # Record the DST TGID and Stream ID - _target_status[_target['TS']]['TX_START'] = pkt_time - _target_status[_target['TS']]['TX_TGID'] = _target['TGID'] - _target_status[_target['TS']]['TX_STREAM_ID'] = _stream_id - _target_status[_target['TS']]['TX_RFS'] = _rf_src - _target_status[_target['TS']]['TX_PEER'] = _peer_id - # Generate LCs (full and EMB) for the TX stream - dst_lc = self.STATUS[_slot]['RX_LC'][0:3] + _target['TGID'] + _rf_src - _target_status[_target['TS']]['TX_H_LC'] = bptc.encode_header_lc(dst_lc) - _target_status[_target['TS']]['TX_T_LC'] = bptc.encode_terminator_lc(dst_lc) - _target_status[_target['TS']]['TX_EMB_LC'] = bptc.encode_emblc(dst_lc) - logger.debug('(%s) Generating TX FULL and EMB LCs for HomeBrew destination: System: %s, TS: %s, TGID: %s', self._system, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - logger.info('(%s) Conference Bridge: %s, Call Bridged to HBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - if CONFIG['REPORTS']['REPORT']: - systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) - - # Set other values for the contention handler to test next time there is a frame to forward - _target_status[_target['TS']]['TX_TIME'] = pkt_time - _target_status[_target['TS']]['TX_TYPE'] = _dtype_vseq - - # Handle any necessary re-writes for the destination - if _system['TS'] != _target['TS']: - _tmp_bits = _bits ^ 1 << 7 - else: - _tmp_bits = _bits - - # Assemble transmit HBP packet header - _tmp_data = b''.join([_data[:8], _target['TGID'], _data[11:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - - # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET - # MUST RE-WRITE DESTINATION TGID IF DIFFERENT - # if _dst_id != rule['DST_GROUP']: - dmrbits = bitarray(endian='big') - dmrbits.frombytes(dmrpkt) - # Create a voice header packet (FULL LC) - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: - dmrbits = _target_status[_target['TS']]['TX_H_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_H_LC'][98:197] - # Create a voice terminator packet (FULL LC) - elif _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VTERM: - dmrbits = _target_status[_target['TS']]['TX_T_LC'][0:98] + dmrbits[98:166] + _target_status[_target['TS']]['TX_T_LC'][98:197] - if CONFIG['REPORTS']['REPORT']: - call_duration = pkt_time - _target_status[_target['TS']]['TX_START'] - systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) - # Create a Burst B-E packet (Embedded LC) - elif _dtype_vseq in [1,2,3,4]: - dmrbits = dmrbits[0:116] + _target_status[_target['TS']]['TX_EMB_LC'][_dtype_vseq] + dmrbits[148:264] - dmrpkt = dmrbits.tobytes() - _tmp_data = b''.join([_tmp_data, dmrpkt, _data[53:55]]) - - # Transmit the packet to the destination system - systems[_target['SYSTEM']].send_system(_tmp_data) - #logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - - - - # Final actions - Is this a voice terminator? - if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): - call_duration = pkt_time - self.STATUS[_slot]['RX_START'] - logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) - if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) - - # - # Begin in-band signalling for call end. This has nothign to do with routing traffic directly. - # - - # Iterate the rules dictionary - - for _bridge in BRIDGES: - for _system in BRIDGES[_bridge]: - if _system['SYSTEM'] == self._system: - - # TGID matches a rule source, reset its timer - if _slot == _system['TS'] and _dst_id == _system['TGID'] and ((_system['TO_TYPE'] == 'ON' and (_system['ACTIVE'] == True)) or (_system['TO_TYPE'] == 'OFF' and _system['ACTIVE'] == False)): + # TGID matches an ACTIVATION trigger + if (_dst_id in _system['ON'] or _dst_id in _system['RESET']) and _slot == _system['TS']: + # Set the matching rule as ACTIVE + if _dst_id in _system['ON']: + if _system['ACTIVE'] == False: + _system['ACTIVE'] = True + _system['TIMER'] = pkt_time + _system['TIMEOUT'] + 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'] = pkt_time + 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'] = pkt_time + _system['TIMEOUT'] - logger.info('(%s) Transmission match for Bridge: %s. Reset timeout to %s', self._system, _bridge, _system['TIMER']) + logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) - # TGID matches an ACTIVATION trigger - if (_dst_id in _system['ON'] or _dst_id in _system['RESET']) and _slot == _system['TS']: - # Set the matching rule as ACTIVE - if _dst_id in _system['ON']: - if _system['ACTIVE'] == False: - _system['ACTIVE'] = True - _system['TIMER'] = pkt_time + _system['TIMEOUT'] - 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'] = pkt_time - 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'] = pkt_time + _system['TIMEOUT'] - logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) + # TGID matches an DE-ACTIVATION trigger + if (_dst_id in _system['OFF'] or _dst_id in _system['RESET']) and _slot == _system['TS']: + # Set the matching rule as ACTIVE + if _dst_id in _system['OFF']: + if _system['ACTIVE'] == True: + _system['ACTIVE'] = False + 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'] = pkt_time + 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'] = pkt_time + _system['TIMEOUT'] + logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) + # Cancel the timer if we've enabled an "ON" type timeout + if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON' and _dst_group in _system['OFF']: + _system['TIMER'] = pkt_time + logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge) - # TGID matches an DE-ACTIVATION trigger - if (_dst_id in _system['OFF'] or _dst_id in _system['RESET']) and _slot == _system['TS']: - # Set the matching rule as ACTIVE - if _dst_id in _system['OFF']: - if _system['ACTIVE'] == True: - _system['ACTIVE'] = False - 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'] = pkt_time - 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'] = pkt_time + _system['TIMEOUT'] - logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) - # Cancel the timer if we've enabled an "ON" type timeout - if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON' and _dst_group in _system['OFF']: - _system['TIMER'] = pkt_time - logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge) - - # - # END IN-BAND SIGNALLING - # + # + # END IN-BAND SIGNALLING + # + # Mark status variables for use later + self.STATUS[_slot]['RX_PEER'] = _peer_id + self.STATUS[_slot]['RX_SEQ'] = _seq + self.STATUS[_slot]['RX_RFS'] = _rf_src + self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq + self.STATUS[_slot]['RX_TGID'] = _dst_id + self.STATUS[_slot]['RX_TIME'] = pkt_time + self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id - # Mark status variables for use later - self.STATUS[_slot]['RX_PEER'] = _peer_id - self.STATUS[_slot]['RX_SEQ'] = _seq - self.STATUS[_slot]['RX_RFS'] = _rf_src - self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq - self.STATUS[_slot]['RX_TGID'] = _dst_id - self.STATUS[_slot]['RX_TIME'] = pkt_time - self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id + def unit_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): + pkt_time = time() + dmrpkt = _data[20:53] + _bits = _data[15] + print('UNIT CALL') + + + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + if _call_type == 'group': + self.group_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) + elif _call_type == 'unit': + self.unit_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) + elif _call_type == 'vscsbk': + logger.debug('CSBK recieved, but HBlink does not process them currently') + else: + logger.error('Unknown call type recieved -- not processed') # # Socket-based reporting section From 887657aa16e7472f57669fee2dff688856221885 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Fri, 29 Nov 2019 10:35:27 -0600 Subject: [PATCH 29/68] Track subscribers this is not yet really useable, just getting the mechanics started --- bridge.py | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/bridge.py b/bridge.py index 8bcf2c8..ab8696d 100755 --- a/bridge.py +++ b/bridge.py @@ -67,6 +67,13 @@ __email__ = 'n0mjs@me.com' # Module gobal varaibles +# Dictionary for dynamically mapping unit (subscriber) to a system. +# This is for pruning unit-to-uint calls to not broadcast once the +# target system for a unit is identified +# format 'unit_id': ('SYSTEM', time) +UNIT_MAP = {} + + # Timed loop used for reporting HBP status # # REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE @@ -116,6 +123,7 @@ def make_bridges(_rules): # Run this every minute for rule timer updates def rule_timer_loop(): + global UNIT_MAP logger.debug('(ROUTER) routerHBP Rule timer loop started') _now = time() @@ -143,6 +151,18 @@ def rule_timer_loop(): logger.debug('(ROUTER) Conference Bridge ACTIVE (no change): System: %s Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) else: logger.debug('(ROUTER) Conference Bridge NO ACTION: System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) + + _then = _now - 60 + remove_list = [] + for unit in UNIT_MAP: + if UNIT_MAP[unit][1] < (_then): + remove_list.append(unit) + + for unit in remove_list: + del UNIT_MAP[unit] + + logger.debug('Removed unit(s) %s from UNIT_MAP', remove_list) + if CONFIG['REPORTS']['REPORT']: report_server.send_clients(b'bridge updated') @@ -396,6 +416,7 @@ class routerOBP(OPENBRIDGE): pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): @@ -413,6 +434,7 @@ class routerHBP(HBSYSTEM): def __init__(self, _name, _config, _report): HBSYSTEM.__init__(self, _name, _config, _report) + self.name = _name # Status information for the system, TS1 & TS2 # 1 & 2 are "timeslot" @@ -471,14 +493,7 @@ class routerHBP(HBSYSTEM): } } } - - # Dictionary for dynamically mapping unit (subscriber) to a system. - # This is for pruning unit-to-uint calls to not broadcast once the - # target system for a unit is identified - # format 'unit_id': ('SYSTEM', time) - self.UNIT_MAP = {} - - + def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() @@ -725,10 +740,16 @@ class routerHBP(HBSYSTEM): def unit_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): + global UNIT_MAP pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] - print('UNIT CALL') + print(UNIT_MAP) + if _rf_src not in UNIT_MAP: + UNIT_MAP[_rf_src] = [self.name, pkt_time] + else: + UNIT_MAP[_rf_src][1] = pkt_time + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): From 112143ec0538af6d0584969df4c0bbbeb2608cdd Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 1 Dec 2019 10:06:08 -0600 Subject: [PATCH 30/68] incremental update - Not Working the program runs at ths point, logging is updated for group calls, no errors in unit sectinon, but no contention handler and send routines not yet built. --- bridge.py | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 9 deletions(-) diff --git a/bridge.py b/bridge.py index ab8696d..f02057a 100755 --- a/bridge.py +++ b/bridge.py @@ -246,7 +246,7 @@ class routerOBP(OPENBRIDGE): self.STATUS[_stream_id]['LC'] = LC_OPT + _dst_id + _rf_src - logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ + logger.info('(%s) *GROUP CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) @@ -402,14 +402,14 @@ class routerOBP(OPENBRIDGE): # Final actions - Is this a voice terminator? if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): call_duration = pkt_time - self.STATUS[_stream_id]['START'] - logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ + logger.info('(%s) *GROUP CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) removed = self.STATUS.pop(_stream_id) logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) if not removed: - selflogger.error('(%s) *CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) + selflogger.error('(%s) *GROUP CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) def unit_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): @@ -496,9 +496,16 @@ class routerHBP(HBSYSTEM): def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): + global UNIT_MAP pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] + + # Make an entry in the UNIT_MAP for this subscriber + if _rf_src not in UNIT_MAP: + UNIT_MAP[_rf_src] = [self.name, pkt_time] + else: + UNIT_MAP[_rf_src][1] = pkt_time # Is this a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): @@ -508,7 +515,7 @@ class routerHBP(HBSYSTEM): # This is a new call stream self.STATUS[_slot]['RX_START'] = pkt_time - logger.info('(%s) *CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ + logger.info('(%s) *GROUP CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) @@ -640,9 +647,6 @@ class routerHBP(HBSYSTEM): # Assemble transmit HBP packet header _tmp_data = b''.join([_data[:8], _target['TGID'], _data[11:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - # MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET - # MUST RE-WRITE DESTINATION TGID IF DIFFERENT - # if _dst_id != rule['DST_GROUP']: dmrbits = bitarray(endian='big') dmrbits.frombytes(dmrpkt) # Create a voice header packet (FULL LC) @@ -669,7 +673,7 @@ class routerHBP(HBSYSTEM): # Final actions - Is this a voice terminator? if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): call_duration = pkt_time - self.STATUS[_slot]['RX_START'] - logger.info('(%s) *CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ + logger.info('(%s) *GROUP CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s, Duration: %.2f', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) @@ -744,12 +748,88 @@ class routerHBP(HBSYSTEM): pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] - print(UNIT_MAP) + if _rf_src not in UNIT_MAP: UNIT_MAP[_rf_src] = [self.name, pkt_time] else: UNIT_MAP[_rf_src][1] = pkt_time + # Is this a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): + logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s UNIT %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) + return + + # This is a new call stream + self.STATUS[_slot]['RX_START'] = pkt_time + logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) + + # If we can, use the LC from the voice header as to keep all options intact + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: + decoded = decode.voice_head_term(dmrpkt) + self.STATUS[_slot]['RX_LC'] = decoded['LC'] + + # If we don't have a voice header then don't wait to decode it from the Embedded LC + # just make a new one from the HBP header. This is good enough, and it saves lots of time + else: + self.STATUS[_slot]['RX_LC'] = LC_OPT + _dst_id + _rf_src + + + #if _dst_id in UNIT_MAP: + if True: + _target = UNIT_MAP[_dst_id][0] + + #if _target != self._system: + if True: + _target_status = systems[_target].STATUS + _target_system = self._CONFIG['SYSTEMS'][_target] + + + # Is this a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + # Record the DST TGID and Stream ID + _target_status[_slot]['TX_START'] = pkt_time + _target_status[_slot]['TX_TGID'] = _dst_id + _target_status[_slot]['TX_STREAM_ID'] = _stream_id + _target_status[_slot]['TX_RFS'] = _rf_src + _target_status[_slot]['TX_PEER'] = _peer_id + + # Set other values for the contention handler to test next time there is a frame to forward + _target_status[_slot]['TX_TIME'] = pkt_time + _target_status[_slot]['TX_TYPE'] = _dtype_vseq + + #send the call: + + systems[_target].send_system(_data) + + + else: + pass + #for target in systems: + # systems[target].send_system(_data) + + + + # Final actions - Is this a voice terminator? + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): + call_duration = pkt_time - self.STATUS[_slot]['RX_START'] + logger.info('(%s) *UNIT CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s, Duration: %.2f', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('UNIT VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) + + # Mark status variables for use later + self.STATUS[_slot]['RX_PEER'] = _peer_id + self.STATUS[_slot]['RX_SEQ'] = _seq + self.STATUS[_slot]['RX_RFS'] = _rf_src + self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq + self.STATUS[_slot]['RX_TGID'] = _dst_id + self.STATUS[_slot]['RX_TIME'] = pkt_time + self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): From 0f38427f2f2359ad610e3743f794c78c3cf1a4e7 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 1 Dec 2019 11:20:14 -0600 Subject: [PATCH 31/68] Works, but no contention handler Private call forwarding works, with destionation routing cache, but no contention handler yet. --- bridge.py | 80 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/bridge.py b/bridge.py index f02057a..68202f9 100755 --- a/bridge.py +++ b/bridge.py @@ -436,6 +436,9 @@ class routerHBP(HBSYSTEM): HBSYSTEM.__init__(self, _name, _config, _report) self.name = _name + # list of targets for unit (subscriber, private) calls + self.targets = [] + # Status information for the system, TS1 & TS2 # 1 & 2 are "timeslot" # In TX_EMB_LC, 2-5 are burst B-E @@ -756,16 +759,29 @@ class routerHBP(HBSYSTEM): # Is this a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + # Create a destination list for the call: + if _dst_id in UNIT_MAP: + if UNIT_MAP[_dst_id][0] != self._system: + self._targets = [UNIT_MAP[_dst_id][0]] + _target_route = UNIT_MAP[_dst_id][0] + else: + self._targets = [] + logger.debug('UNIT call to a subscriber on the same system, send nothing') + else: + self._targets = list(systems) + self._targets.remove(self._system) + _target_route = 'FLOOD' + if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s UNIT %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) return # This is a new call stream self.STATUS[_slot]['RX_START'] = pkt_time - logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) + logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT: %s (%s), TS: %s, FORWARD: %s', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, _target_route) if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) + self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), _target_route).encode(encoding='utf-8', errors='ignore')) # If we can, use the LC from the voice header as to keep all options intact if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: @@ -776,45 +792,35 @@ class routerHBP(HBSYSTEM): # just make a new one from the HBP header. This is good enough, and it saves lots of time else: self.STATUS[_slot]['RX_LC'] = LC_OPT + _dst_id + _rf_src - - - #if _dst_id in UNIT_MAP: - if True: - _target = UNIT_MAP[_dst_id][0] + + for _target in self._targets: + if self._CONFIG['SYSTEMS'][_target]['MODE'] == 'OPENBRIDGE': + # THIS IS ONLY UNTIL OPENBRIDGE IS SUPPORTED + continue + + _target_status = systems[_target].STATUS + #_target_system = self._CONFIG['SYSTEMS'][_target] + + # Is this a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + # Record the DST TGID and Stream ID + _target_status[_slot]['TX_START'] = pkt_time + _target_status[_slot]['TX_TGID'] = _dst_id + _target_status[_slot]['TX_STREAM_ID'] = _stream_id + _target_status[_slot]['TX_RFS'] = _rf_src + _target_status[_slot]['TX_PEER'] = _peer_id - #if _target != self._system: - if True: - _target_status = systems[_target].STATUS - _target_system = self._CONFIG['SYSTEMS'][_target] - - - # Is this a new call stream? - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): - # Record the DST TGID and Stream ID - _target_status[_slot]['TX_START'] = pkt_time - _target_status[_slot]['TX_TGID'] = _dst_id - _target_status[_slot]['TX_STREAM_ID'] = _stream_id - _target_status[_slot]['TX_RFS'] = _rf_src - _target_status[_slot]['TX_PEER'] = _peer_id - - # Set other values for the contention handler to test next time there is a frame to forward - _target_status[_slot]['TX_TIME'] = pkt_time - _target_status[_slot]['TX_TYPE'] = _dtype_vseq - - #send the call: + # Set other values for the contention handler to test next time there is a frame to forward + _target_status[_slot]['TX_TIME'] = pkt_time + _target_status[_slot]['TX_TYPE'] = _dtype_vseq - systems[_target].send_system(_data) - - - else: - pass - #for target in systems: - # systems[target].send_system(_data) - - + #send the call: + systems[_target].send_system(_data) + # Final actions - Is this a voice terminator? if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): + self._targets = [] call_duration = pkt_time - self.STATUS[_slot]['RX_START'] logger.info('(%s) *UNIT CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s, Duration: %.2f', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) From 281860db0b695e25220fb66afb54c87e720cccbb Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 1 Dec 2019 13:27:07 -0600 Subject: [PATCH 32/68] unit to unit calls, with contention working - HBSystems only This appears to work, but no openbridge support yet. --- bridge.py | 89 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 31 deletions(-) diff --git a/bridge.py b/bridge.py index 68202f9..d5e1d31 100755 --- a/bridge.py +++ b/bridge.py @@ -504,11 +504,8 @@ class routerHBP(HBSYSTEM): dmrpkt = _data[20:53] _bits = _data[15] - # Make an entry in the UNIT_MAP for this subscriber - if _rf_src not in UNIT_MAP: - UNIT_MAP[_rf_src] = [self.name, pkt_time] - else: - UNIT_MAP[_rf_src][1] = pkt_time + # Make/update an entry in the UNIT_MAP for this subscriber + UNIT_MAP[_rf_src] = (self.name, pkt_time) # Is this a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): @@ -752,13 +749,18 @@ class routerHBP(HBSYSTEM): dmrpkt = _data[20:53] _bits = _data[15] - if _rf_src not in UNIT_MAP: - UNIT_MAP[_rf_src] = [self.name, pkt_time] - else: - UNIT_MAP[_rf_src][1] = pkt_time + # Make/update this unit in the UNIT_MAP cache + UNIT_MAP[_rf_src] = (self.name, pkt_time) + # Is this a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + + # Collision in progress, bail out! + if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): + logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s UNIT %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) + return + # Create a destination list for the call: if _dst_id in UNIT_MAP: if UNIT_MAP[_dst_id][0] != self._system: @@ -772,17 +774,14 @@ class routerHBP(HBSYSTEM): self._targets.remove(self._system) _target_route = 'FLOOD' - if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): - logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s UNIT %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) - return - - # This is a new call stream + # This is a new call stream, so log & report self.STATUS[_slot]['RX_START'] = pkt_time logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT: %s (%s), TS: %s, FORWARD: %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, _target_route) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), _target_route).encode(encoding='utf-8', errors='ignore')) - + + ''' LC CREATION NOT NECESSARY, SINCE WE'RE NOT TRANSCODING # If we can, use the LC from the voice header as to keep all options intact if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: decoded = decode.voice_head_term(dmrpkt) @@ -792,27 +791,55 @@ class routerHBP(HBSYSTEM): # just make a new one from the HBP header. This is good enough, and it saves lots of time else: self.STATUS[_slot]['RX_LC'] = LC_OPT + _dst_id + _rf_src - + ''' + for _target in self._targets: + _target_status = systems[_target].STATUS + _target_system = self._CONFIG['SYSTEMS'][_target] + if self._CONFIG['SYSTEMS'][_target]['MODE'] == 'OPENBRIDGE': # THIS IS ONLY UNTIL OPENBRIDGE IS SUPPORTED continue - - _target_status = systems[_target].STATUS - #_target_system = self._CONFIG['SYSTEMS'][_target] - - # Is this a new call stream? - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): - # Record the DST TGID and Stream ID - _target_status[_slot]['TX_START'] = pkt_time - _target_status[_slot]['TX_TGID'] = _dst_id - _target_status[_slot]['TX_STREAM_ID'] = _stream_id - _target_status[_slot]['TX_RFS'] = _rf_src - _target_status[_slot]['TX_PEER'] = _peer_id - # Set other values for the contention handler to test next time there is a frame to forward - _target_status[_slot]['TX_TIME'] = pkt_time - _target_status[_slot]['TX_TYPE'] = _dtype_vseq + else: + # BEGIN STANDARD CONTENTION HANDLING + # + # 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 HBSystem, but it has been less than Group Hangtime + # From a different group than last TX to this HBSystem, but it has been less than Group Hangtime + # From the same group as the last RX from this HBSystem, but from a different subscriber, and it has been less than stream timeout + # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout + # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules + # + if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, target active or in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + continue + if ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, target in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) + continue + if (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, matching call already active on target: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + continue + if (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, DEST: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) + continue + + # Record target information if this is a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + # Record the DST TGID and Stream ID + _target_status[_slot]['TX_START'] = pkt_time + _target_status[_slot]['TX_TGID'] = _dst_id + _target_status[_slot]['TX_STREAM_ID'] = _stream_id + _target_status[_slot]['TX_RFS'] = _rf_src + _target_status[_slot]['TX_PEER'] = _peer_id + + # Set other values for the contention handler to test next time there is a frame to forward + _target_status[_slot]['TX_TIME'] = pkt_time + _target_status[_slot]['TX_TYPE'] = _dtype_vseq #send the call: systems[_target].send_system(_data) From 836c655739ed1721f911ad3fc6b82a7b8b64b55e Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Mon, 2 Dec 2019 19:49:37 -0600 Subject: [PATCH 33/68] add configuration to enable unit routing --- bridge.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index d5e1d31..ceb3a08 100755 --- a/bridge.py +++ b/bridge.py @@ -869,7 +869,8 @@ class routerHBP(HBSYSTEM): if _call_type == 'group': self.group_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) elif _call_type == 'unit': - self.unit_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) + if UNIT: + self.unit_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) elif _call_type == 'vscsbk': logger.debug('CSBK recieved, but HBlink does not process them currently') else: @@ -954,6 +955,9 @@ if __name__ == '__main__': # Build the routing rules file BRIDGES = make_bridges(rules_module.BRIDGES) + + # Get rule parameter for private calls + UNIT = rules_module.BRIDGES # INITIALIZE THE REPORTING LOOP if CONFIG['REPORTS']['REPORT']: From 2e09350ad23894c2cb365d2b13f3b859f2dbd77f Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Mon, 2 Dec 2019 08:29:52 -0600 Subject: [PATCH 34/68] OBP Support Added -- CURRENTLY UNTESTED OpenBridge support added, but not yet tested. This push is so that I can pick up my updates on another computer to test. --- bridge.py | 74 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/bridge.py b/bridge.py index ceb3a08..69204ff 100755 --- a/bridge.py +++ b/bridge.py @@ -151,18 +151,18 @@ def rule_timer_loop(): logger.debug('(ROUTER) Conference Bridge ACTIVE (no change): System: %s Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) else: logger.debug('(ROUTER) Conference Bridge NO ACTION: System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) - + _then = _now - 60 remove_list = [] for unit in UNIT_MAP: if UNIT_MAP[unit][1] < (_then): remove_list.append(unit) - + for unit in remove_list: del UNIT_MAP[unit] - + logger.debug('Removed unit(s) %s from UNIT_MAP', remove_list) - + if CONFIG['REPORTS']['REPORT']: report_server.send_clients(b'bridge updated') @@ -219,7 +219,7 @@ class routerOBP(OPENBRIDGE): def __init__(self, _name, _config, _report): OPENBRIDGE.__init__(self, _name, _config, _report) self.STATUS = {} - + def group_recieved(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() dmrpkt = _data[20:53] @@ -410,13 +410,13 @@ class routerOBP(OPENBRIDGE): logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) if not removed: selflogger.error('(%s) *GROUP CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) - - + + def unit_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] - + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): @@ -428,7 +428,7 @@ class routerOBP(OPENBRIDGE): logger.debug('CSBK recieved, but HBlink does not process them currently') else: logger.error('Unknown call type recieved -- not processed') - + class routerHBP(HBSYSTEM): @@ -438,7 +438,7 @@ class routerHBP(HBSYSTEM): # list of targets for unit (subscriber, private) calls self.targets = [] - + # Status information for the system, TS1 & TS2 # 1 & 2 are "timeslot" # In TX_EMB_LC, 2-5 are burst B-E @@ -496,7 +496,7 @@ class routerHBP(HBSYSTEM): } } } - + def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): global UNIT_MAP @@ -560,7 +560,7 @@ class routerHBP(HBSYSTEM): logger.info('(%s) Conference Bridge: %s, Call Bridged to OBP System: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) if CONFIG['REPORTS']['REPORT']: systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) - + # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time # Clear the TS bit -- all OpenBridge streams are effectively on TS1 @@ -780,26 +780,34 @@ class routerHBP(HBSYSTEM): self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, _target_route) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), _target_route).encode(encoding='utf-8', errors='ignore')) - - ''' LC CREATION NOT NECESSARY, SINCE WE'RE NOT TRANSCODING - # If we can, use the LC from the voice header as to keep all options intact - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD: - decoded = decode.voice_head_term(dmrpkt) - self.STATUS[_slot]['RX_LC'] = decoded['LC'] - # If we don't have a voice header then don't wait to decode it from the Embedded LC - # just make a new one from the HBP header. This is good enough, and it saves lots of time - else: - self.STATUS[_slot]['RX_LC'] = LC_OPT + _dst_id + _rf_src - ''' - for _target in self._targets: _target_status = systems[_target].STATUS _target_system = self._CONFIG['SYSTEMS'][_target] if self._CONFIG['SYSTEMS'][_target]['MODE'] == 'OPENBRIDGE': - # THIS IS ONLY UNTIL OPENBRIDGE IS SUPPORTED - continue + if (_stream_id not in _target_status): + # This is a new call stream on the target + _target_status[_stream_id] = { + 'START': pkt_time, + 'CONTENTION':False, + 'RFS': _rf_src, + 'TGID': _dst_id, + } + + logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _target['TS'], int_id(_target['TGID'])) + if CONFIG['REPORTS']['REPORT']: + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) + + # Record the time of this packet so we can later identify a stale stream + _target_status[_stream_id]['LAST'] = pkt_time + # Clear the TS bit -- all OpenBridge streams are effectively on TS1 + _tmp_bits = _bits & ~(1 << 7) + + # Assemble transmit HBP packet + _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + _tmp_data = b''.join([_tmp_data, dmrpkt]) + else: # BEGIN STANDARD CONTENTION HANDLING @@ -827,7 +835,7 @@ class routerHBP(HBSYSTEM): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, DEST: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) continue - + # Record target information if this is a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): # Record the DST TGID and Stream ID @@ -836,11 +844,11 @@ class routerHBP(HBSYSTEM): _target_status[_slot]['TX_STREAM_ID'] = _stream_id _target_status[_slot]['TX_RFS'] = _rf_src _target_status[_slot]['TX_PEER'] = _peer_id - + # Set other values for the contention handler to test next time there is a frame to forward _target_status[_slot]['TX_TIME'] = pkt_time _target_status[_slot]['TX_TYPE'] = _dtype_vseq - + #send the call: systems[_target].send_system(_data) @@ -853,7 +861,7 @@ class routerHBP(HBSYSTEM): self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('UNIT VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) - + # Mark status variables for use later self.STATUS[_slot]['RX_PEER'] = _peer_id self.STATUS[_slot]['RX_SEQ'] = _seq @@ -862,9 +870,9 @@ class routerHBP(HBSYSTEM): self.STATUS[_slot]['RX_TGID'] = _dst_id self.STATUS[_slot]['RX_TIME'] = pkt_time self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id - - - + + + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): if _call_type == 'group': self.group_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) From 857c4d09d0b3926309a452c128d5d4582eea3c76 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Mon, 2 Dec 2019 08:30:25 -0600 Subject: [PATCH 35/68] untested OBP support added Push is just to sync with a test system. --- bridge.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bridge.py b/bridge.py index 69204ff..77077c7 100755 --- a/bridge.py +++ b/bridge.py @@ -795,9 +795,9 @@ class routerHBP(HBSYSTEM): 'TGID': _dst_id, } - logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _target['TS'], int_id(_target['TGID'])) + logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot, int_id(_dst_id)) if CONFIG['REPORTS']['REPORT']: - systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID'])).encode(encoding='utf-8', errors='ignore')) + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id).encode(encoding='utf-8', errors='ignore')) # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time @@ -844,6 +844,10 @@ class routerHBP(HBSYSTEM): _target_status[_slot]['TX_STREAM_ID'] = _stream_id _target_status[_slot]['TX_RFS'] = _rf_src _target_status[_slot]['TX_PEER'] = _peer_id + + logger.info('(%s) Unit call bridged to HBP System: %s TS: %s, UNIT: %s', self._system, _target, _slot, int_id(_dst_id)) + if CONFIG['REPORTS']['REPORT']: + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) # Set other values for the contention handler to test next time there is a frame to forward _target_status[_slot]['TX_TIME'] = pkt_time From 717e802b9bd80ca86fd09f33be5d2257a6a2d0c2 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Mon, 2 Dec 2019 19:56:41 -0600 Subject: [PATCH 36/68] fixed missing ) on logging line --- bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index 77077c7..abb1189 100755 --- a/bridge.py +++ b/bridge.py @@ -797,7 +797,7 @@ class routerHBP(HBSYSTEM): logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot, int_id(_dst_id)) if CONFIG['REPORTS']['REPORT']: - systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id).encode(encoding='utf-8', errors='ignore')) + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id).encode(encoding='utf-8', errors='ignore'))) # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time From 364ead81ec4e81bfcb8dfb376b13596d7c2ee64d Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Tue, 3 Dec 2019 13:11:01 -0600 Subject: [PATCH 37/68] Working Code - REPORT ERRORS! --- bridge.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/bridge.py b/bridge.py index abb1189..c41876b 100755 --- a/bridge.py +++ b/bridge.py @@ -220,7 +220,7 @@ class routerOBP(OPENBRIDGE): OPENBRIDGE.__init__(self, _name, _config, _report) self.STATUS = {} - def group_recieved(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): + def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] @@ -797,7 +797,7 @@ class routerHBP(HBSYSTEM): logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot, int_id(_dst_id)) if CONFIG['REPORTS']['REPORT']: - systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id).encode(encoding='utf-8', errors='ignore'))) + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time @@ -806,9 +806,8 @@ class routerHBP(HBSYSTEM): # Assemble transmit HBP packet _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - _tmp_data = b''.join([_tmp_data, dmrpkt]) - - + _data = b''.join([_tmp_data, dmrpkt]) + else: # BEGIN STANDARD CONTENTION HANDLING # @@ -847,7 +846,7 @@ class routerHBP(HBSYSTEM): logger.info('(%s) Unit call bridged to HBP System: %s TS: %s, UNIT: %s', self._system, _target, _slot, int_id(_dst_id)) if CONFIG['REPORTS']['REPORT']: - systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) # Set other values for the contention handler to test next time there is a frame to forward _target_status[_slot]['TX_TIME'] = pkt_time @@ -969,7 +968,7 @@ if __name__ == '__main__': BRIDGES = make_bridges(rules_module.BRIDGES) # Get rule parameter for private calls - UNIT = rules_module.BRIDGES + UNIT = rules_module.UNIT # INITIALIZE THE REPORTING LOOP if CONFIG['REPORTS']['REPORT']: From 877e1c6c65e1c6de67fafcce28c88eab901488bd Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 17:12:26 -0600 Subject: [PATCH 38/68] Adding inbound OBP Processing --- bridge.py | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/bridge.py b/bridge.py index c41876b..3e844f6 100755 --- a/bridge.py +++ b/bridge.py @@ -413,9 +413,135 @@ class routerOBP(OPENBRIDGE): def unit_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): + global UNIT_MAP pkt_time = time() dmrpkt = _data[20:53] _bits = _data[15] + + # Make/update this unit in the UNIT_MAP cache + UNIT_MAP[_rf_src] = (self.name, pkt_time) + + + # Is this a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + + # Collision in progress, bail out! + if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): + logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s UNIT %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) + return + + # Create a destination list for the call: + if _dst_id in UNIT_MAP: + if UNIT_MAP[_dst_id][0] != self._system: + self._targets = [UNIT_MAP[_dst_id][0]] + _target_route = UNIT_MAP[_dst_id][0] + else: + self._targets = [] + logger.debug('UNIT call to a subscriber on the same system, send nothing') + else: + self._targets = list(systems) + self._targets.remove(self._system) + _target_route = 'FLOOD' + + # This is a new call stream, so log & report + self.STATUS[_slot]['RX_START'] = pkt_time + logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT: %s (%s), TS: %s, FORWARD: %s', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, _target_route) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), _target_route).encode(encoding='utf-8', errors='ignore')) + + for _target in self._targets: + _target_status = systems[_target].STATUS + _target_system = self._CONFIG['SYSTEMS'][_target] + + if self._CONFIG['SYSTEMS'][_target]['MODE'] == 'OPENBRIDGE': + if (_stream_id not in _target_status): + # This is a new call stream on the target + _target_status[_stream_id] = { + 'START': pkt_time, + 'CONTENTION':False, + 'RFS': _rf_src, + 'TGID': _dst_id, + } + + logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot, int_id(_dst_id)) + if CONFIG['REPORTS']['REPORT']: + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) + + # Record the time of this packet so we can later identify a stale stream + _target_status[_stream_id]['LAST'] = pkt_time + # Clear the TS bit -- all OpenBridge streams are effectively on TS1 + _tmp_bits = _bits & ~(1 << 7) + + # Assemble transmit HBP packet + _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + _data = b''.join([_tmp_data, dmrpkt]) + + else: + # BEGIN STANDARD CONTENTION HANDLING + # + # 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 HBSystem, but it has been less than Group Hangtime + # From a different group than last TX to this HBSystem, but it has been less than Group Hangtime + # From the same group as the last RX from this HBSystem, but from a different subscriber, and it has been less than stream timeout + # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout + # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules + # + if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, target active or in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + continue + if ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, target in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) + continue + if (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, matching call already active on target: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + continue + if (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, DEST: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) + continue + + # Record target information if this is a new call stream? + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + # Record the DST TGID and Stream ID + _target_status[_slot]['TX_START'] = pkt_time + _target_status[_slot]['TX_TGID'] = _dst_id + _target_status[_slot]['TX_STREAM_ID'] = _stream_id + _target_status[_slot]['TX_RFS'] = _rf_src + _target_status[_slot]['TX_PEER'] = _peer_id + + logger.info('(%s) Unit call bridged to HBP System: %s TS: %s, UNIT: %s', self._system, _target, _slot, int_id(_dst_id)) + if CONFIG['REPORTS']['REPORT']: + systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) + + # Set other values for the contention handler to test next time there is a frame to forward + _target_status[_slot]['TX_TIME'] = pkt_time + _target_status[_slot]['TX_TYPE'] = _dtype_vseq + + #send the call: + systems[_target].send_system(_data) + + + # Final actions - Is this a voice terminator? + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): + self._targets = [] + call_duration = pkt_time - self.STATUS[_slot]['RX_START'] + logger.info('(%s) *UNIT CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s, Duration: %.2f', \ + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) + if CONFIG['REPORTS']['REPORT']: + self._report.send_bridgeEvent('UNIT VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) + + # Mark status variables for use later + self.STATUS[_slot]['RX_PEER'] = _peer_id + self.STATUS[_slot]['RX_SEQ'] = _seq + self.STATUS[_slot]['RX_RFS'] = _rf_src + self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq + self.STATUS[_slot]['RX_TGID'] = _dst_id + self.STATUS[_slot]['RX_TIME'] = pkt_time + self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): From 4643ea47b7bc2b2f9ca790fc01a35a97f41cbc5e Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 17:23:04 -0600 Subject: [PATCH 39/68] Inbound OBP Unit calls --- bridge.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bridge.py b/bridge.py index 3e844f6..ee1e0f9 100755 --- a/bridge.py +++ b/bridge.py @@ -218,7 +218,11 @@ class routerOBP(OPENBRIDGE): def __init__(self, _name, _config, _report): OPENBRIDGE.__init__(self, _name, _config, _report) + self.name = _name self.STATUS = {} + + # list of targets for unit (subscriber, private) calls + self.targets = [] def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() From 63604a5b38a7cd648458290fef3b5ce8090be39b Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 17:25:50 -0600 Subject: [PATCH 40/68] inbound OBP Unit calls --- bridge.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index ee1e0f9..323836e 100755 --- a/bridge.py +++ b/bridge.py @@ -427,7 +427,14 @@ class routerOBP(OPENBRIDGE): # Is this a new call stream? - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + if (_stream_id not in self.STATUS): + # This is a new call stream + self.STATUS[_stream_id] = { + 'START': pkt_time, + 'CONTENTION':False, + 'RFS': _rf_src, + 'TGID': _dst_id, + } # Collision in progress, bail out! if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): From bb4356e027eee0fcdfc509a9dd21f8137a662fa4 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 18:45:22 -0600 Subject: [PATCH 41/68] Update bridge.py --- bridge.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bridge.py b/bridge.py index 323836e..3ec2437 100755 --- a/bridge.py +++ b/bridge.py @@ -435,11 +435,6 @@ class routerOBP(OPENBRIDGE): 'RFS': _rf_src, 'TGID': _dst_id, } - - # Collision in progress, bail out! - if (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM) and (pkt_time < (self.STATUS[_slot]['RX_TIME'] + STREAM_TO)) and (_rf_src != self.STATUS[_slot]['RX_RFS']): - logger.warning('(%s) Packet received with STREAM ID: %s SUB: %s PEER: %s UNIT %s, SLOT %s collided with existing call', self._system, int_id(_stream_id), int_id(_rf_src), int_id(_peer_id), int_id(_dst_id), _slot) - return # Create a destination list for the call: if _dst_id in UNIT_MAP: From 8c2b35ece086c19326cba26291e843bcfb68fe52 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 18:48:51 -0600 Subject: [PATCH 42/68] Update bridge.py --- bridge.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/bridge.py b/bridge.py index 3ec2437..25c35f0 100755 --- a/bridge.py +++ b/bridge.py @@ -450,7 +450,7 @@ class routerOBP(OPENBRIDGE): _target_route = 'FLOOD' # This is a new call stream, so log & report - self.STATUS[_slot]['RX_START'] = pkt_time + self.STATUS['START'] = pkt_time logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT: %s (%s), TS: %s, FORWARD: %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, _target_route) if CONFIG['REPORTS']['REPORT']: @@ -540,15 +540,6 @@ class routerOBP(OPENBRIDGE): if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('UNIT VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) - # Mark status variables for use later - self.STATUS[_slot]['RX_PEER'] = _peer_id - self.STATUS[_slot]['RX_SEQ'] = _seq - self.STATUS[_slot]['RX_RFS'] = _rf_src - self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq - self.STATUS[_slot]['RX_TGID'] = _dst_id - self.STATUS[_slot]['RX_TIME'] = pkt_time - self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id - def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): From b5e77e64b12465b504c12ab6a5675db08a38048f Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 18:51:42 -0600 Subject: [PATCH 43/68] Update bridge.py --- bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index 25c35f0..b0032de 100755 --- a/bridge.py +++ b/bridge.py @@ -511,7 +511,7 @@ class routerOBP(OPENBRIDGE): continue # Record target information if this is a new call stream? - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + if (_stream_id not in self.STATUS): # Record the DST TGID and Stream ID _target_status[_slot]['TX_START'] = pkt_time _target_status[_slot]['TX_TGID'] = _dst_id From 94afc15d7af15700b2e659f5fd5901495b9b2b1e Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 18:57:44 -0600 Subject: [PATCH 44/68] Update bridge.py --- bridge.py | 49 +++++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/bridge.py b/bridge.py index b0032de..e183559 100755 --- a/bridge.py +++ b/bridge.py @@ -484,7 +484,7 @@ class routerOBP(OPENBRIDGE): _data = b''.join([_tmp_data, dmrpkt]) else: - # BEGIN STANDARD CONTENTION HANDLING + # BEGIN CONTENTION HANDLING # # 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 HBSystem, but it has been less than Group Hangtime @@ -493,22 +493,27 @@ class routerOBP(OPENBRIDGE): # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules # - if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to destination %s, target active or in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + if ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %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 ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to destination %s, target in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) + if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %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 (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to destination %s, matching call already active on target: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) - continue - if (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, DEST: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) + if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %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 (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) + continue + # Record target information if this is a new call stream? if (_stream_id not in self.STATUS): @@ -722,21 +727,21 @@ class routerHBP(HBSYSTEM): # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout # 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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) continue - if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID'])) + logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) continue - if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): + if (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) continue - if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): + if (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) continue # Is this a new call stream? From 9ac426aa2d87f9296aabf7b507f5694cab744e34 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 18:59:55 -0600 Subject: [PATCH 45/68] Revert "Update bridge.py" This reverts commit 0a2edc09a53d748143dd8e55e38ac58208a3f6ef. --- bridge.py | 49 ++++++++++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/bridge.py b/bridge.py index e183559..b0032de 100755 --- a/bridge.py +++ b/bridge.py @@ -484,7 +484,7 @@ class routerOBP(OPENBRIDGE): _data = b''.join([_tmp_data, dmrpkt]) else: - # BEGIN CONTENTION HANDLING + # BEGIN STANDARD CONTENTION HANDLING # # 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 HBSystem, but it has been less than Group Hangtime @@ -493,27 +493,22 @@ class routerOBP(OPENBRIDGE): # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout # 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 ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, target active or in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) continue - if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID'])) + if ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, target in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) continue - if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) + if (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed to destination %s, matching call already active on target: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + continue + if (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): + if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, DEST: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) continue - if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): - if self.STATUS[_stream_id]['CONTENTION'] == False: - self.STATUS[_stream_id]['CONTENTION'] = True - logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) - continue - # Record target information if this is a new call stream? if (_stream_id not in self.STATUS): @@ -727,21 +722,21 @@ class routerHBP(HBSYSTEM): # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules # - if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): + if ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %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 ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): + if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) + logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %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 (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): + if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %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 (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): + if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) continue # Is this a new call stream? From e6949e65ec6877d764f21fda9b99b22fa90f530e Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 19:00:13 -0600 Subject: [PATCH 46/68] Revert "Update bridge.py" This reverts commit ab1a53614759db657323241e75fd91d98925d295. --- bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index b0032de..25c35f0 100755 --- a/bridge.py +++ b/bridge.py @@ -511,7 +511,7 @@ class routerOBP(OPENBRIDGE): continue # Record target information if this is a new call stream? - if (_stream_id not in self.STATUS): + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): # Record the DST TGID and Stream ID _target_status[_slot]['TX_START'] = pkt_time _target_status[_slot]['TX_TGID'] = _dst_id From ed2a1ca5494a012189f23d2a52143b379d0a578f Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 19:02:24 -0600 Subject: [PATCH 47/68] Update bridge.py --- bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index 25c35f0..b0032de 100755 --- a/bridge.py +++ b/bridge.py @@ -511,7 +511,7 @@ class routerOBP(OPENBRIDGE): continue # Record target information if this is a new call stream? - if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + if (_stream_id not in self.STATUS): # Record the DST TGID and Stream ID _target_status[_slot]['TX_START'] = pkt_time _target_status[_slot]['TX_TGID'] = _dst_id From 508b9c719741dd0812259e67a13af3dffd362ac8 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 19:05:48 -0600 Subject: [PATCH 48/68] Update bridge.py --- bridge.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/bridge.py b/bridge.py index b0032de..283eaae 100755 --- a/bridge.py +++ b/bridge.py @@ -494,20 +494,24 @@ class routerOBP(OPENBRIDGE): # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules # if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to destination %s, target active or in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) continue if ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to destination %s, target in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) continue if (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed to destination %s, matching call already active on target: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) continue if (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): - if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: - logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, DEST: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) + if self.STATUS[_stream_id]['CONTENTION'] == False: + self.STATUS[_stream_id]['CONTENTION'] = True + logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) continue # Record target information if this is a new call stream? From 30ed93aa307eb89ca8df92fafc4db085519af08b Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 19:06:57 -0600 Subject: [PATCH 49/68] Update bridge.py --- bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index 283eaae..8cf0190 100755 --- a/bridge.py +++ b/bridge.py @@ -536,7 +536,7 @@ class routerOBP(OPENBRIDGE): # Final actions - Is this a voice terminator? - if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): self._targets = [] call_duration = pkt_time - self.STATUS[_slot]['RX_START'] logger.info('(%s) *UNIT CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s, Duration: %.2f', \ From 35ba251e71c1379144bb9d81c5d24c1a03dd31c2 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 19:07:58 -0600 Subject: [PATCH 50/68] Update bridge.py --- bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index 8cf0190..75e5061 100755 --- a/bridge.py +++ b/bridge.py @@ -538,7 +538,7 @@ class routerOBP(OPENBRIDGE): # Final actions - Is this a voice terminator? if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): self._targets = [] - call_duration = pkt_time - self.STATUS[_slot]['RX_START'] + call_duration = pkt_time - self.STATUS['START'] logger.info('(%s) *UNIT CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s, Duration: %.2f', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) if CONFIG['REPORTS']['REPORT']: From 2fa615dd373473da7013549050b918b2a8d1f135 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 4 Dec 2019 19:13:13 -0600 Subject: [PATCH 51/68] Update bridge.py --- bridge.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bridge.py b/bridge.py index 75e5061..b3163d1 100755 --- a/bridge.py +++ b/bridge.py @@ -174,6 +174,7 @@ def stream_trimmer_loop(): _now = time() for system in systems: + print(systems[system].STATUS) # HBP systems, master and peer if CONFIG['SYSTEMS'][system]['MODE'] != 'OPENBRIDGE': for slot in range(1,3): From 3d9aa41fcb0e3daed7e0b65daf40d73d0a1733af Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 9 Feb 2020 16:33:46 -0600 Subject: [PATCH 52/68] Update bridge.py --- bridge.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bridge.py b/bridge.py index b3163d1..75e5061 100755 --- a/bridge.py +++ b/bridge.py @@ -174,7 +174,6 @@ def stream_trimmer_loop(): _now = time() for system in systems: - print(systems[system].STATUS) # HBP systems, master and peer if CONFIG['SYSTEMS'][system]['MODE'] != 'OPENBRIDGE': for slot in range(1,3): From 4aa5210f5bbfd167615332a19adedd14414f9d1f Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 9 Feb 2020 17:08:32 -0600 Subject: [PATCH 53/68] Update bridge.py --- bridge.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bridge.py b/bridge.py index 75e5061..2dacd6e 100755 --- a/bridge.py +++ b/bridge.py @@ -573,7 +573,7 @@ class routerHBP(HBSYSTEM): 1: { 'RX_START': time(), 'TX_START': time(), - 'RX_SEQ': b'\x00', + 'RX_SEQ': 0, 'RX_RFS': b'\x00', 'TX_RFS': b'\x00', 'RX_PEER': b'\x00', @@ -599,7 +599,7 @@ class routerHBP(HBSYSTEM): 2: { 'RX_START': time(), 'TX_START': time(), - 'RX_SEQ': b'\x00', + 'RX_SEQ': 0, 'RX_RFS': b'\x00', 'TX_RFS': b'\x00', 'RX_PEER': b'\x00', From d44a181c06168fc8b540cbe2c7472cca2b9810a0 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Mon, 10 Feb 2020 10:11:11 -0600 Subject: [PATCH 54/68] create blank app template --- blank_app.py | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100755 blank_app.py diff --git a/blank_app.py b/blank_app.py new file mode 100755 index 0000000..b13e29a --- /dev/null +++ b/blank_app.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# +############################################################################### +# Copyright (C) 2020 Cortney T. Buffington, N0MJS +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +############################################################################### + +''' +This is a blank application template to build on top of hblink.py. It contains +only the things you need to, essentially, run hblink.py underneath and do nothing +else. The expected behaviour is to override the dmrd_received function from +hblink.py to do somethign meaningful, so that framework is completed, but as +it stands, still does nothing with the DMRD packet. +''' + +# Python modules we need +import sys +from bitarray import bitarray +from time import time +from importlib import import_module +from types import ModuleType + +# Twisted is pretty important, so I keep it separate +from twisted.internet.protocol import Factory, Protocol +from twisted.protocols.basic import NetstringReceiver +from twisted.internet import reactor, task + +# Things we import from the main hblink module +from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, mk_aliases, acl_check +from dmr_utils3.utils import bytes_3, int_id, get_alias +from dmr_utils3 import decode, bptc, const +import config +import log +import const + +# The module needs logging logging, but handlers, etc. are controlled by the parent +import logging +logger = logging.getLogger(__name__) + + +# Does anybody read this stuff? There's a PEP somewhere that says I should do this. +__author__ = 'Cortney T. Buffington, N0MJS' +__copyright__ = 'Copyright (c) 2020 Cortney T. Buffington' +__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT' +__license__ = 'GNU GPLv3' +__maintainer__ = 'Cort Buffington, N0MJS' +__email__ = 'n0mjs@me.com' +__status__ = 'pre-alpha' + +# Module gobal varaibles + + +class blankSYSTEM(HBSYSTEM): + + def __init__(self, _name, _config, _report): + HBSYSTEM.__init__(self, _name, _config, _report) + + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + pass + + +#************************************************ +# MAIN PROGRAM LOOP STARTS HERE +#************************************************ + +if __name__ == '__main__': + import argparse + import sys + import os + import signal + from dmr_utils3.utils import try_download, mk_id_dict + + # Change the current directory to the location of the application + os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) + + # CLI argument parser - handles picking up the config file from the command line, and sending a "help" message + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)') + parser.add_argument('-l', '--logging', action='store', dest='LOG_LEVEL', help='Override config file logging level.') + cli_args = parser.parse_args() + + # Ensure we have a path for the config file, if one wasn't specified, then use the default (top of file) + if not cli_args.CONFIG_FILE: + cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg' + + # Call the external routine to build the configuration dictionary + CONFIG = config.build_config(cli_args.CONFIG_FILE) + + # Start the system logger + if cli_args.LOG_LEVEL: + CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL + logger = log.config_logging(CONFIG['LOGGER']) + logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018, 2019\n\tThe Regents of the K0USY Group. All rights reserved.\n') + logger.debug('Logging system started, anything from here on gets logged') + + # Set up the signal handler + def sig_handler(_signal, _frame): + logger.info('SHUTDOWN: >>>BLANK APP<<< IS TERMINATING WITH SIGNAL %s', str(_signal)) + hblink_handler(_signal, _frame) + logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR') + reactor.stop() + + # Set signal handers so that we can gracefully exit if need be + for sig in [signal.SIGTERM, signal.SIGINT]: + signal.signal(sig, sig_handler) + + # Create the name-number mapping dictionaries + peer_ids, subscriber_ids, talkgroup_ids = mk_aliases(CONFIG) + + + # INITIALIZE THE REPORTING LOOP + if CONFIG['REPORTS']['REPORT']: + report_server = config_reports(CONFIG, reportFactory) + else: + report_server = None + logger.info('(REPORT) TCP Socket reporting not configured') + + # HBlink instance creation + logger.info('HBlink \'blank_app.py\' -- SYSTEM STARTING...') + for system in CONFIG['SYSTEMS']: + if CONFIG['SYSTEMS'][system]['ENABLED']: + if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': + systems[system] = OPENBRIDGE(system, CONFIG, report_server) + else: + systems[system] = HBSYSTEM(system, CONFIG, report_server) + + reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) + logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) + + reactor.run() From 41f04dc28be4e21875b3a1b503d4131d594dca28 Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Tue, 11 Feb 2020 14:24:29 -0600 Subject: [PATCH 55/68] Skeleton App for Data Development --- data_dev.py | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100755 data_dev.py diff --git a/data_dev.py b/data_dev.py new file mode 100755 index 0000000..4532194 --- /dev/null +++ b/data_dev.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# +############################################################################### +# Copyright (C) 2020 Cortney T. Buffington, N0MJS +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +############################################################################### + +''' +This is a blank application template to build on top of hblink.py. It contains +only the things you need to, essentially, run hblink.py underneath and do nothing +else. The expected behaviour is to override the dmrd_received function from +hblink.py to do somethign meaningful, so that framework is completed, but as +it stands, still does nothing with the DMRD packet. +''' + +# Python modules we need +import sys +from bitarray import bitarray +from time import time +from importlib import import_module +from types import ModuleType + +# Twisted is pretty important, so I keep it separate +from twisted.internet.protocol import Factory, Protocol +from twisted.protocols.basic import NetstringReceiver +from twisted.internet import reactor, task + +# Things we import from the main hblink module +from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, mk_aliases, acl_check +from dmr_utils3.utils import bytes_3, int_id, get_alias +from dmr_utils3 import decode, bptc, const +import config +import log +import const + +# The module needs logging logging, but handlers, etc. are controlled by the parent +import logging +logger = logging.getLogger(__name__) + + +# Does anybody read this stuff? There's a PEP somewhere that says I should do this. +__author__ = 'Cortney T. Buffington, N0MJS' +__copyright__ = 'Copyright (c) 2020 Cortney T. Buffington' +__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT' +__license__ = 'GNU GPLv3' +__maintainer__ = 'Cort Buffington, N0MJS' +__email__ = 'n0mjs@me.com' +__status__ = 'pre-alpha' + +# Module gobal varaibles + + +class dataSYSTEM(HBSYSTEM): + + def __init__(self, _name, _config, _report): + HBSYSTEM.__init__(self, _name, _config, _report) + + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): + print(_rf_src, _stream_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq) + + +#************************************************ +# MAIN PROGRAM LOOP STARTS HERE +#************************************************ + +if __name__ == '__main__': + import argparse + import sys + import os + import signal + from dmr_utils3.utils import try_download, mk_id_dict + + # Change the current directory to the location of the application + os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) + + # CLI argument parser - handles picking up the config file from the command line, and sending a "help" message + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)') + parser.add_argument('-l', '--logging', action='store', dest='LOG_LEVEL', help='Override config file logging level.') + cli_args = parser.parse_args() + + # Ensure we have a path for the config file, if one wasn't specified, then use the default (top of file) + if not cli_args.CONFIG_FILE: + cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg' + + # Call the external routine to build the configuration dictionary + CONFIG = config.build_config(cli_args.CONFIG_FILE) + + # Start the system logger + if cli_args.LOG_LEVEL: + CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL + logger = log.config_logging(CONFIG['LOGGER']) + logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018, 2019\n\tThe Regents of the K0USY Group. All rights reserved.\n') + logger.debug('Logging system started, anything from here on gets logged') + + # Set up the signal handler + def sig_handler(_signal, _frame): + logger.info('SHUTDOWN: DATA APP IS TERMINATING WITH SIGNAL %s', str(_signal)) + hblink_handler(_signal, _frame) + logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR') + reactor.stop() + + # Set signal handers so that we can gracefully exit if need be + for sig in [signal.SIGTERM, signal.SIGINT]: + signal.signal(sig, sig_handler) + + # Create the name-number mapping dictionaries + peer_ids, subscriber_ids, talkgroup_ids = mk_aliases(CONFIG) + + + # INITIALIZE THE REPORTING LOOP + if CONFIG['REPORTS']['REPORT']: + report_server = config_reports(CONFIG, reportFactory) + else: + report_server = None + logger.info('(REPORT) TCP Socket reporting not configured') + + # HBlink instance creation + logger.info('HBlink \'data_dev.py\' -- SYSTEM STARTING...') + for system in CONFIG['SYSTEMS']: + if CONFIG['SYSTEMS'][system]['ENABLED']: + if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': + systems[system] = OPENBRIDGE(system, CONFIG, report_server) + else: + systems[system] = HBSYSTEM(system, CONFIG, report_server) + + reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) + logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) + + reactor.run() From 50387c2133b7986d8eaaa5ac66934517f0b6862d Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Tue, 18 Feb 2020 01:18:11 +0000 Subject: [PATCH 56/68] update --- bridge.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index 2dacd6e..4a9f3ae 100755 --- a/bridge.py +++ b/bridge.py @@ -250,7 +250,7 @@ class routerOBP(OPENBRIDGE): self.STATUS[_stream_id]['LC'] = LC_OPT + _dst_id + _rf_src - logger.info('(%s) *GROUP CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ + logger.info('(%s) *GROUP CALL START* OBP STREAM ID: %s SUB: %s (%s) PEER: %s (%s) TGID %s (%s), TS %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,START,RX,{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) @@ -945,6 +945,7 @@ class routerHBP(HBSYSTEM): # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules # + ''' if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: logger.info('(%s) Call not routed to destination %s, target active or in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) @@ -953,6 +954,7 @@ class routerHBP(HBSYSTEM): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: logger.info('(%s) Call not routed to destination %s, target in group hangtime: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) continue + ''' if (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): if _frame_type == HBPF_DATA_SYNC and _dtype_vseq == HBPF_SLT_VHEAD and self.STATUS[_slot]['RX_STREAM_ID'] != _stream_id: logger.info('(%s) Call not routed to destination %s, matching call already active on target: HBSystem: %s, TS: %s, DEST: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) From 21f9c680beace0f07d864f909b37326b92878c5a Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Mon, 17 Feb 2020 19:20:10 -0600 Subject: [PATCH 57/68] update sample config --- bridge.py | 14 ++++++++++---- rules_SAMPLE.py | 13 +++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/bridge.py b/bridge.py index 2dacd6e..ee6e1a1 100755 --- a/bridge.py +++ b/bridge.py @@ -892,23 +892,27 @@ class routerHBP(HBSYSTEM): if _dst_id in UNIT_MAP: if UNIT_MAP[_dst_id][0] != self._system: self._targets = [UNIT_MAP[_dst_id][0]] - _target_route = UNIT_MAP[_dst_id][0] + #_target_route = UNIT_MAP[_dst_id][0] else: self._targets = [] logger.debug('UNIT call to a subscriber on the same system, send nothing') else: self._targets = list(systems) self._targets.remove(self._system) - _target_route = 'FLOOD' + #_target_route = 'FLOOD' # This is a new call stream, so log & report self.STATUS[_slot]['RX_START'] = pkt_time logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT: %s (%s), TS: %s, FORWARD: %s', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, _target_route) + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, self._targets) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), _target_route).encode(encoding='utf-8', errors='ignore')) for _target in self._targets: + if _target not in UNIT: + logger.debug('(%s) *UNIT CALL NOT FORWARDED* UNIT calling is disabled for this system (EGRESS)', self._system) + continue + _target_status = systems[_target].STATUS _target_system = self._CONFIG['SYSTEMS'][_target] @@ -1007,7 +1011,9 @@ class routerHBP(HBSYSTEM): if _call_type == 'group': self.group_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) elif _call_type == 'unit': - if UNIT: + if self._system not in UNIT: + logger.debug('(%s) *UNIT CALL NOT FORWARDED* UNIT calling is disabled for this system (INGRESS)', self._system) + else: self.unit_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) elif _call_type == 'vscsbk': logger.debug('CSBK recieved, but HBlink does not process them currently') diff --git a/rules_SAMPLE.py b/rules_SAMPLE.py index a3c3701..a86d02d 100755 --- a/rules_SAMPLE.py +++ b/rules_SAMPLE.py @@ -47,6 +47,19 @@ BRIDGES = { ] } +''' +list the names of each system that should process unit to unit (individual) calls. Processing +is both ingress and egress. +''' + +UNIT = ['ONE', 'TWO'] + +''' +This is for testing the syntax of the file. It won't eliminate all errors, but running this file +like it were a Python program itself will tell you if the syntax is correct! +''' + if __name__ == '__main__': from pprint import pprint pprint(BRIDGES) + print(UNIT) From 9e2876dfae69d793fd0723115b168f0243e15cf4 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Tue, 18 Feb 2020 18:11:19 +0000 Subject: [PATCH 58/68] update --- bridge.py | 50 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/bridge.py b/bridge.py index 9b57460..47ce580 100755 --- a/bridge.py +++ b/bridge.py @@ -206,10 +206,13 @@ def stream_trimmer_loop(): if stream_id in systems[system].STATUS: _stream = systems[system].STATUS[stream_id] _sysconfig = CONFIG['SYSTEMS'][system] - logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s PEER: %s TGID: %s TS 1 Duration: %.2f', \ - system, int_id(stream_id), get_alias(int_id(_stream['RFS']), subscriber_ids), get_alias(int_id(_sysconfig['NETWORK_ID']), peer_ids), get_alias(int_id(_stream['TGID']), talkgroup_ids), _stream['LAST'] - _stream['START']) + logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s PEER: %s TYPE: %s DST ID: %s TS 1 Duration: %.2f', \ + system, int_id(stream_id), get_alias(int_id(_stream['RFS']), subscriber_ids), get_alias(int_id(_sysconfig['NETWORK_ID']), peer_ids), _stream['TYPE'], get_alias(int_id(_stream['DST']), talkgroup_ids), _stream['LAST'] - _stream['START']) if CONFIG['REPORTS']['REPORT']: - systems[system]._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(system, int_id(stream_id), int_id(_sysconfig['NETWORK_ID']), int_id(_stream['RFS']), 1, int_id(_stream['TGID']), _stream['LAST'] - _stream['START']).encode(encoding='utf-8', errors='ignore')) + if _stream['TYPE'] == 'GROUP': + systems[system]._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(system, int_id(stream_id), int_id(_sysconfig['NETWORK_ID']), int_id(_stream['RFS']), 1, int_id(_stream['DST']), _stream['LAST'] - _stream['START']).encode(encoding='utf-8', errors='ignore')) + elif _stream['TYPE'] == 'UNIT': + systems[system]._report.send_bridgeEvent('UNIT VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(system, int_id(stream_id), int_id(_sysconfig['NETWORK_ID']), int_id(_stream['RFS']), 1, int_id(_stream['DST']), _stream['LAST'] - _stream['START']).encode(encoding='utf-8', errors='ignore')) removed = systems[system].STATUS.pop(stream_id) else: logger.error('(%s) Attemped to remove OpenBridge Stream ID %s not in the Stream ID list: %s', system, int_id(stream_id), [id for id in systems[system].STATUS]) @@ -236,7 +239,8 @@ class routerOBP(OPENBRIDGE): 'START': pkt_time, 'CONTENTION':False, 'RFS': _rf_src, - 'TGID': _dst_id, + 'TYPE': 'GROUP', + 'TGID': _dst_id } # If we can, use the LC from the voice header as to keep all options intact @@ -275,7 +279,8 @@ class routerOBP(OPENBRIDGE): 'START': pkt_time, 'CONTENTION':False, 'RFS': _rf_src, - 'TGID': _dst_id, + 'TYPE': 'GROUP' + 'DST': _dst_id, } # Generate LCs (full and EMB) for the TX stream dst_lc = b''.join([self.STATUS[_stream_id]['LC'][0:3], _target['TGID'], _rf_src]) @@ -433,7 +438,8 @@ class routerOBP(OPENBRIDGE): 'START': pkt_time, 'CONTENTION':False, 'RFS': _rf_src, - 'TGID': _dst_id, + 'TYPE': 'UNIT', + 'TGID': _dst_id } # Create a destination list for the call: @@ -467,7 +473,8 @@ class routerOBP(OPENBRIDGE): 'START': pkt_time, 'CONTENTION':False, 'RFS': _rf_src, - 'TGID': _dst_id, + 'TYPE': 'UNIT' + 'DST': _dst_id, } logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot, int_id(_dst_id)) @@ -493,6 +500,7 @@ class routerOBP(OPENBRIDGE): # From the same group as the last TX to this HBSystem, but from a different subscriber, and it has been less than stream timeout # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules # + ''' if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): if self.STATUS[_stream_id]['CONTENTION'] == False: self.STATUS[_stream_id]['CONTENTION'] = True @@ -503,6 +511,7 @@ class routerOBP(OPENBRIDGE): self.STATUS[_stream_id]['CONTENTION'] = True logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) continue + ''' if (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): if self.STATUS[_stream_id]['CONTENTION'] == False: self.STATUS[_stream_id]['CONTENTION'] = True @@ -676,7 +685,8 @@ class routerHBP(HBSYSTEM): 'START': pkt_time, 'CONTENTION':False, 'RFS': _rf_src, - 'TGID': _dst_id, + 'TYPE': 'GROUP', + 'DST': _dst_id, } # Generate LCs (full and EMB) for the TX stream dst_lc = b''.join([self.STATUS[_slot]['RX_LC'][0:3], _target['TGID'], _rf_src]) @@ -892,14 +902,12 @@ class routerHBP(HBSYSTEM): if _dst_id in UNIT_MAP: if UNIT_MAP[_dst_id][0] != self._system: self._targets = [UNIT_MAP[_dst_id][0]] - #_target_route = UNIT_MAP[_dst_id][0] else: self._targets = [] - logger.debug('UNIT call to a subscriber on the same system, send nothing') + logger.error('UNIT call to a subscriber on the same system, send nothing') else: - self._targets = list(systems) + self._targets = list(UNIT) self._targets.remove(self._system) - #_target_route = 'FLOOD' # This is a new call stream, so log & report self.STATUS[_slot]['RX_START'] = pkt_time @@ -909,9 +917,6 @@ class routerHBP(HBSYSTEM): self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), _target_route).encode(encoding='utf-8', errors='ignore')) for _target in self._targets: - if _target not in UNIT: - logger.debug('(%s) *UNIT CALL NOT FORWARDED* UNIT calling is disabled for this system (EGRESS)', self._system) - continue _target_status = systems[_target].STATUS _target_system = self._CONFIG['SYSTEMS'][_target] @@ -923,10 +928,11 @@ class routerHBP(HBSYSTEM): 'START': pkt_time, 'CONTENTION':False, 'RFS': _rf_src, - 'TGID': _dst_id, + 'TYPE': 'UNIT', + 'DST': _dst_id } - logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot, int_id(_dst_id)) + logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, UNIT: %s', self._system, _target, _slot, int_id(_dst_id)) if CONFIG['REPORTS']['REPORT']: systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) @@ -987,7 +993,12 @@ class routerHBP(HBSYSTEM): #send the call: systems[_target].send_system(_data) - + + if self._CONFIG['SYSTEMS'][_target]['MODE'] == 'OPENBRIDGE': + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): + if (_stream_id in _target_status): + _target_status.pop(_stream_id) + # Final actions - Is this a voice terminator? if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): @@ -1008,13 +1019,12 @@ class routerHBP(HBSYSTEM): self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id - def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): if _call_type == 'group': self.group_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) elif _call_type == 'unit': if self._system not in UNIT: - logger.debug('(%s) *UNIT CALL NOT FORWARDED* UNIT calling is disabled for this system (INGRESS)', self._system) + logger.error('(%s) *UNIT CALL NOT FORWARDED* UNIT calling is disabled for this system (INGRESS)', self._system) else: self.unit_received(_peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data) elif _call_type == 'vscsbk': From 4e3a13daafc80fcfcfaae6e01c3ee91b3b8bfd96 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 19 Feb 2020 19:27:37 +0000 Subject: [PATCH 59/68] fixing typos on obp call tracking --- bridge.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bridge.py b/bridge.py index 47ce580..4b3a438 100755 --- a/bridge.py +++ b/bridge.py @@ -240,7 +240,7 @@ class routerOBP(OPENBRIDGE): 'CONTENTION':False, 'RFS': _rf_src, 'TYPE': 'GROUP', - 'TGID': _dst_id + 'DST': _dst_id } # If we can, use the LC from the voice header as to keep all options intact @@ -279,8 +279,8 @@ class routerOBP(OPENBRIDGE): 'START': pkt_time, 'CONTENTION':False, 'RFS': _rf_src, - 'TYPE': 'GROUP' - 'DST': _dst_id, + 'TYPE': 'GROUP', + 'DST': _dst_id } # Generate LCs (full and EMB) for the TX stream dst_lc = b''.join([self.STATUS[_stream_id]['LC'][0:3], _target['TGID'], _rf_src]) @@ -439,7 +439,7 @@ class routerOBP(OPENBRIDGE): 'CONTENTION':False, 'RFS': _rf_src, 'TYPE': 'UNIT', - 'TGID': _dst_id + 'DST': _dst_id } # Create a destination list for the call: @@ -473,8 +473,8 @@ class routerOBP(OPENBRIDGE): 'START': pkt_time, 'CONTENTION':False, 'RFS': _rf_src, - 'TYPE': 'UNIT' - 'DST': _dst_id, + 'TYPE': 'UNIT', + 'DST': _dst_id } logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot, int_id(_dst_id)) @@ -686,7 +686,7 @@ class routerHBP(HBSYSTEM): 'CONTENTION':False, 'RFS': _rf_src, 'TYPE': 'GROUP', - 'DST': _dst_id, + 'DST': _dst_id } # Generate LCs (full and EMB) for the TX stream dst_lc = b''.join([self.STATUS[_slot]['RX_LC'][0:3], _target['TGID'], _rf_src]) From 21c6bf9281c4757f5baf46f07da7fd7242826f93 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Tue, 3 Mar 2020 22:51:50 +0000 Subject: [PATCH 60/68] save of incremental work --- bridge.py | 50 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/bridge.py b/bridge.py index 4b3a438..237037f 100755 --- a/bridge.py +++ b/bridge.py @@ -206,6 +206,7 @@ def stream_trimmer_loop(): if stream_id in systems[system].STATUS: _stream = systems[system].STATUS[stream_id] _sysconfig = CONFIG['SYSTEMS'][system] + if systems[system].STATUS[stream_id]['ACTIVE']: logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s PEER: %s TYPE: %s DST ID: %s TS 1 Duration: %.2f', \ system, int_id(stream_id), get_alias(int_id(_stream['RFS']), subscriber_ids), get_alias(int_id(_sysconfig['NETWORK_ID']), peer_ids), _stream['TYPE'], get_alias(int_id(_stream['DST']), talkgroup_ids), _stream['LAST'] - _stream['START']) if CONFIG['REPORTS']['REPORT']: @@ -240,7 +241,8 @@ class routerOBP(OPENBRIDGE): 'CONTENTION':False, 'RFS': _rf_src, 'TYPE': 'GROUP', - 'DST': _dst_id + 'DST': _dst_id, + 'ACTIVE': True } # If we can, use the LC from the voice header as to keep all options intact @@ -280,7 +282,8 @@ class routerOBP(OPENBRIDGE): 'CONTENTION':False, 'RFS': _rf_src, 'TYPE': 'GROUP', - 'DST': _dst_id + 'DST': _dst_id, + 'ACTIVE': True } # Generate LCs (full and EMB) for the TX stream dst_lc = b''.join([self.STATUS[_stream_id]['LC'][0:3], _target['TGID'], _rf_src]) @@ -313,7 +316,8 @@ class routerOBP(OPENBRIDGE): dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] if CONFIG['REPORTS']['REPORT']: call_duration = pkt_time - _target_status[_stream_id]['START'] - systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) + _target_status[_stream_id]['ACTIVE']: False + systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) # Create a Burst B-E packet (Embedded LC) elif _dtype_vseq in [1,2,3,4]: dmrbits = dmrbits[0:116] + _target_status[_stream_id]['EMB_LC'][_dtype_vseq] + dmrbits[148:264] @@ -407,7 +411,6 @@ class routerOBP(OPENBRIDGE): #logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - # Final actions - Is this a voice terminator? if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): call_duration = pkt_time - self.STATUS[_stream_id]['START'] @@ -415,10 +418,8 @@ class routerOBP(OPENBRIDGE): self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) - removed = self.STATUS.pop(_stream_id) + _target_status[_stream_id]['ACTIVE']: False logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) - if not removed: - selflogger.error('(%s) *GROUP CALL END* STREAM ID: %s NOT IN LIST -- THIS IS A REAL PROBLEM', self._system, int_id(_stream_id)) def unit_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): @@ -439,7 +440,8 @@ class routerOBP(OPENBRIDGE): 'CONTENTION':False, 'RFS': _rf_src, 'TYPE': 'UNIT', - 'DST': _dst_id + 'DST': _dst_id, + 'ACTIVE': True } # Create a destination list for the call: @@ -474,7 +476,8 @@ class routerOBP(OPENBRIDGE): 'CONTENTION':False, 'RFS': _rf_src, 'TYPE': 'UNIT', - 'DST': _dst_id + 'DST': _dst_id, + 'ACTIVE': True } logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot, int_id(_dst_id)) @@ -489,6 +492,9 @@ class routerOBP(OPENBRIDGE): # Assemble transmit HBP packet _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) _data = b''.join([_tmp_data, dmrpkt]) + + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): + _target_status[_stream_id]['ACTIVE']: False else: # BEGIN STANDARD CONTENTION HANDLING @@ -542,6 +548,11 @@ class routerOBP(OPENBRIDGE): #send the call: systems[_target].send_system(_data) + + if _target_system['MODE'] == 'OPENBRIDGE': + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): + if (_stream_id in _target_status): + _target_status.pop(_stream_id) # Final actions - Is this a voice terminator? @@ -686,7 +697,8 @@ class routerHBP(HBSYSTEM): 'CONTENTION':False, 'RFS': _rf_src, 'TYPE': 'GROUP', - 'DST': _dst_id + 'DST': _dst_id, + 'ACTIVE': True, } # Generate LCs (full and EMB) for the TX stream dst_lc = b''.join([self.STATUS[_slot]['RX_LC'][0:3], _target['TGID'], _rf_src]) @@ -719,6 +731,7 @@ class routerHBP(HBSYSTEM): dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] if CONFIG['REPORTS']['REPORT']: call_duration = pkt_time - _target_status[_stream_id]['START'] + _target_status[_stream_id]['ACTIVE']: False systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) # Create a Burst B-E packet (Embedded LC) elif _dtype_vseq in [1,2,3,4]: @@ -804,7 +817,11 @@ class routerHBP(HBSYSTEM): # Transmit the packet to the destination system systems[_target['SYSTEM']].send_system(_tmp_data) #logger.debug('(%s) Packet routed by bridge: %s to system: %s TS: %s, TGID: %s', self._system, _bridge, _target['SYSTEM'], _target['TS'], int_id(_target['TGID'])) - + + if _target_system['MODE'] == 'OPENBRIDGE': + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): + if (_stream_id in _target_status): + _target_status.pop(_stream_id) # Final actions - Is this a voice terminator? @@ -929,7 +946,8 @@ class routerHBP(HBSYSTEM): 'CONTENTION':False, 'RFS': _rf_src, 'TYPE': 'UNIT', - 'DST': _dst_id + 'DST': _dst_id, + 'ACTIVE': True } logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, UNIT: %s', self._system, _target, _slot, int_id(_dst_id)) @@ -944,6 +962,9 @@ class routerHBP(HBSYSTEM): # Assemble transmit HBP packet _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) _data = b''.join([_tmp_data, dmrpkt]) + + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): + _target_status[_stream_id]['ACTIVE']: False else: # BEGIN STANDARD CONTENTION HANDLING @@ -993,11 +1014,6 @@ class routerHBP(HBSYSTEM): #send the call: systems[_target].send_system(_data) - - if self._CONFIG['SYSTEMS'][_target]['MODE'] == 'OPENBRIDGE': - if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): - if (_stream_id in _target_status): - _target_status.pop(_stream_id) # Final actions - Is this a voice terminator? From f00d29e8a0142c0d1ab3b2ad966990993a670df2 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sat, 14 Mar 2020 10:56:16 -0500 Subject: [PATCH 61/68] Update to sync dev system message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # On branch private-call-dev # Your branch is ahead of 'origin/private-call-dev' by 1 commit. # (use "git push" to publish your local commits) # --- .gitattributes | 0 Dockerfile | 0 data_dev.py | 143 ------------------------------------------------- voice_lib.py | 0 4 files changed, 143 deletions(-) mode change 100644 => 100755 .gitattributes mode change 100644 => 100755 Dockerfile delete mode 100755 data_dev.py mode change 100644 => 100755 voice_lib.py diff --git a/.gitattributes b/.gitattributes old mode 100644 new mode 100755 diff --git a/Dockerfile b/Dockerfile old mode 100644 new mode 100755 diff --git a/data_dev.py b/data_dev.py deleted file mode 100755 index 4532194..0000000 --- a/data_dev.py +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env python -# -############################################################################### -# Copyright (C) 2020 Cortney T. Buffington, N0MJS -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -############################################################################### - -''' -This is a blank application template to build on top of hblink.py. It contains -only the things you need to, essentially, run hblink.py underneath and do nothing -else. The expected behaviour is to override the dmrd_received function from -hblink.py to do somethign meaningful, so that framework is completed, but as -it stands, still does nothing with the DMRD packet. -''' - -# Python modules we need -import sys -from bitarray import bitarray -from time import time -from importlib import import_module -from types import ModuleType - -# Twisted is pretty important, so I keep it separate -from twisted.internet.protocol import Factory, Protocol -from twisted.protocols.basic import NetstringReceiver -from twisted.internet import reactor, task - -# Things we import from the main hblink module -from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, mk_aliases, acl_check -from dmr_utils3.utils import bytes_3, int_id, get_alias -from dmr_utils3 import decode, bptc, const -import config -import log -import const - -# The module needs logging logging, but handlers, etc. are controlled by the parent -import logging -logger = logging.getLogger(__name__) - - -# Does anybody read this stuff? There's a PEP somewhere that says I should do this. -__author__ = 'Cortney T. Buffington, N0MJS' -__copyright__ = 'Copyright (c) 2020 Cortney T. Buffington' -__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT' -__license__ = 'GNU GPLv3' -__maintainer__ = 'Cort Buffington, N0MJS' -__email__ = 'n0mjs@me.com' -__status__ = 'pre-alpha' - -# Module gobal varaibles - - -class dataSYSTEM(HBSYSTEM): - - def __init__(self, _name, _config, _report): - HBSYSTEM.__init__(self, _name, _config, _report) - - def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): - print(_rf_src, _stream_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq) - - -#************************************************ -# MAIN PROGRAM LOOP STARTS HERE -#************************************************ - -if __name__ == '__main__': - import argparse - import sys - import os - import signal - from dmr_utils3.utils import try_download, mk_id_dict - - # Change the current directory to the location of the application - os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) - - # CLI argument parser - handles picking up the config file from the command line, and sending a "help" message - parser = argparse.ArgumentParser() - parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)') - parser.add_argument('-l', '--logging', action='store', dest='LOG_LEVEL', help='Override config file logging level.') - cli_args = parser.parse_args() - - # Ensure we have a path for the config file, if one wasn't specified, then use the default (top of file) - if not cli_args.CONFIG_FILE: - cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg' - - # Call the external routine to build the configuration dictionary - CONFIG = config.build_config(cli_args.CONFIG_FILE) - - # Start the system logger - if cli_args.LOG_LEVEL: - CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL - logger = log.config_logging(CONFIG['LOGGER']) - logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018, 2019\n\tThe Regents of the K0USY Group. All rights reserved.\n') - logger.debug('Logging system started, anything from here on gets logged') - - # Set up the signal handler - def sig_handler(_signal, _frame): - logger.info('SHUTDOWN: DATA APP IS TERMINATING WITH SIGNAL %s', str(_signal)) - hblink_handler(_signal, _frame) - logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR') - reactor.stop() - - # Set signal handers so that we can gracefully exit if need be - for sig in [signal.SIGTERM, signal.SIGINT]: - signal.signal(sig, sig_handler) - - # Create the name-number mapping dictionaries - peer_ids, subscriber_ids, talkgroup_ids = mk_aliases(CONFIG) - - - # INITIALIZE THE REPORTING LOOP - if CONFIG['REPORTS']['REPORT']: - report_server = config_reports(CONFIG, reportFactory) - else: - report_server = None - logger.info('(REPORT) TCP Socket reporting not configured') - - # HBlink instance creation - logger.info('HBlink \'data_dev.py\' -- SYSTEM STARTING...') - for system in CONFIG['SYSTEMS']: - if CONFIG['SYSTEMS'][system]['ENABLED']: - if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': - systems[system] = OPENBRIDGE(system, CONFIG, report_server) - else: - systems[system] = HBSYSTEM(system, CONFIG, report_server) - - reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) - logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) - - reactor.run() diff --git a/voice_lib.py b/voice_lib.py old mode 100644 new mode 100755 From b48e522f850447d156b9628c6c0d04d66d05473e Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Mon, 16 Mar 2020 02:20:24 +0000 Subject: [PATCH 62/68] Now working version of private call routing, on a per-system basis, with the ability to overload OBP with TS information for unit calls. --- bridge.py | 53 ++++++++++++++++++++++++----------------------- config.py | 1 + hblink-SAMPLE.cfg | 5 ++++- hblink.py | 4 ++-- rules_SAMPLE.py | 3 +-- 5 files changed, 35 insertions(+), 31 deletions(-) diff --git a/bridge.py b/bridge.py index 237037f..6530e85 100755 --- a/bridge.py +++ b/bridge.py @@ -207,7 +207,7 @@ def stream_trimmer_loop(): _stream = systems[system].STATUS[stream_id] _sysconfig = CONFIG['SYSTEMS'][system] if systems[system].STATUS[stream_id]['ACTIVE']: - logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s PEER: %s TYPE: %s DST ID: %s TS 1 Duration: %.2f', \ + logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s PEER: %s TYPE: %s DST ID: %s TS 1 Duration: %.2f', \ system, int_id(stream_id), get_alias(int_id(_stream['RFS']), subscriber_ids), get_alias(int_id(_sysconfig['NETWORK_ID']), peer_ids), _stream['TYPE'], get_alias(int_id(_stream['DST']), talkgroup_ids), _stream['LAST'] - _stream['START']) if CONFIG['REPORTS']['REPORT']: if _stream['TYPE'] == 'GROUP': @@ -225,8 +225,8 @@ class routerOBP(OPENBRIDGE): self.name = _name self.STATUS = {} - # list of targets for unit (subscriber, private) calls - self.targets = [] + # list of self._targets for unit (subscriber, private) calls + self._targets = [] def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() @@ -418,7 +418,7 @@ class routerOBP(OPENBRIDGE): self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) - _target_status[_stream_id]['ACTIVE']: False + self.STATUS[_stream_id]['ACTIVE']: False logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) @@ -444,25 +444,24 @@ class routerOBP(OPENBRIDGE): 'ACTIVE': True } - # Create a destination list for the call: + # Create a destination list for the call: if _dst_id in UNIT_MAP: if UNIT_MAP[_dst_id][0] != self._system: self._targets = [UNIT_MAP[_dst_id][0]] - _target_route = UNIT_MAP[_dst_id][0] else: self._targets = [] - logger.debug('UNIT call to a subscriber on the same system, send nothing') + logger.error('UNIT call to a subscriber on the same system, send nothing') else: - self._targets = list(systems) + self._targets = list(UNIT) self._targets.remove(self._system) - _target_route = 'FLOOD' + # This is a new call stream, so log & report self.STATUS['START'] = pkt_time logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT: %s (%s), TS: %s, FORWARD: %s', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, _target_route) + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, self._targets) if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), _target_route).encode(encoding='utf-8', errors='ignore')) + self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), self._targets).encode(encoding='utf-8', errors='ignore')) for _target in self._targets: _target_status = systems[_target].STATUS @@ -480,18 +479,19 @@ class routerOBP(OPENBRIDGE): 'ACTIVE': True } - logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot, int_id(_dst_id)) + logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot if _target_system['BOTH_SLOTS'] else 1, int_id(_dst_id)) if CONFIG['REPORTS']['REPORT']: systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time - # Clear the TS bit -- all OpenBridge streams are effectively on TS1 - _tmp_bits = _bits & ~(1 << 7) + # Clear the TS bit and follow propper OBP definition, unless "BOTH_SLOTS" is set. This only works for unit calls. + if not _target_system['BOTH_SLOTS']: + _tmp_bits = _bits & ~(1 << 7) - # Assemble transmit HBP packet - _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - _data = b''.join([_tmp_data, dmrpkt]) + # Assemble transmit HBP packet + _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + _data = b''.join([_tmp_data, dmrpkt]) if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): _target_status[_stream_id]['ACTIVE']: False @@ -583,8 +583,8 @@ class routerHBP(HBSYSTEM): HBSYSTEM.__init__(self, _name, _config, _report) self.name = _name - # list of targets for unit (subscriber, private) calls - self.targets = [] + # list of self._targets for unit (subscriber, private) calls + self._targets = [] # Status information for the system, TS1 & TS2 # 1 & 2 are "timeslot" @@ -931,7 +931,7 @@ class routerHBP(HBSYSTEM): logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT: %s (%s), TS: %s, FORWARD: %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, self._targets) if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), _target_route).encode(encoding='utf-8', errors='ignore')) + self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), self._targets).encode(encoding='utf-8', errors='ignore')) for _target in self._targets: @@ -950,18 +950,19 @@ class routerHBP(HBSYSTEM): 'ACTIVE': True } - logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, UNIT: %s', self._system, _target, _slot, int_id(_dst_id)) + logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, UNIT: %s', self._system, _target, _slot if _target_system['BOTH_SLOTS'] else 1, int_id(_dst_id)) if CONFIG['REPORTS']['REPORT']: systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time - # Clear the TS bit -- all OpenBridge streams are effectively on TS1 - _tmp_bits = _bits & ~(1 << 7) + # Clear the TS bit and follow propper OBP definition, unless "BOTH_SLOTS" is set. This only works for unit calls. + if not _target_system['BOTH_SLOTS']: + _tmp_bits = _bits & ~(1 << 7) - # Assemble transmit HBP packet - _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - _data = b''.join([_tmp_data, dmrpkt]) + # Assemble transmit HBP packet + _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + _data = b''.join([_tmp_data, dmrpkt]) if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): _target_status[_stream_id]['ACTIVE']: False diff --git a/config.py b/config.py index f86628c..33b2960 100755 --- a/config.py +++ b/config.py @@ -274,6 +274,7 @@ def build_config(_config_file): 'TARGET_SOCK': (gethostbyname(config.get(section, 'TARGET_IP')), config.getint(section, 'TARGET_PORT')), 'TARGET_IP': gethostbyname(config.get(section, 'TARGET_IP')), 'TARGET_PORT': config.getint(section, 'TARGET_PORT'), + 'BOTH_SLOTS': config.getboolean(section, 'BOTH_SLOTS'), 'USE_ACL': config.getboolean(section, 'USE_ACL'), 'SUB_ACL': config.get(section, 'SUB_ACL'), 'TG1_ACL': config.get(section, 'TGID_ACL'), diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index a3b616c..c2e789d 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -120,7 +120,9 @@ STALE_DAYS: 7 # # ACLs: # OpenBridge does not 'register', so registration ACL is meaningless. -# OpenBridge passes all traffic on TS1, so there is only 1 TGID ACL. +# Proper OpenBridge passes all traffic on TS1. +# HBlink can extend OPB to use both slots for unit calls only. +# Setting "BOTH_SLOTS" True ONLY affects unit traffic! # Otherwise ACLs work as described in the global stanza [OBP-1] MODE: OPENBRIDGE @@ -131,6 +133,7 @@ NETWORK_ID: 3129100 PASSPHRASE: password TARGET_IP: 1.2.3.4 TARGET_PORT: 62035 +BOTH_SLOTS: True USE_ACL: True SUB_ACL: DENY:1 TGID_ACL: PERMIT:ALL diff --git a/hblink.py b/hblink.py index bd2e94f..76aab3d 100755 --- a/hblink.py +++ b/hblink.py @@ -160,8 +160,8 @@ class OPENBRIDGE(DatagramProtocol): _stream_id = _data[16:20] #logger.debug('(%s) DMRD - Seqence: %s, RF Source: %s, Destination ID: %s', self._system, int_id(_seq), int_id(_rf_src), int_id(_dst_id)) - # Sanity check for OpenBridge -- all calls must be on Slot 1 - if _slot != 1: + # Sanity check for OpenBridge -- all calls must be on Slot 1 for Brandmeister or DMR+. Other HBlinks can process timeslot on OPB if the flag is set + if _slot != 1 and not self._config['BOTH_SLOTS'] and not _call_type == 'unit': logger.error('(%s) OpenBridge packet discarded because it was not received on slot 1. SID: %s, TGID %s', self._system, int_id(_rf_src), int_id(_dst_id)) return diff --git a/rules_SAMPLE.py b/rules_SAMPLE.py index a86d02d..b46a46a 100755 --- a/rules_SAMPLE.py +++ b/rules_SAMPLE.py @@ -48,8 +48,7 @@ BRIDGES = { } ''' -list the names of each system that should process unit to unit (individual) calls. Processing -is both ingress and egress. +list the names of each system that should bridge unit to unit (individual) calls. ''' UNIT = ['ONE', 'TWO'] From fdbfbf2362f7e95deeca9a77b7405bb3a719ed13 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Sun, 15 Mar 2020 21:23:03 -0500 Subject: [PATCH 63/68] READY FOR TESTING unit calls should be working on HBP and OBP systems. Also adds the ability to send TS data in OBP for unit calls to preserve timeslot routing for unit calls. --- bridge.py | 53 ++++++++++++++++++++++++----------------------- config.py | 1 + hblink-SAMPLE.cfg | 5 ++++- hblink.py | 4 ++-- rules_SAMPLE.py | 3 +-- 5 files changed, 35 insertions(+), 31 deletions(-) diff --git a/bridge.py b/bridge.py index 237037f..6530e85 100755 --- a/bridge.py +++ b/bridge.py @@ -207,7 +207,7 @@ def stream_trimmer_loop(): _stream = systems[system].STATUS[stream_id] _sysconfig = CONFIG['SYSTEMS'][system] if systems[system].STATUS[stream_id]['ACTIVE']: - logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s PEER: %s TYPE: %s DST ID: %s TS 1 Duration: %.2f', \ + logger.info('(%s) *TIME OUT* STREAM ID: %s SUB: %s PEER: %s TYPE: %s DST ID: %s TS 1 Duration: %.2f', \ system, int_id(stream_id), get_alias(int_id(_stream['RFS']), subscriber_ids), get_alias(int_id(_sysconfig['NETWORK_ID']), peer_ids), _stream['TYPE'], get_alias(int_id(_stream['DST']), talkgroup_ids), _stream['LAST'] - _stream['START']) if CONFIG['REPORTS']['REPORT']: if _stream['TYPE'] == 'GROUP': @@ -225,8 +225,8 @@ class routerOBP(OPENBRIDGE): self.name = _name self.STATUS = {} - # list of targets for unit (subscriber, private) calls - self.targets = [] + # list of self._targets for unit (subscriber, private) calls + self._targets = [] def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _dtype_vseq, _stream_id, _data): pkt_time = time() @@ -418,7 +418,7 @@ class routerOBP(OPENBRIDGE): self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) - _target_status[_stream_id]['ACTIVE']: False + self.STATUS[_stream_id]['ACTIVE']: False logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) @@ -444,25 +444,24 @@ class routerOBP(OPENBRIDGE): 'ACTIVE': True } - # Create a destination list for the call: + # Create a destination list for the call: if _dst_id in UNIT_MAP: if UNIT_MAP[_dst_id][0] != self._system: self._targets = [UNIT_MAP[_dst_id][0]] - _target_route = UNIT_MAP[_dst_id][0] else: self._targets = [] - logger.debug('UNIT call to a subscriber on the same system, send nothing') + logger.error('UNIT call to a subscriber on the same system, send nothing') else: - self._targets = list(systems) + self._targets = list(UNIT) self._targets.remove(self._system) - _target_route = 'FLOOD' + # This is a new call stream, so log & report self.STATUS['START'] = pkt_time logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT: %s (%s), TS: %s, FORWARD: %s', \ - self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, _target_route) + self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, self._targets) if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), _target_route).encode(encoding='utf-8', errors='ignore')) + self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), self._targets).encode(encoding='utf-8', errors='ignore')) for _target in self._targets: _target_status = systems[_target].STATUS @@ -480,18 +479,19 @@ class routerOBP(OPENBRIDGE): 'ACTIVE': True } - logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot, int_id(_dst_id)) + logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, TGID: %s', self._system, _target, _slot if _target_system['BOTH_SLOTS'] else 1, int_id(_dst_id)) if CONFIG['REPORTS']['REPORT']: systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time - # Clear the TS bit -- all OpenBridge streams are effectively on TS1 - _tmp_bits = _bits & ~(1 << 7) + # Clear the TS bit and follow propper OBP definition, unless "BOTH_SLOTS" is set. This only works for unit calls. + if not _target_system['BOTH_SLOTS']: + _tmp_bits = _bits & ~(1 << 7) - # Assemble transmit HBP packet - _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - _data = b''.join([_tmp_data, dmrpkt]) + # Assemble transmit HBP packet + _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + _data = b''.join([_tmp_data, dmrpkt]) if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): _target_status[_stream_id]['ACTIVE']: False @@ -583,8 +583,8 @@ class routerHBP(HBSYSTEM): HBSYSTEM.__init__(self, _name, _config, _report) self.name = _name - # list of targets for unit (subscriber, private) calls - self.targets = [] + # list of self._targets for unit (subscriber, private) calls + self._targets = [] # Status information for the system, TS1 & TS2 # 1 & 2 are "timeslot" @@ -931,7 +931,7 @@ class routerHBP(HBSYSTEM): logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT: %s (%s), TS: %s, FORWARD: %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, self._targets) if CONFIG['REPORTS']['REPORT']: - self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), _target_route).encode(encoding='utf-8', errors='ignore')) + self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), self._targets).encode(encoding='utf-8', errors='ignore')) for _target in self._targets: @@ -950,18 +950,19 @@ class routerHBP(HBSYSTEM): 'ACTIVE': True } - logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, UNIT: %s', self._system, _target, _slot, int_id(_dst_id)) + logger.info('(%s) Unit call bridged to OBP System: %s TS: %s, UNIT: %s', self._system, _target, _slot if _target_system['BOTH_SLOTS'] else 1, int_id(_dst_id)) if CONFIG['REPORTS']['REPORT']: systems[_target]._report.send_bridgeEvent('UNIT VOICE,START,TX,{},{},{},{},{},{}'.format(_target, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id)).encode(encoding='utf-8', errors='ignore')) # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time - # Clear the TS bit -- all OpenBridge streams are effectively on TS1 - _tmp_bits = _bits & ~(1 << 7) + # Clear the TS bit and follow propper OBP definition, unless "BOTH_SLOTS" is set. This only works for unit calls. + if not _target_system['BOTH_SLOTS']: + _tmp_bits = _bits & ~(1 << 7) - # Assemble transmit HBP packet - _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - _data = b''.join([_tmp_data, dmrpkt]) + # Assemble transmit HBP packet + _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + _data = b''.join([_tmp_data, dmrpkt]) if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): _target_status[_stream_id]['ACTIVE']: False diff --git a/config.py b/config.py index f86628c..33b2960 100755 --- a/config.py +++ b/config.py @@ -274,6 +274,7 @@ def build_config(_config_file): 'TARGET_SOCK': (gethostbyname(config.get(section, 'TARGET_IP')), config.getint(section, 'TARGET_PORT')), 'TARGET_IP': gethostbyname(config.get(section, 'TARGET_IP')), 'TARGET_PORT': config.getint(section, 'TARGET_PORT'), + 'BOTH_SLOTS': config.getboolean(section, 'BOTH_SLOTS'), 'USE_ACL': config.getboolean(section, 'USE_ACL'), 'SUB_ACL': config.get(section, 'SUB_ACL'), 'TG1_ACL': config.get(section, 'TGID_ACL'), diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index a3b616c..c2e789d 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -120,7 +120,9 @@ STALE_DAYS: 7 # # ACLs: # OpenBridge does not 'register', so registration ACL is meaningless. -# OpenBridge passes all traffic on TS1, so there is only 1 TGID ACL. +# Proper OpenBridge passes all traffic on TS1. +# HBlink can extend OPB to use both slots for unit calls only. +# Setting "BOTH_SLOTS" True ONLY affects unit traffic! # Otherwise ACLs work as described in the global stanza [OBP-1] MODE: OPENBRIDGE @@ -131,6 +133,7 @@ NETWORK_ID: 3129100 PASSPHRASE: password TARGET_IP: 1.2.3.4 TARGET_PORT: 62035 +BOTH_SLOTS: True USE_ACL: True SUB_ACL: DENY:1 TGID_ACL: PERMIT:ALL diff --git a/hblink.py b/hblink.py index bd2e94f..76aab3d 100755 --- a/hblink.py +++ b/hblink.py @@ -160,8 +160,8 @@ class OPENBRIDGE(DatagramProtocol): _stream_id = _data[16:20] #logger.debug('(%s) DMRD - Seqence: %s, RF Source: %s, Destination ID: %s', self._system, int_id(_seq), int_id(_rf_src), int_id(_dst_id)) - # Sanity check for OpenBridge -- all calls must be on Slot 1 - if _slot != 1: + # Sanity check for OpenBridge -- all calls must be on Slot 1 for Brandmeister or DMR+. Other HBlinks can process timeslot on OPB if the flag is set + if _slot != 1 and not self._config['BOTH_SLOTS'] and not _call_type == 'unit': logger.error('(%s) OpenBridge packet discarded because it was not received on slot 1. SID: %s, TGID %s', self._system, int_id(_rf_src), int_id(_dst_id)) return diff --git a/rules_SAMPLE.py b/rules_SAMPLE.py index a86d02d..b46a46a 100755 --- a/rules_SAMPLE.py +++ b/rules_SAMPLE.py @@ -48,8 +48,7 @@ BRIDGES = { } ''' -list the names of each system that should process unit to unit (individual) calls. Processing -is both ingress and egress. +list the names of each system that should bridge unit to unit (individual) calls. ''' UNIT = ['ONE', 'TWO'] From a2c0bb54c6125ca5f757004b251408ed2be2e6e3 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Mon, 16 Mar 2020 08:38:16 -0500 Subject: [PATCH 64/68] Fixed typo in call state tracking. --- bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge.py b/bridge.py index 6530e85..7d0faf4 100755 --- a/bridge.py +++ b/bridge.py @@ -316,7 +316,7 @@ class routerOBP(OPENBRIDGE): dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] if CONFIG['REPORTS']['REPORT']: call_duration = pkt_time - _target_status[_stream_id]['START'] - _target_status[_stream_id]['ACTIVE']: False + _target_status[_stream_id]['ACTIVE'] = False systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) # Create a Burst B-E packet (Embedded LC) elif _dtype_vseq in [1,2,3,4]: From 8c1e9e518525ef2b2526a476db3821a88bd773a1 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Mon, 16 Mar 2020 14:35:28 +0000 Subject: [PATCH 65/68] update coypright years --- bridge.py | 2 +- hblink.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bridge.py b/bridge.py index 7d0faf4..e1ef54b 100755 --- a/bridge.py +++ b/bridge.py @@ -1100,7 +1100,7 @@ if __name__ == '__main__': if cli_args.LOG_LEVEL: CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL logger = log.config_logging(CONFIG['LOGGER']) - logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018, 2019\n\tThe Regents of the K0USY Group. All rights reserved.\n') + logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018, 2019, 2020\n\tThe Regents of the K0USY Group. All rights reserved.\n') logger.debug('(GLOBAL) Logging system started, anything from here on gets logged') # Set up the signal handler diff --git a/hblink.py b/hblink.py index 76aab3d..7445012 100755 --- a/hblink.py +++ b/hblink.py @@ -789,7 +789,7 @@ if __name__ == '__main__': if cli_args.LOG_LEVEL: CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL logger = log.config_logging(CONFIG['LOGGER']) - logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018, 2019\n\tThe Regents of the K0USY Group. All rights reserved.\n') + logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018, 2019, 2020\n\tThe Regents of the K0USY Group. All rights reserved.\n') logger.debug('(GLOBAL) Logging system started, anything from here on gets logged') # Set up the signal handler From 24dca2e6ad167daee074fb985b5a15d541f1c8ea Mon Sep 17 00:00:00 2001 From: n0mjs710 Date: Wed, 18 Mar 2020 09:32:32 -0500 Subject: [PATCH 66/68] Fixed assignment typos [_stream_id]['ACTIVE']: False to [_stream_id]['ACTIVE'] = False Thanks, Steve! --- bridge.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bridge.py b/bridge.py index e1ef54b..9294f14 100755 --- a/bridge.py +++ b/bridge.py @@ -418,7 +418,7 @@ class routerOBP(OPENBRIDGE): self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('GROUP VOICE,END,RX,{},{},{},{},{},{},{:.2f}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), call_duration).encode(encoding='utf-8', errors='ignore')) - self.STATUS[_stream_id]['ACTIVE']: False + self.STATUS[_stream_id]['ACTIVE'] = False logger.debug('(%s) OpenBridge sourced call stream end, remove terminated Stream ID: %s', self._system, int_id(_stream_id)) @@ -494,7 +494,7 @@ class routerOBP(OPENBRIDGE): _data = b''.join([_tmp_data, dmrpkt]) if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): - _target_status[_stream_id]['ACTIVE']: False + _target_status[_stream_id]['ACTIVE'] = False else: # BEGIN STANDARD CONTENTION HANDLING @@ -731,7 +731,7 @@ class routerHBP(HBSYSTEM): dmrbits = _target_status[_stream_id]['T_LC'][0:98] + dmrbits[98:166] + _target_status[_stream_id]['T_LC'][98:197] if CONFIG['REPORTS']['REPORT']: call_duration = pkt_time - _target_status[_stream_id]['START'] - _target_status[_stream_id]['ACTIVE']: False + _target_status[_stream_id]['ACTIVE'] = False systems[_target['SYSTEM']]._report.send_bridgeEvent('GROUP VOICE,END,TX,{},{},{},{},{},{},{:.2f}'.format(_target['SYSTEM'], int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _target['TS'], int_id(_target['TGID']), call_duration).encode(encoding='utf-8', errors='ignore')) # Create a Burst B-E packet (Embedded LC) elif _dtype_vseq in [1,2,3,4]: @@ -965,7 +965,7 @@ class routerHBP(HBSYSTEM): _data = b''.join([_tmp_data, dmrpkt]) if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): - _target_status[_stream_id]['ACTIVE']: False + _target_status[_stream_id]['ACTIVE'] = False else: # BEGIN STANDARD CONTENTION HANDLING From ccfd862a0a238662529a8790bda9e921e7d983ea Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Mon, 27 Apr 2020 11:01:54 -0500 Subject: [PATCH 67/68] Fixed Private Call Bridging when BOTH_SLOTS selected This is the problem where bridge.py is told to send private calls marked with EITHER timeslot over openbridge. Packets weren't assembled correctly when the BOTH_SLOTS option was set True in the hblink configuraiton file. d: # modified: bridge.py # modified: hblink.py # # Changes not staged for commit: # modified: .gitattributes # modified: Dockerfile # modified: voice_lib.py # --- bridge.py | 26 ++++++---- hblink-750.cfg | 127 +++++++++++++++++++++++++++++++++++++++++++++++++ hblink-800.cfg | 127 +++++++++++++++++++++++++++++++++++++++++++++++++ rules-750.py | 16 +++++++ rules-800.py | 16 +++++++ 5 files changed, 302 insertions(+), 10 deletions(-) create mode 100755 hblink-750.cfg create mode 100755 hblink-800.cfg create mode 100755 rules-750.py create mode 100755 rules-800.py diff --git a/bridge.py b/bridge.py index 9294f14..fc40c19 100755 --- a/bridge.py +++ b/bridge.py @@ -457,12 +457,14 @@ class routerOBP(OPENBRIDGE): # This is a new call stream, so log & report - self.STATUS['START'] = pkt_time logger.info('(%s) *UNIT CALL START* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT: %s (%s), TS: %s, FORWARD: %s', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, self._targets) if CONFIG['REPORTS']['REPORT']: self._report.send_bridgeEvent('UNIT VOICE,START,RX,{},{},{},{},{},{},{}'.format(self._system, int_id(_stream_id), int_id(_peer_id), int_id(_rf_src), _slot, int_id(_dst_id), self._targets).encode(encoding='utf-8', errors='ignore')) + # Record the time of this packet so we can later identify a stale stream + self.STATUS[_stream_id]['LAST'] = pkt_time + for _target in self._targets: _target_status = systems[_target].STATUS _target_system = self._CONFIG['SYSTEMS'][_target] @@ -486,12 +488,14 @@ class routerOBP(OPENBRIDGE): # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time # Clear the TS bit and follow propper OBP definition, unless "BOTH_SLOTS" is set. This only works for unit calls. - if not _target_system['BOTH_SLOTS']: + if _target_system['BOTH_SLOTS']: + _tmp_bits = _bits + else: _tmp_bits = _bits & ~(1 << 7) - # Assemble transmit HBP packet - _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - _data = b''.join([_tmp_data, dmrpkt]) + # Assemble transmit HBP packet + _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + _data = b''.join([_tmp_data, dmrpkt]) if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): _target_status[_stream_id]['ACTIVE'] = False @@ -558,7 +562,7 @@ class routerOBP(OPENBRIDGE): # Final actions - Is this a voice terminator? if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): self._targets = [] - call_duration = pkt_time - self.STATUS['START'] + call_duration = pkt_time - self.STATUS[_stream_id]['START'] logger.info('(%s) *UNIT CALL END* STREAM ID: %s SUB: %s (%s) PEER: %s (%s) UNIT %s (%s), TS %s, Duration: %.2f', \ self._system, int_id(_stream_id), get_alias(_rf_src, subscriber_ids), int_id(_rf_src), get_alias(_peer_id, peer_ids), int_id(_peer_id), get_alias(_dst_id, talkgroup_ids), int_id(_dst_id), _slot, call_duration) if CONFIG['REPORTS']['REPORT']: @@ -957,12 +961,14 @@ class routerHBP(HBSYSTEM): # Record the time of this packet so we can later identify a stale stream _target_status[_stream_id]['LAST'] = pkt_time # Clear the TS bit and follow propper OBP definition, unless "BOTH_SLOTS" is set. This only works for unit calls. - if not _target_system['BOTH_SLOTS']: + if _target_system['BOTH_SLOTS']: + _tmp_bits = _bits + else: _tmp_bits = _bits & ~(1 << 7) - # Assemble transmit HBP packet - _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) - _data = b''.join([_tmp_data, dmrpkt]) + # Assemble transmit HBP packet + _tmp_data = b''.join([_data[:15], _tmp_bits.to_bytes(1, 'big'), _data[16:20]]) + _data = b''.join([_tmp_data, dmrpkt]) if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM): _target_status[_stream_id]['ACTIVE'] = False diff --git a/hblink-750.cfg b/hblink-750.cfg new file mode 100755 index 0000000..aecfff4 --- /dev/null +++ b/hblink-750.cfg @@ -0,0 +1,127 @@ +[GLOBAL] +PATH: ./ +PING_TIME: 5 +MAX_MISSED: 3 +USE_ACL: False +REG_ACL: DENY:1 +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL + +[REPORTS] +REPORT: False +REPORT_INTERVAL: 60 +REPORT_PORT: 4321 +REPORT_CLIENTS: 127.0.0.1 + +[LOGGER] +LOG_FILE: /tmp/hblink.log +LOG_HANDLERS: console-timed +LOG_LEVEL: INFO +LOG_NAME: 444.750 + +[ALIASES] +TRY_DOWNLOAD: False +PATH: ./ +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 + +[OBP] +MODE: OPENBRIDGE +ENABLED: True +IP: +PORT: 50100 +NETWORK_ID: 1 +PASSPHRASE: deadbeef +#TARGET_IP: olympic.k0usy.org +TARGET_IP: 127.0.0.1 +#TARGET_PORT: 50666 +TARGET_PORT: 50101 +BOTH_SLOTS: True +USE_ACL: False +SUB_ACL: DENY:1 +TGID_ACL: PERMIT:ALL + +[444.750] +MODE: MASTER +ENABLED: True +REPEAT: True +MAX_PEERS: 5 +EXPORT_AMBE: False +IP: +PORT: 50001 +PASSPHRASE: jimmy +GROUP_HANGTIME: 10 +USE_ACL: False +REG_ACL: DENY:1 +SUB_ACL: DENY:1 +TGID_TS1_ACL: DENY:8 +TGID_TS2_ACL: PERMIT:3120 + +[TWO] +MODE: MASTER +ENABLED: False +REPEAT: True +MAX_PEERS: 5 +EXPORT_AMBE: False +IP: +PORT:50002 +PASSPHRASE: jimmy +GROUP_HANGTIME: 10 +USE_ACL: False +REG_ACL: DENY:1 +SUB_ACL: DENY:1 +TGID_TS1_ACL: DENY:8 +TGID_TS2_ACL: PERMIT:3120 + +[THREE] +MODE: MASTER +ENABLED: False +REPEAT: True +MAX_PEERS: 5 +EXPORT_AMBE: False +IP: +PORT:50003 +PASSPHRASE: jimmy +GROUP_HANGTIME: 10 +USE_ACL: False +REG_ACL: DENY:1 +SUB_ACL: DENY:1 +TGID_TS1_ACL: DENY:8 +TGID_TS2_ACL: PERMIT:3120 + +[KS-DMR] +MODE: PEER +ENABLED: False +LOOSE: False +EXPORT_AMBE: False +IP: +PORT: 54001 +MASTER_IP: olympic.k0usy.org +MASTER_PORT: 62071 +PASSPHRASE: c0ffee +CALLSIGN: W1ABC +RADIO_ID: 312312 +RX_FREQ: 449000000 +TX_FREQ: 444000000 +TX_POWER: 25 +COLORCODE: 1 +SLOTS: 1 +LATITUDE: 38.0000 +LONGITUDE: -095.0000 +HEIGHT: 75 +LOCATION: Anywhere, USA +DESCRIPTION: This is a cool repeater +URL: www.w1abc.org +SOFTWARE_ID: 20170620 +PACKAGE_ID: MMDVM_HBlink +GROUP_HANGTIME: 5 +OPTIONS: +USE_ACL: True +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL diff --git a/hblink-800.cfg b/hblink-800.cfg new file mode 100755 index 0000000..0fe46db --- /dev/null +++ b/hblink-800.cfg @@ -0,0 +1,127 @@ +[GLOBAL] +PATH: ./ +PING_TIME: 5 +MAX_MISSED: 3 +USE_ACL: False +REG_ACL: DENY:1 +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL + +[REPORTS] +REPORT: False +REPORT_INTERVAL: 60 +REPORT_PORT: 4321 +REPORT_CLIENTS: 127.0.0.1 + +[LOGGER] +LOG_FILE: /tmp/hblink.log +LOG_HANDLERS: console-timed +LOG_LEVEL: INFO +LOG_NAME: 444.800 + +[ALIASES] +TRY_DOWNLOAD: False +PATH: ./ +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 + +[OBP] +MODE: OPENBRIDGE +ENABLED: True +IP: +PORT: 50101 +NETWORK_ID: 2 +PASSPHRASE: deadbeef +#TARGET_IP: olympic.k0usy.org +TARGET_IP: 127.0.0.1 +#TARGET_PORT: 50666 +TARGET_PORT: 50100 +BOTH_SLOTS: True +USE_ACL: False +SUB_ACL: DENY:1 +TGID_ACL: PERMIT:ALL + +[444.800] +MODE: MASTER +ENABLED: True +REPEAT: True +MAX_PEERS: 5 +EXPORT_AMBE: False +IP: +PORT: 50011 +PASSPHRASE: jimmy +GROUP_HANGTIME: 10 +USE_ACL: False +REG_ACL: DENY:1 +SUB_ACL: DENY:1 +TGID_TS1_ACL: DENY:8 +TGID_TS2_ACL: PERMIT:3120 + +[TWO] +MODE: MASTER +ENABLED: False +REPEAT: True +MAX_PEERS: 5 +EXPORT_AMBE: False +IP: +PORT:50012 +PASSPHRASE: jimmy +GROUP_HANGTIME: 10 +USE_ACL: False +REG_ACL: DENY:1 +SUB_ACL: DENY:1 +TGID_TS1_ACL: DENY:8 +TGID_TS2_ACL: PERMIT:3120 + +[THREE] +MODE: MASTER +ENABLED: False +REPEAT: True +MAX_PEERS: 5 +EXPORT_AMBE: False +IP: +PORT:50013 +PASSPHRASE: jimmy +GROUP_HANGTIME: 10 +USE_ACL: False +REG_ACL: DENY:1 +SUB_ACL: DENY:1 +TGID_TS1_ACL: DENY:8 +TGID_TS2_ACL: PERMIT:3120 + +[KS-DMR] +MODE: PEER +ENABLED: False +LOOSE: False +EXPORT_AMBE: False +IP: +PORT: 54011 +MASTER_IP: olympic.k0usy.org +MASTER_PORT: 62071 +PASSPHRASE: c0ffee +CALLSIGN: W1ABC +RADIO_ID: 312312 +RX_FREQ: 449000000 +TX_FREQ: 444000000 +TX_POWER: 25 +COLORCODE: 1 +SLOTS: 1 +LATITUDE: 38.0000 +LONGITUDE: -095.0000 +HEIGHT: 75 +LOCATION: Anywhere, USA +DESCRIPTION: This is a cool repeater +URL: www.w1abc.org +SOFTWARE_ID: 20170620 +PACKAGE_ID: MMDVM_HBlink +GROUP_HANGTIME: 5 +OPTIONS: +USE_ACL: True +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL diff --git a/rules-750.py b/rules-750.py new file mode 100755 index 0000000..b0c1b5f --- /dev/null +++ b/rules-750.py @@ -0,0 +1,16 @@ +BRIDGES = { + '1/2': [ + {'SYSTEM': '444.750', 'TS': 1, 'TGID': 2, 'ACTIVE': True, 'TIMEOUT': 5,'TO_TYPE': 'NONE', 'ON': [], 'OFF': [], 'RESET': []}, + {'SYSTEM': 'OBP', 'TS': 1, 'TGID': 2, 'ACTIVE': True, 'TIMEOUT': 5,'TO_TYPE': 'NONE', 'ON': [], 'OFF': [], 'RESET': []} + ], + 'KANSAS': [ + {'SYSTEM': '444.750', 'TS': 2, 'TGID': 3120, 'ACTIVE': True, 'TIMEOUT': 5,'TO_TYPE': 'NONE', 'ON': [], 'OFF': [], 'RESET': []}, + {'SYSTEM': 'OBP', 'TS': 1, 'TGID': 3120, 'ACTIVE': True, 'TIMEOUT': 5,'TO_TYPE': 'NONE', 'ON': [], 'OFF': [], 'RESET': []} + ] +} + +UNIT = ['444.750', 'OBP'] + +if __name__ == '__main__': + from pprint import pprint + pprint(BRIDGES) diff --git a/rules-800.py b/rules-800.py new file mode 100755 index 0000000..0d10f53 --- /dev/null +++ b/rules-800.py @@ -0,0 +1,16 @@ +BRIDGES = { + '1/2': [ + {'SYSTEM': '444.800', 'TS': 1, 'TGID': 2, 'ACTIVE': True, 'TIMEOUT': 5,'TO_TYPE': 'NONE', 'ON': [], 'OFF': [], 'RESET': []}, + {'SYSTEM': 'OBP', 'TS': 1, 'TGID': 2, 'ACTIVE': True, 'TIMEOUT': 5,'TO_TYPE': 'NONE', 'ON': [], 'OFF': [], 'RESET': []} + ], + 'KANSAS': [ + {'SYSTEM': '444.800', 'TS': 2, 'TGID': 3120, 'ACTIVE': True, 'TIMEOUT': 5,'TO_TYPE': 'NONE', 'ON': [], 'OFF': [], 'RESET': []}, + {'SYSTEM': 'OBP', 'TS': 1, 'TGID': 3120, 'ACTIVE': True, 'TIMEOUT': 5,'TO_TYPE': 'NONE', 'ON': [], 'OFF': [], 'RESET': []} + ] +} + +UNIT = ["444.800", "OBP"] + +if __name__ == '__main__': + from pprint import pprint + pprint(BRIDGES) From 89040e7c14d3d2b356cb2bcccce4650ef4571807 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Mon, 12 Oct 2020 06:37:21 -0500 Subject: [PATCH 68/68] Remove Cruft --- Dockerfile | 30 ---------- app_template.py | 150 ---------------------------------------------- blank_app.py | 143 ------------------------------------------- config.py | 46 -------------- entrypoint | 19 ------ hblink-SAMPLE.cfg | 34 +---------- hblink.py | 36 +---------- 7 files changed, 2 insertions(+), 456 deletions(-) delete mode 100644 Dockerfile delete mode 100755 app_template.py delete mode 100755 blank_app.py delete mode 100755 entrypoint diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 842c902..0000000 --- a/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -FROM python:3.7-slim-stretch - -RUN apt update && \ - apt install -y git && \ - cd /usr/src/ && \ - git clone https://github.com/n0mjs710/dmr_utils3 && \ - cd /usr/src/dmr_utils3 && \ - ./install.sh && \ - rm -rf /var/lib/apt/lists/* && \ - cd /opt && \ - rm -rf /usr/src/dmr_utils3 && \ - git clone https://github.com/n0mjs710/hblink3 -ENV AAA BBBB -RUN cd /opt/hblink3/ && \ - sed -i s/.*python.*//g requirements.txt && \ - pip install --no-cache-dir -r requirements.txt - - -ADD entrypoint /entrypoint - -RUN adduser -u 54000 radio && \ - adduser radio radio && \ - chmod 755 /entrypoint && \ - chown radio:radio /entrypoint && \ - chown radio /opt/hblink3 - -USER radio -EXPOSE 54000 - -ENTRYPOINT [ "/entrypoint" ] diff --git a/app_template.py b/app_template.py deleted file mode 100755 index b919960..0000000 --- a/app_template.py +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env python -# -############################################################################### -# Copyright (C) 2016-2019 Cortney T. Buffington, N0MJS -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -############################################################################### - -# Python modules we need -import sys -from bitarray import bitarray -from time import time -from importlib import import_module - -# Twisted is pretty important, so I keep it separate -from twisted.internet.protocol import Factory, Protocol -from twisted.protocols.basic import NetstringReceiver -from twisted.internet import reactor, task - -# Things we import from the main hblink module -from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, mk_aliases -from dmr_utils3.utils import bytes_3, int_id, get_alias -from dmr_utils3 import decode, bptc, const -import config -import log -from const import * - -# Stuff for socket reporting -import pickle -# REMOVE LATER from datetime import datetime -# The module needs logging, but handlers, etc. are controlled by the parent -import logging -logger = logging.getLogger(__name__) - - -# 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-2018 Cortney T. Buffington, N0MJS and the K0USY Group' -__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT' -__license__ = 'GNU GPLv3' -__maintainer__ = 'Cort Buffington, N0MJS' -__email__ = 'n0mjs@me.com' - -# Module gobal varaibles - - -class OBP(OPENBRIDGE): - - def __init__(self, _name, _config, _report): - OPENBRIDGE.__init__(self, _name, _config, _report) - - - def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): - pass - - -class HBP(HBSYSTEM): - - def __init__(self, _name, _config, _report): - HBSYSTEM.__init__(self, _name, _config, _report) - - def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): - pass - - - -#************************************************ -# MAIN PROGRAM LOOP STARTS HERE -#************************************************ - -if __name__ == '__main__': - - import argparse - import sys - import os - import signal - - # Change the current directory to the location of the application - os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) - - # CLI argument parser - handles picking up the config file from the command line, and sending a "help" message - parser = argparse.ArgumentParser() - parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)') - parser.add_argument('-l', '--logging', action='store', dest='LOG_LEVEL', help='Override config file logging level.') - cli_args = parser.parse_args() - - # Ensure we have a path for the config file, if one wasn't specified, then use the default (top of file) - if not cli_args.CONFIG_FILE: - cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg' - - # Call the external routine to build the configuration dictionary - CONFIG = config.build_config(cli_args.CONFIG_FILE) - - # Start the system logger - if cli_args.LOG_LEVEL: - CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL - logger = log.config_logging(CONFIG['LOGGER']) - logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018\n\tThe Regents of the K0USY Group. All rights reserved.\n') - logger.debug('(GLOBAL) Logging system started, anything from here on gets logged') - - # Set up the signal handler - def sig_handler(_signal, _frame): - logger.info('(GLOBAL) SHUTDOWN: CONFBRIDGE IS TERMINATING WITH SIGNAL %s', str(_signal)) - hblink_handler(_signal, _frame) - logger.info('(GLOBAL) SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR') - reactor.stop() - - # Set signal handers so that we can gracefully exit if need be - for sig in [signal.SIGINT, signal.SIGTERM]: - signal.signal(sig, sig_handler) - - # Create the name-number mapping dictionaries - peer_ids, subscriber_ids, talkgroup_ids = mk_aliases(CONFIG) - - # INITIALIZE THE REPORTING LOOP - if CONFIG['REPORTS']['REPORT']: - report_server = config_reports(CONFIG, bridgeReportFactory) - else: - report_server = None - logger.info('(REPORT) TCP Socket reporting not configured') - - # HBlink instance creation - logger.info('(GLOBAL) HBlink \'bridge.py\' -- SYSTEM STARTING...') - for system in CONFIG['SYSTEMS']: - if CONFIG['SYSTEMS'][system]['ENABLED']: - if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': - systems[system] = OBP(system, CONFIG, report_server) - else: - systems[system] = HBP(system, CONFIG, report_server) - reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) - logger.debug('(GLOBAL) %s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) - - def loopingErrHandle(failure): - logger.error('(GLOBAL) STOPPING REACTOR TO AVOID MEMORY LEAK: Unhandled error in timed loop.\n %s', failure) - reactor.stop() - - - reactor.run() diff --git a/blank_app.py b/blank_app.py deleted file mode 100755 index b13e29a..0000000 --- a/blank_app.py +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env python -# -############################################################################### -# Copyright (C) 2020 Cortney T. Buffington, N0MJS -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -############################################################################### - -''' -This is a blank application template to build on top of hblink.py. It contains -only the things you need to, essentially, run hblink.py underneath and do nothing -else. The expected behaviour is to override the dmrd_received function from -hblink.py to do somethign meaningful, so that framework is completed, but as -it stands, still does nothing with the DMRD packet. -''' - -# Python modules we need -import sys -from bitarray import bitarray -from time import time -from importlib import import_module -from types import ModuleType - -# Twisted is pretty important, so I keep it separate -from twisted.internet.protocol import Factory, Protocol -from twisted.protocols.basic import NetstringReceiver -from twisted.internet import reactor, task - -# Things we import from the main hblink module -from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, mk_aliases, acl_check -from dmr_utils3.utils import bytes_3, int_id, get_alias -from dmr_utils3 import decode, bptc, const -import config -import log -import const - -# The module needs logging logging, but handlers, etc. are controlled by the parent -import logging -logger = logging.getLogger(__name__) - - -# Does anybody read this stuff? There's a PEP somewhere that says I should do this. -__author__ = 'Cortney T. Buffington, N0MJS' -__copyright__ = 'Copyright (c) 2020 Cortney T. Buffington' -__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT' -__license__ = 'GNU GPLv3' -__maintainer__ = 'Cort Buffington, N0MJS' -__email__ = 'n0mjs@me.com' -__status__ = 'pre-alpha' - -# Module gobal varaibles - - -class blankSYSTEM(HBSYSTEM): - - def __init__(self, _name, _config, _report): - HBSYSTEM.__init__(self, _name, _config, _report) - - def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): - pass - - -#************************************************ -# MAIN PROGRAM LOOP STARTS HERE -#************************************************ - -if __name__ == '__main__': - import argparse - import sys - import os - import signal - from dmr_utils3.utils import try_download, mk_id_dict - - # Change the current directory to the location of the application - os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) - - # CLI argument parser - handles picking up the config file from the command line, and sending a "help" message - parser = argparse.ArgumentParser() - parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)') - parser.add_argument('-l', '--logging', action='store', dest='LOG_LEVEL', help='Override config file logging level.') - cli_args = parser.parse_args() - - # Ensure we have a path for the config file, if one wasn't specified, then use the default (top of file) - if not cli_args.CONFIG_FILE: - cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg' - - # Call the external routine to build the configuration dictionary - CONFIG = config.build_config(cli_args.CONFIG_FILE) - - # Start the system logger - if cli_args.LOG_LEVEL: - CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL - logger = log.config_logging(CONFIG['LOGGER']) - logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018, 2019\n\tThe Regents of the K0USY Group. All rights reserved.\n') - logger.debug('Logging system started, anything from here on gets logged') - - # Set up the signal handler - def sig_handler(_signal, _frame): - logger.info('SHUTDOWN: >>>BLANK APP<<< IS TERMINATING WITH SIGNAL %s', str(_signal)) - hblink_handler(_signal, _frame) - logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR') - reactor.stop() - - # Set signal handers so that we can gracefully exit if need be - for sig in [signal.SIGTERM, signal.SIGINT]: - signal.signal(sig, sig_handler) - - # Create the name-number mapping dictionaries - peer_ids, subscriber_ids, talkgroup_ids = mk_aliases(CONFIG) - - - # INITIALIZE THE REPORTING LOOP - if CONFIG['REPORTS']['REPORT']: - report_server = config_reports(CONFIG, reportFactory) - else: - report_server = None - logger.info('(REPORT) TCP Socket reporting not configured') - - # HBlink instance creation - logger.info('HBlink \'blank_app.py\' -- SYSTEM STARTING...') - for system in CONFIG['SYSTEMS']: - if CONFIG['SYSTEMS'][system]['ENABLED']: - if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE': - systems[system] = OPENBRIDGE(system, CONFIG, report_server) - else: - systems[system] = HBSYSTEM(system, CONFIG, report_server) - - reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) - logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) - - reactor.run() diff --git a/config.py b/config.py index f86628c..c5daed2 100755 --- a/config.py +++ b/config.py @@ -199,52 +199,6 @@ def build_config(_config_file): 'LAST_PING_ACK_TIME': 0, }}) - if config.get(section, 'MODE') == 'XLXPEER': - CONFIG['SYSTEMS'].update({section: { - 'MODE': config.get(section, 'MODE'), - 'ENABLED': config.getboolean(section, 'ENABLED'), - 'LOOSE': config.getboolean(section, 'LOOSE'), - 'SOCK_ADDR': (gethostbyname(config.get(section, 'IP')), config.getint(section, 'PORT')), - 'IP': gethostbyname(config.get(section, 'IP')), - 'PORT': config.getint(section, 'PORT'), - 'MASTER_SOCKADDR': (gethostbyname(config.get(section, 'MASTER_IP')), config.getint(section, 'MASTER_PORT')), - 'MASTER_IP': gethostbyname(config.get(section, 'MASTER_IP')), - 'MASTER_PORT': config.getint(section, 'MASTER_PORT'), - 'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE'), 'utf-8'), - 'CALLSIGN': bytes(config.get(section, 'CALLSIGN').ljust(8)[:8], 'utf-8'), - 'RADIO_ID': config.getint(section, 'RADIO_ID').to_bytes(4, 'big'), - 'RX_FREQ': bytes(config.get(section, 'RX_FREQ').ljust(9)[:9], 'utf-8'), - 'TX_FREQ': bytes(config.get(section, 'TX_FREQ').ljust(9)[:9], 'utf-8'), - 'TX_POWER': bytes(config.get(section, 'TX_POWER').rjust(2,'0'), 'utf-8'), - 'COLORCODE': bytes(config.get(section, 'COLORCODE').rjust(2,'0'), 'utf-8'), - 'LATITUDE': bytes(config.get(section, 'LATITUDE').ljust(8)[:8], 'utf-8'), - 'LONGITUDE': bytes(config.get(section, 'LONGITUDE').ljust(9)[:9], 'utf-8'), - 'HEIGHT': bytes(config.get(section, 'HEIGHT').rjust(3,'0'), 'utf-8'), - 'LOCATION': bytes(config.get(section, 'LOCATION').ljust(20)[:20], 'utf-8'), - 'DESCRIPTION': bytes(config.get(section, 'DESCRIPTION').ljust(19)[:19], 'utf-8'), - 'SLOTS': bytes(config.get(section, 'SLOTS'), 'utf-8'), - 'URL': bytes(config.get(section, 'URL').ljust(124)[:124], 'utf-8'), - 'SOFTWARE_ID': bytes(config.get(section, 'SOFTWARE_ID').ljust(40)[:40], 'utf-8'), - 'PACKAGE_ID': bytes(config.get(section, 'PACKAGE_ID').ljust(40)[:40], 'utf-8'), - 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'), - 'XLXMODULE': config.getint(section, 'XLXMODULE'), - 'OPTIONS': '', - 'USE_ACL': config.getboolean(section, 'USE_ACL'), - 'SUB_ACL': config.get(section, 'SUB_ACL'), - 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), - 'TG2_ACL': config.get(section, 'TGID_TS2_ACL') - }}) - CONFIG['SYSTEMS'][section].update({'XLXSTATS': { - 'CONNECTION': 'NO', # NO, RTPL_SENT, AUTHENTICATED, CONFIG-SENT, YES - 'CONNECTED': None, - 'PINGS_SENT': 0, - 'PINGS_ACKD': 0, - 'NUM_OUTSTANDING': 0, - 'PING_OUTSTANDING': False, - 'LAST_PING_TX_TIME': 0, - 'LAST_PING_ACK_TIME': 0, - }}) - elif config.get(section, 'MODE') == 'MASTER': CONFIG['SYSTEMS'].update({section: { 'MODE': config.get(section, 'MODE'), diff --git a/entrypoint b/entrypoint deleted file mode 100755 index 422f0f3..0000000 --- a/entrypoint +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -if [ -z "$GIT_REPO" ]; then - mkdir -p /var/tmp/config - cd /var/tmp/config - git clone https://${GIT_USER}:${GIT_PASSWORD}@${GIT_REPO} - - DIR=$(echo ${GIT_REPO}| sed s/.git$//g | sed s#^.*/##g) - - cp -a /var/tmp/config/${DIR}/*cfg /opt/hblink3/ - cp -a /var/tmp/config/${DIR}/*csv /opt/hblink3/ - cp -a /var/tmp/config/${DIR}/*json /opt/hblink3/ -else - cp -a /opt/config/*cfg /opt/hblink3/ - cp -a /opt/config/*csv /opt/hblink3/ - cp -a /opt/config/*json /opt/hblink3/ -fi -cd /opt/hblink3 -python /opt/hblink3/hblink.py -c hblink.cfg diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index a3b616c..cf56295 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -206,36 +206,4 @@ OPTIONS: USE_ACL: True SUB_ACL: DENY:1 TGID_TS1_ACL: PERMIT:ALL -TGID_TS2_ACL: PERMIT:ALL - -[XLX-1] -MODE: XLXPEER -ENABLED: True -LOOSE: True -EXPORT_AMBE: False -IP: -PORT: 54002 -MASTER_IP: 172.16.1.1 -MASTER_PORT: 62030 -PASSPHRASE: passw0rd -CALLSIGN: W1ABC -RADIO_ID: 312000 -RX_FREQ: 449000000 -TX_FREQ: 444000000 -TX_POWER: 25 -COLORCODE: 1 -SLOTS: 1 -LATITUDE: 38.0000 -LONGITUDE: -095.0000 -HEIGHT: 75 -LOCATION: Anywhere, USA -DESCRIPTION: This is a cool repeater -URL: www.w1abc.org -SOFTWARE_ID: 20170620 -PACKAGE_ID: MMDVM_HBlink -GROUP_HANGTIME: 5 -XLXMODULE: 4004 -USE_ACL: True -SUB_ACL: DENY:1 -TGID_TS1_ACL: PERMIT:ALL -TGID_TS2_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL \ No newline at end of file diff --git a/hblink.py b/hblink.py index 83b67ad..bbcff98 100755 --- a/hblink.py +++ b/hblink.py @@ -223,13 +223,6 @@ class HBSYSTEM(DatagramProtocol): self.datagramReceived = self.peer_datagramReceived self.dereg = self.peer_dereg - elif self._config['MODE'] == 'XLXPEER': - self._stats = self._config['XLXSTATS'] - self.send_system = self.send_master - self.maintenance_loop = self.peer_maintenance_loop - self.datagramReceived = self.peer_datagramReceived - self.dereg = self.peer_dereg - def startProtocol(self): # Set up periodic loop for tracking pings from peers. Run every 'PING_TIME' seconds self._system_maintenance = task.LoopingCall(self.maintenance_loop) @@ -289,29 +282,6 @@ class HBSYSTEM(DatagramProtocol): # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! # logger.debug('(%s) TX Packet to %s:%s -- %s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT'], ahex(_packet)) - def send_xlxmaster(self, radio, xlx, mastersock): - radio3 = int.from_bytes(radio, 'big').to_bytes(3, 'big') - radio4 = int.from_bytes(radio, 'big').to_bytes(4, 'big') - xlx3 = xlx.to_bytes(3, 'big') - streamid = randint(0,255).to_bytes(1, 'big')+randint(0,255).to_bytes(1, 'big')+randint(0,255).to_bytes(1, 'big')+randint(0,255).to_bytes(1, 'big') - # Wait for .5 secs for the XLX to log us in - for packetnr in range(5): - if packetnr < 3: - # First 3 packets, voice start, stream type e1 - strmtype = 225 - payload = bytearray.fromhex('4f2e00b501ae3a001c40a0c1cc7dff57d75df5d5065026f82880bd616f13f185890000') - else: - # Last 2 packets, voice end, stream type e2 - strmtype = 226 - payload = bytearray.fromhex('4f410061011e3a781c30a061ccbdff57d75df5d2534425c02fe0b1216713e885ba0000') - packetnr1 = packetnr.to_bytes(1, 'big') - strmtype1 = strmtype.to_bytes(1, 'big') - _packet = b''.join([DMRD, packetnr1, radio3, xlx3, radio4, strmtype1, streamid, payload]) - self.transport.write(_packet, mastersock) - # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! - #logger.debug('(%s) XLX Module Change Packet: %s', self._system, ahex(_packet)) - return - def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pass @@ -648,11 +618,7 @@ class HBSYSTEM(DatagramProtocol): self._stats['CONNECTION'] = 'YES' self._stats['CONNECTED'] = time() logger.info('(%s) Connection to Master Completed', self._system) - # If we are an XLX, send the XLX module request here. - if self._config['MODE'] == 'XLXPEER': - self.send_xlxmaster(self._config['RADIO_ID'], int(4000), self._config['MASTER_SOCKADDR']) - self.send_xlxmaster(self._config['RADIO_ID'], self._config['XLXMODULE'], self._config['MASTER_SOCKADDR']) - logger.info('(%s) Sending XLX Module request', self._system) + else: self._stats['CONNECTION'] = 'NO' logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system)