@ -336,7 +336,7 @@ def peer_single_same_tg_foreign_tx_blocks(
return True
def peer_hotspot_voice_slot_busy (
def peer_hotspot_voice_slot_busy _reason (
peer_id : bytes ,
voice_slot : int ,
stream_id : bytes ,
@ -350,8 +350,12 @@ def peer_hotspot_voice_slot_busy(
peers : dict [ Any , Any ] | None = None ,
peer : dict [ str , Any ] | None = None ,
sys_cfg : dict [ str , Any ] | None = None ,
) - > bool :
""" True when this hotspot must not receive another group stream on ``voice_slot``.
) - > str | None :
""" Reason this hotspot must not receive another group stream on ``voice_slot``, or None.
Same logic as ` ` peer_hotspot_voice_slot_busy ` ` ( which just checks ` ` is not
None ` ` ) - - kept as one function so the diagnostic reason can never drift
from the actual accept / reject decision .
Hard rules ( SINGLE = 0 and SINGLE = 1 ) :
@ -366,10 +370,10 @@ def peer_hotspot_voice_slot_busy(
if peer is None and peers is not None :
peer = peers . get ( peer_id ) or peers . get ( pk )
if _peer_transmit_hangtime_blocks ( hang_row , incoming_tgid_b , pkt_time , group_hangtime ) :
return True
return f " GROUP_HANGTIME: recent local transmit hanging on slot { voice_slot } "
incoming_tgid = int_id ( incoming_tgid_b )
active = ( peer_slots or { } ) . get ( int ( voice_slot ) )
if isinstance ( active , dict ) :
incoming_tgid = int_id ( incoming_tgid_b )
active_tgid = int ( active . get ( " tgid " , 0 ) or 0 )
active_stream = active . get ( " stream_id " )
active_time = float ( active . get ( " time " , 0 ) or 0 )
@ -378,10 +382,10 @@ def peer_hotspot_voice_slot_busy(
peer_slots . pop ( int ( voice_slot ) , None )
active = None
if isinstance ( active , dict ) and active . get ( " ingress " ) :
return True
return f " peer is transmitting (ingress) on slot { voice_slot } "
if isinstance ( active , dict ) and active . get ( " bridge_hold " ) and active_tgid and incoming_tgid != active_tgid :
if age < = group_hangtime :
return True
return f " bridge hold: TG { active_tgid } active on slot { voice_slot } "
peer_slots . pop ( int ( voice_slot ) , None )
active = None
if isinstance ( active , dict ) and stream_id and active_stream :
@ -391,24 +395,28 @@ def peer_hotspot_voice_slot_busy(
if age > = STREAM_TO :
peer_slots . pop ( int ( voice_slot ) , None )
else :
return True
return f " TG { incoming_tgid } already active on slot { voice_slot } with a different stream "
elif active_tgid and incoming_tgid != active_tgid :
return True
return f " slot { voice_slot } busy with different TG { active_tgid } "
else :
return True
return f " slot { voice_slot } busy with an unidentified active stream "
elif isinstance ( active , dict ) :
if active_tgid and active_tgid == incoming_tgid :
pass
else :
return True
return (
f " slot { voice_slot } already occupied by TG { active_tgid } "
if active_tgid
else f " slot { voice_slot } already occupied by another stream "
)
if isinstance ( peer , dict ) and peer_single_blocks_foreign_same_tg_downlink (
peer , pk , voice_slot , incoming_tgid_b , peer_slots , sys_cfg , now = pkt_time ,
) :
return True
return f " SINGLE mode: local UA lock on TG { incoming_tgid } blocks foreign downlink on slot { voice_slot } "
if isinstance ( peer , dict ) and peer_single_same_tg_foreign_tx_blocks (
peer , pk , incoming_tgid_b , stream_id , slot_st , sys_cfg , pkt_time = pkt_time ,
) :
return True
return f " SINGLE mode: peer is transmitting a different call on TG { incoming_tgid } "
if bytes_4 ( int_id ( slot_st . get ( " RX_PEER " , b " " ) ) ) == pk :
rx_active = (
slot_st . get ( " RX_TYPE " ) is not None
@ -416,12 +424,47 @@ def peer_hotspot_voice_slot_busy(
and ( pkt_time - float ( slot_st . get ( " RX_TIME " , 0 ) ) ) < STREAM_TO
)
if rx_active and stream_id != slot_st . get ( " RX_STREAM_ID " ) :
return True
return f " slot { voice_slot } STATUS RX owner with a different active stream "
if _peer_status_rx_hangtime_blocks (
peer_id , slot_st , incoming_tgid_b , pkt_time , group_hangtime ,
) :
return True
return False
return f " GROUP_HANGTIME: recent STATUS RX hangtime on slot { voice_slot } "
return None
def peer_hotspot_voice_slot_busy (
peer_id : bytes ,
voice_slot : int ,
stream_id : bytes ,
incoming_tgid_b : bytes ,
slot_st : dict [ str , Any ] ,
peer_slots : PeerVoiceSlotMap | None ,
hang_row : tuple [ int , float ] | None ,
pkt_time : float ,
group_hangtime : float ,
* ,
peers : dict [ Any , Any ] | None = None ,
peer : dict [ str , Any ] | None = None ,
sys_cfg : dict [ str , Any ] | None = None ,
) - > bool :
""" True when this hotspot must not receive another group stream on ``voice_slot``. """
return (
peer_hotspot_voice_slot_busy_reason (
peer_id ,
voice_slot ,
stream_id ,
incoming_tgid_b ,
slot_st ,
peer_slots ,
hang_row ,
pkt_time ,
group_hangtime ,
peers = peers ,
peer = peer ,
sys_cfg = sys_cfg ,
)
is not None
)
def master_per_peer_slot_contention (
@ -602,7 +645,7 @@ def obp_clear_deferred_bridge_tx_leg(
obp_clear_flat_bridge_tx ( slot_st )
def hbp_slot_blocks_group_voice_for_peer (
def hbp_slot_blocks_group_voice_for_peer _reason (
slot_st : dict [ str , Any ] ,
peer_id : bytes ,
incoming_tgid_b : bytes ,
@ -616,24 +659,21 @@ def hbp_slot_blocks_group_voice_for_peer(
peer_hang_row : tuple [ int , float ] | None = None ,
voice_slot : int | None = None ,
sys_cfg : dict [ str , Any ] | None = None ,
) - > bool :
""" Slot contention scoped to one hotspot when ``per_peer`` (inject-only multi-HS).
` ` STATUS [ slot ] ` ` is shared at the MASTER , but each connected hotspot has an
independent RF timeslot . Another peer ' s active QSO must not block this peer.
) - > str | None :
""" Reason for ``hbp_slot_blocks_group_voice_for_peer`` ' s block, or None.
Inject- only OBP → HBP defers global contention and stamps bridge ` ` TX_ * ` ` on the
shared slot row before ` ` send_peer ` ` . Same - stream exemption must not treat that
bridge TX stamp as the hotspot ' s own leg while the peer is still on the air (RX) .
Same logic as ` ` hbp_slot_blocks_group_voice_for_peer ` ` ( which just checks
` ` is not None ` ` ) - - kept as one function so the diagnostic reason can
never drift from the actual accept / reject decision .
"""
if per_peer :
if voice_slot is None :
return Fals e
return Non e
peer = None
if peers is not None :
pk = bytes_4 ( int_id ( peer_id ) )
peer = peers . get ( peer_id ) or peers . get ( pk )
return peer_hotspot_voice_slot_busy (
return peer_hotspot_voice_slot_busy _reason (
peer_id ,
int ( voice_slot ) ,
stream_id ,
@ -647,8 +687,53 @@ def hbp_slot_blocks_group_voice_for_peer(
peer = peer if isinstance ( peer , dict ) else None ,
sys_cfg = sys_cfg ,
)
return hbp_slot_blocks_group_voice (
if hbp_slot_blocks_group_voice (
slot_st , incoming_tgid_b , stream_id , pkt_time , group_hangtime ,
) :
return " global STATUS slot contention "
return None
def hbp_slot_blocks_group_voice_for_peer (
slot_st : dict [ str , Any ] ,
peer_id : bytes ,
incoming_tgid_b : bytes ,
stream_id : bytes ,
pkt_time : float ,
group_hangtime : float ,
* ,
per_peer : bool ,
peers : dict [ Any , Any ] | None = None ,
peer_slots : PeerVoiceSlotMap | None = None ,
peer_hang_row : tuple [ int , float ] | None = None ,
voice_slot : int | None = None ,
sys_cfg : dict [ str , Any ] | None = None ,
) - > bool :
""" Slot contention scoped to one hotspot when ``per_peer`` (inject-only multi-HS).
` ` STATUS [ slot ] ` ` is shared at the MASTER , but each connected hotspot has an
independent RF timeslot . Another peer ' s active QSO must not block this peer.
Inject - only OBP → HBP defers global contention and stamps bridge ` ` TX_ * ` ` on the
shared slot row before ` ` send_peer ` ` . Same - stream exemption must not treat that
bridge TX stamp as the hotspot ' s own leg while the peer is still on the air (RX).
"""
return (
hbp_slot_blocks_group_voice_for_peer_reason (
slot_st ,
peer_id ,
incoming_tgid_b ,
stream_id ,
pkt_time ,
group_hangtime ,
per_peer = per_peer ,
peers = peers ,
peer_slots = peer_slots ,
peer_hang_row = peer_hang_row ,
voice_slot = voice_slot ,
sys_cfg = sys_cfg ,
)
is not None
)
@ -1793,6 +1878,17 @@ def peer_downlink_voice_slot(
static = peer_options_static_tg_slot ( peer , tgid )
if static is not None :
return static
from adn_server . application . report . payloads import parse_peer_options_static
ts1 , ts2 = parse_peer_options_static ( peer . get ( " OPTIONS " ) )
tg = str ( tgid )
if tg in ts1 and tg in ts2 :
# Static on both slots (peer_options_static_tg_slot returns None
# because that's ambiguous *as a single answer*, not because the TG
# is unresolved) -- an in-progress SINGLE=1 exclusive lock or SINGLE=0
# UA_MULTI entry from this peer's own current transmission must not
# override a config that already, unambiguously, permits wire_slot.
return int ( wire_slot )
if sys_cfg is not None and peer_id is not None :
tgid_i = int ( tgid )
pk = bytes_4 ( int_id ( peer_id ) )
@ -1806,6 +1902,10 @@ def peer_downlink_voice_slot(
if isinstance ( store , dict ) :
per_peer = store . get ( pk )
if isinstance ( per_peer , dict ) :
wire_slot_i = int ( wire_slot )
wire_slot_set = per_peer . get ( wire_slot_i )
if isinstance ( wire_slot_set , set ) and tgid_i in wire_slot_set :
return wire_slot_i
for voice_slot in ( 1 , 2 ) :
slot_set = per_peer . get ( voice_slot )
if isinstance ( slot_set , set ) and tgid_i in slot_set :