From fdf1b33ee86ea00a6e76b6e5b8f8f07ca0433d31 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Thu, 25 Jun 2026 07:13:59 -0500 Subject: [PATCH] bridge.py: log slot collisions once per stream, not per frame The source-side "collided with existing call" warning fired on every dropped frame, flooding the log when streams contend for a slot (e.g. several peers on one master keying the same talkgroup). Gate it on a per-slot RX_COLLISION_SID so each contending stream is reported once, matching the once-per-stream behavior of the target-side contention log. Routing/emissions are unchanged; this is logging volume only. The dashboard's stale-stream reaper stays as-is for defense-in-depth (non-local dashboard, hblink failure, etc.). Co-Authored-By: Claude Opus 4.8 --- bridge.py | 14 ++++++++++++-- tests/test_end_emission.py | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/bridge.py b/bridge.py index a1ab516..57aa2be 100755 --- a/bridge.py +++ b/bridge.py @@ -522,6 +522,9 @@ class routerHBP(HBSYSTEM): # sentinel drops the END of any stream whose last burst was vseq 2. 'RX_TERMINATED': True, 'TX_TERMINATED': True, + # Last stream-id we logged a slot collision for, so a contending + # stream is reported once -- not once per colliding frame. + 'RX_COLLISION_SID': b'\x00', 'RX_CT': 'GROUP VOICE', 'RX_LC': b'\x00', 'TX_H_LC': b'\x00', @@ -555,6 +558,9 @@ class routerHBP(HBSYSTEM): # sentinel drops the END of any stream whose last burst was vseq 2. 'RX_TERMINATED': True, 'TX_TERMINATED': True, + # Last stream-id we logged a slot collision for, so a contending + # stream is reported once -- not once per colliding frame. + 'RX_COLLISION_SID': b'\x00', 'RX_CT': 'GROUP VOICE', 'RX_LC': b'\x00', 'TX_H_LC': b'\x00', @@ -598,7 +604,9 @@ class routerHBP(HBSYSTEM): # Is this a new call stream? if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): if (not self.STATUS[_slot]['RX_TERMINATED']) 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) + if self.STATUS[_slot]['RX_COLLISION_SID'] != _stream_id: + self.STATUS[_slot]['RX_COLLISION_SID'] = _stream_id + 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 # A new stream is taking the slot: end any prior un-terminated stream first @@ -793,7 +801,9 @@ class routerHBP(HBSYSTEM): # Collision in progress, bail out! if (not self.STATUS[_slot]['RX_TERMINATED']) 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) + if self.STATUS[_slot]['RX_COLLISION_SID'] != _stream_id: + self.STATUS[_slot]['RX_COLLISION_SID'] = _stream_id + 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 # A new stream is taking the slot: end any prior un-terminated stream first diff --git a/tests/test_end_emission.py b/tests/test_end_emission.py index 863df97..8d923ae 100644 --- a/tests/test_end_emission.py +++ b/tests/test_end_emission.py @@ -122,5 +122,23 @@ class TestGroupEnd(unittest.TestCase): self.assertEqual(len(_ends(rpt)), 1) # exactly one, not two +class TestCollisionLogging(unittest.TestCase): + + def test_collision_logged_once_per_stream(self): + """A contending stream is reported once, not once per colliding frame.""" + w, _ = _world() + owner = b'\x00\x00\x00\x10' + w.feed_group_header('MASTER-1', stream_id=owner, **_KW) + w.feed_group_burst('MASTER-1', 1, stream_id=owner, **_KW) + collider = dict(_KW, rf_src=bytes_3(312999)) # different subscriber + cid = b'\x00\x00\x00\x11' + with self.assertLogs('bridge', level='WARNING') as cm: + w.feed_group_header('MASTER-1', stream_id=cid, **collider) + w.feed_group_burst('MASTER-1', 1, stream_id=cid, **collider) + w.feed_group_burst('MASTER-1', 2, stream_id=cid, **collider) + hits = [m for m in cm.output if 'collided with existing call' in m] + self.assertEqual(len(hits), 1) + + if __name__ == '__main__': unittest.main()