2016-07-31 10:42:11 -04:00
#!/usr/bin/env python
#
# This work is licensed under the Creative Attribution-NonCommercial-ShareAlike
# 3.0 Unported License.To view a copy of this license, visit
# http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to
# Creative Commons, 444 Castro Street, Suite 900, Mountain View,
# California, 94041, USA.
from __future__ import print_function
# Python modules we need
import sys
2016-08-26 23:43:51 -04:00
from binascii import b2a_hex as h
2016-10-10 21:49:51 -04:00
from bitarray import bitarray
2016-11-01 20:49:13 -04:00
from time import time
2016-07-31 10:42:11 -04:00
# Debugging functions
from pprint import pprint
# Twisted is pretty important, so I keep it separate
from twisted . internet . protocol import DatagramProtocol
from twisted . internet import reactor
from twisted . internet import task
# Things we import from the main hblink module
2016-08-25 21:44:15 -04:00
from hblink import CONFIG , HBMASTER , HBCLIENT , logger , systems , hex_str_3 , int_id
2016-10-23 12:07:16 -04:00
import dec_dmr
2016-11-15 21:35:08 -05:00
import bptc
2016-10-24 17:25:14 -04:00
import constants as const
2016-07-31 10:42:11 -04:00
# Import Bridging rules
# Note: A stanza *must* exist for any MASTER or CLIENT configured in the main
# configuration file and listed as "active". It can be empty,
# but it has to exist.
try :
from hb_routing_rules import RULES as RULES_FILE
logger . info ( ' Routing rules file found and rules imported ' )
except ImportError :
sys . exit ( ' Routing rules file not found or invalid ' )
# Convert integer GROUP ID numbers from the config into hex strings
# we need to send in the actual data packets.
2016-08-25 21:44:15 -04:00
for _system in RULES_FILE :
for _rule in RULES_FILE [ _system ] [ ' GROUP_VOICE ' ] :
2016-07-31 10:42:11 -04:00
_rule [ ' SRC_GROUP ' ] = hex_str_3 ( _rule [ ' SRC_GROUP ' ] )
_rule [ ' DST_GROUP ' ] = hex_str_3 ( _rule [ ' DST_GROUP ' ] )
_rule [ ' SRC_TS ' ] = _rule [ ' SRC_TS ' ]
_rule [ ' DST_TS ' ] = _rule [ ' DST_TS ' ]
for i , e in enumerate ( _rule [ ' ON ' ] ) :
_rule [ ' ON ' ] [ i ] = hex_str_3 ( _rule [ ' ON ' ] [ i ] )
for i , e in enumerate ( _rule [ ' OFF ' ] ) :
_rule [ ' OFF ' ] [ i ] = hex_str_3 ( _rule [ ' OFF ' ] [ i ] )
2016-08-25 21:44:15 -04:00
if _system not in CONFIG [ ' SYSTEMS ' ] :
sys . exit ( ' ERROR: Routing rules found for system not configured main configuration ' )
for _system in CONFIG [ ' SYSTEMS ' ] :
if _system not in RULES_FILE :
sys . exit ( ' ERROR: Routing rules not found for all systems configured ' )
2016-07-31 10:42:11 -04:00
RULES = RULES_FILE
# TEMPORARY DEBUGGING LINE -- TO BE REMOVED LATER
2016-08-01 21:24:37 -04:00
#pprint(RULES)
2016-07-31 10:42:11 -04:00
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = ' Cortney T. Buffington, N0MJS '
__copyright__ = ' Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group '
__credits__ = ' Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT '
__license__ = ' Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported '
__maintainer__ = ' Cort Buffington, N0MJS '
__email__ = ' n0mjs@me.com '
__status__ = ' pre-alpha '
class routerMASTER ( HBMASTER ) :
2016-10-10 21:49:51 -04:00
def __init__ ( self , * args , * * kwargs ) :
HBMASTER . __init__ ( self , * args , * * kwargs )
2016-11-01 20:49:13 -04:00
2016-11-08 17:16:22 -05:00
# 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_STREAM_ID ' : ' \x00 ' ,
' TX_STREAM_ID ' : ' \x00 ' ,
2016-11-15 21:35:08 -05:00
' RX_TGID ' : ' \x00 \x00 \x00 ' ,
' TX_TGID ' : ' \x00 \x00 \x00 ' ,
2016-11-08 17:16:22 -05:00
' RX_TIME ' : time ( ) ,
' TX_TIME ' : time ( ) ,
' RX_TYPE ' : const . HBPF_SLT_VTERM ,
' RX_LC ' : ' \x00 ' ,
' TX_LC ' : ' \x00 ' ,
' TX_EMB_LC ' : {
2 : ' \x00 ' ,
3 : ' \x00 ' ,
4 : ' \x00 ' ,
5 : ' \x00 ' ,
}
} ,
2 : {
' RX_STREAM_ID ' : ' \x00 ' ,
' TX_STREAM_ID ' : ' \x00 ' ,
2016-11-15 21:35:08 -05:00
' RX_TGID ' : ' \x00 \x00 \x00 ' ,
' TX_TGID ' : ' \x00 \x00 \x00 ' ,
2016-11-08 17:16:22 -05:00
' RX_TIME ' : time ( ) ,
' TX_TIME ' : time ( ) ,
' RX_TYPE ' : const . HBPF_SLT_VTERM ,
' RX_LC ' : ' \x00 ' ,
' TX_LC ' : ' \x00 ' ,
' TX_EMB_LC ' : {
2 : ' \x00 ' ,
3 : ' \x00 ' ,
4 : ' \x00 ' ,
5 : ' \x00 ' ,
}
}
}
2016-08-26 22:33:35 -04:00
2016-09-22 10:12:38 -04:00
def dmrd_received ( self , _radio_id , _rf_src , _dst_id , _seq , _slot , _call_type , _frame_type , _dtype_vseq , _stream_id , _data ) :
2016-11-01 20:49:13 -04:00
pkt_time = time ( )
dmrpkt = _data [ 20 : 54 ]
2016-11-01 21:54:43 -04:00
_bits = int_id ( _data [ 15 ] )
if _call_type == ' group ' :
2016-11-01 20:49:13 -04:00
2016-11-01 21:54:43 -04:00
# Is this a new call stream?
2016-11-08 17:16:22 -05:00
if ( _stream_id != self . STATUS [ _slot ] [ ' RX_STREAM_ID ' ] ) :
if ( ( self . STATUS [ _slot ] [ ' RX_TYPE ' ] != const . HBPF_SLT_VTERM ) or ( pkt_time < self . STATUS [ _slot ] [ ' RX_TIME ' ] + const . STREAM_TO ) ) :
2016-11-14 08:27:44 -05:00
logger . warning ( ' ( %s ) Packet received with STREAM ID: %s <FROM> SUB: %s REPEATER: %s <TO> TGID %s , SLOT %s collided with existing call ' , self . _master , int_id ( _stream_id ) , int_id ( _rf_src ) , int_id ( _radio_id ) , int_id ( _dst_id ) , _slot )
2016-11-01 21:54:43 -04:00
return
2016-11-08 15:56:11 -05:00
2016-11-08 17:16:22 -05:00
# This is a new call stream
2016-11-14 08:27:44 -05:00
logger . info ( ' ( %s ) Call stream START with STREAM ID: %s <FROM> SUB: %s REPEATER: %s <TO> TGID %s , SLOT %s ' , self . _master , int_id ( _stream_id ) , int_id ( _rf_src ) , int_id ( _radio_id ) , int_id ( _dst_id ) , _slot )
2016-10-23 12:07:16 -04:00
2016-11-01 21:54:43 -04:00
# If we can, use the LC from the voice header as to keep all options intact
if _frame_type == const . HBPF_DATA_SYNC and _dtype_vseq == const . HBPF_SLT_VHEAD :
decoded = dec_dmr . voice_head_term ( dmrpkt )
2016-11-08 17:16:22 -05:00
self . STATUS [ _slot ] [ ' RX_LC ' ] = decoded [ ' LC ' ]
2016-11-01 21:54:43 -04:00
# If we don't have a voice header then don't wait to decode it from the Embedded LC
2016-11-14 08:27:44 -05:00
# just make a new one from the HBP header. This is good enough, and it saves lots of time
2016-11-01 21:54:43 -04:00
else :
2016-11-08 17:16:22 -05:00
self . STATUS [ _slot ] [ ' RX_LC ' ] = const . LC_OPT + _dst_id + _rf_src
2016-11-01 21:54:43 -04:00
2016-10-10 21:11:50 -04:00
2016-11-08 17:16:22 -05:00
2016-08-25 21:44:15 -04:00
for rule in RULES [ self . _master ] [ ' GROUP_VOICE ' ] :
2016-08-01 22:31:28 -04:00
_target = rule [ ' DST_NET ' ]
2016-11-15 15:50:09 -05:00
_target_status = systems [ _target ] . STATUS
2016-08-26 22:33:35 -04:00
if ( rule [ ' SRC_GROUP ' ] == _dst_id and rule [ ' SRC_TS ' ] == _slot and rule [ ' ACTIVE ' ] == True ) :
2016-11-15 15:50:09 -05:00
# BEGIN CONTENTION HANDLING
#
# The rules for each of the 4 "ifs" below are listed here for readability. The Frame To Send is:
2016-11-15 21:35:08 -05:00
# From a different group than last TX to the target HBP system, but it has been less than Group Hangtime
# From the same group as the last TX to the target HBP system, but stream ID is different, and it is less than stream timout
2016-11-15 15:50:09 -05:00
# The "continue" at the end of each means the next iteration of the for loop that tests for matching rules
#
2016-11-15 21:35:08 -05:00
if ( ( rule [ ' DST_GROUP ' ] != _target_status [ _slot ] [ ' TX_TGID ' ] ) and ( ( pkt_time - self . STATUS [ _slot ] [ ' RX_TIME ' ] ) < RULES [ self . _master ] [ ' GROUP_HANGTIME ' ] ) ) :
if const . HBPF_DATA_SYNC and _dtype_vseq == const . HBPF_SLT_VHEAD :
logger . info ( ' ( %s ) Call not routed, target active or in group hangtime: HBP system %s , TS %s , TGID %s ' , self . _master , _target , _slot , int_id ( rule [ ' DST_GROUP ' ] ) )
2016-11-15 15:50:09 -05:00
continue
2016-11-15 21:35:08 -05:00
if ( rule [ ' DST_GROUP ' ] == self . STATUS [ _slot ] [ ' TX_TGID ' ] ) and ( _stream_id != self . STATUS [ _slot ] [ ' TX_STREAM_ID ' ] ) and ( ( pkt_time - _status [ _slot ] [ ' TX_TIME ' ] ) < const . STREAM_TO ) :
if const . HBPF_DATA_SYNC and _dtype_vseq == const . HBPF_SLT_VHEAD :
logger . info ( ' ( %s ) Call not routed, call bridge in progress from %s , target: HBP system %s , TS %s , TGID %s ' , self . _master , int_id ( _src_sub ) , _target , _slot , int_id ( rule [ ' DST_GROUP ' ] ) )
2016-11-15 15:50:09 -05:00
continue
# Set values for the contention handler to test next time there is a frame to forward
2016-11-15 21:35:08 -05:00
_target_status [ _slot ] [ ' TX_TIME ' ] = pkt_time
2016-11-15 15:50:09 -05:00
2016-11-15 21:35:08 -05:00
if _stream_id != self . STATUS [ _slot ] [ ' RX_STREAM_ID ' ] :
_target_status [ _slot ] [ ' TX_TGID ' ] = rule [ ' DST_GROUP ' ]
_target_status [ _slot ] [ ' TX_STREAM_ID ' ] = _stream_id
_target_status [ _slot ] [ ' TX_LC ' ] = bptc . encode_header_lc ( self . STATUS [ _slot ] [ ' RX_LC ' ] [ 0 : 3 ] + rule [ ' DST_GROUP ' ] + _rf_src )
print ( ' new stream id, calcuate/store stuff ' , h ( bptc . decode_full_lc ( _target_status [ _slot ] [ ' TX_LC ' ] ) . tobytes ( ) ) )
2016-11-15 15:50:09 -05:00
# Handle any necessary re-writes for the destination
2016-08-26 23:43:51 -04:00
if rule [ ' SRC_TS ' ] != rule [ ' DST_TS ' ] :
2016-08-26 22:33:35 -04:00
_tmp_bits = _bits ^ 1 << 7
2016-08-26 22:31:20 -04:00
else :
2016-08-26 22:33:35 -04:00
_tmp_bits = _bits
2016-11-15 15:50:09 -05:00
# MUST TEST FOR NEW STREAM AND IF SO, RE-WRITE THE LC FOR THE TARGET
# MUST RE-WRITE DESTINATION TGID IF DIFFERENT
# Assemble transmit packet
2016-09-03 06:05:54 -04:00
_tmp_data = _data [ : 8 ] + rule [ ' DST_GROUP ' ] + _data [ 11 : 15 ] + chr ( _tmp_bits ) + _data [ 16 : ]
2016-11-15 15:50:09 -05:00
# Transmit the packet to the destination system
2016-08-26 22:33:35 -04:00
systems [ _target ] . send_system ( _tmp_data )
2016-08-26 23:43:51 -04:00
logger . debug ( ' ( %s ) Packet routed to %s system: %s ' , self . _master , CONFIG [ ' SYSTEMS ' ] [ _target ] [ ' MODE ' ] , _target )
2016-11-08 17:16:22 -05:00
2016-11-01 21:54:43 -04:00
2016-11-15 15:50:09 -05:00
# Final actions - Is this a voice terminator?
2016-11-08 17:16:22 -05:00
if ( _frame_type == const . HBPF_DATA_SYNC ) and ( _dtype_vseq == const . HBPF_SLT_VTERM ) and ( self . STATUS [ _slot ] [ ' RX_TYPE ' ] != const . HBPF_SLT_VTERM ) :
self . STATUS [ _slot ] [ ' LC ' ] = ' '
2016-11-14 08:27:44 -05:00
logger . info ( ' ( %s ) Call stream END with STREAM ID: %s <FROM> SUB: %s REPEATER: %s <TO> TGID %s , SLOT %s ' , self . _master , int_id ( _stream_id ) , int_id ( _rf_src ) , int_id ( _radio_id ) , int_id ( _dst_id ) , _slot )
2016-11-15 15:50:09 -05:00
# Mark status variables for use later
2016-11-15 21:35:08 -05:00
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
2016-11-01 21:54:43 -04:00
2016-07-31 10:42:11 -04:00
class routerCLIENT ( HBCLIENT ) :
2016-08-26 22:33:35 -04:00
2016-10-10 21:49:51 -04:00
def __init__ ( self , * args , * * kwargs ) :
HBCLIENT . __init__ ( self , * args , * * kwargs )
2016-11-15 21:35:08 -05:00
# 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_STREAM_ID ' : ' \x00 ' ,
' TX_STREAM_ID ' : ' \x00 ' ,
' RX_TGID ' : ' \x00 ' ,
' TX_TGID ' : ' \x00 ' ,
' RX_TIME ' : time ( ) ,
' TX_TIME ' : time ( ) ,
' RX_TYPE ' : const . HBPF_SLT_VTERM ,
' RX_LC ' : ' \x00 ' ,
' TX_LC ' : ' \x00 ' ,
' TX_EMB_LC ' : {
2 : ' \x00 ' ,
3 : ' \x00 ' ,
4 : ' \x00 ' ,
5 : ' \x00 ' ,
}
} ,
2 : {
' RX_STREAM_ID ' : ' \x00 ' ,
' TX_STREAM_ID ' : ' \x00 ' ,
' RX_TGID ' : ' \x00 ' ,
' TX_TGID ' : ' \x00 ' ,
' RX_TIME ' : time ( ) ,
' TX_TIME ' : time ( ) ,
' RX_TYPE ' : const . HBPF_SLT_VTERM ,
' RX_LC ' : ' \x00 ' ,
' TX_LC ' : ' \x00 ' ,
' TX_EMB_LC ' : {
2 : ' \x00 ' ,
3 : ' \x00 ' ,
4 : ' \x00 ' ,
5 : ' \x00 ' ,
}
}
}
2016-10-10 21:49:51 -04:00
2016-09-22 10:12:38 -04:00
def dmrd_received ( self , _radio_id , _rf_src , _dst_id , _seq , _slot , _call_type , _frame_type , _dtype_vseq , _stream_id , _data ) :
2016-11-14 08:27:44 -05:00
return
2016-07-31 10:42:11 -04:00
#************************************************
# MAIN PROGRAM LOOP STARTS HERE
#************************************************
if __name__ == ' __main__ ' :
logger . info ( ' HBlink \' hb_router.py \' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING... ' )
2016-08-25 21:44:15 -04:00
# HBlink instance creation
for system in CONFIG [ ' SYSTEMS ' ] :
if CONFIG [ ' SYSTEMS ' ] [ system ] [ ' ENABLED ' ] :
if CONFIG [ ' SYSTEMS ' ] [ system ] [ ' MODE ' ] == ' MASTER ' :
2016-08-25 22:27:10 -04:00
systems [ system ] = routerMASTER ( system )
2016-08-25 21:44:15 -04:00
elif CONFIG [ ' SYSTEMS ' ] [ system ] [ ' MODE ' ] == ' CLIENT ' :
2016-08-25 22:27:10 -04:00
systems [ system ] = routerCLIENT ( system )
2016-08-25 21:44:15 -04:00
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 ] )
2016-07-31 10:42:11 -04:00
reactor . run ( )