2018-12-26 02:10:06 +00:00
#! /usr/bin/env python
2013-12-10 00:47:21 +00:00
2018-03-15 18:43:57 +00:00
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Max H. Parke KA1RBI
2013-12-10 00:47:21 +00:00
#
# This file is part of OP25
#
# OP25 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, or (at your option)
# any later version.
#
# OP25 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 OP25; see the file COPYING. If not, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
# 02110-1301, USA.
#
2014-12-20 00:16:15 +00:00
import sys
2021-02-14 16:56:45 +00:00
import os
2013-12-10 00:47:21 +00:00
import time
2014-12-21 13:59:03 +00:00
import collections
2017-04-25 23:59:35 +00:00
import json
2014-12-20 00:16:15 +00:00
sys . path . append ( ' tdma ' )
import lfsr
2021-07-26 00:46:14 +00:00
from tsvfile import make_config , load_tsv , id_registry , read_tags_file
2021-02-14 16:56:45 +00:00
from create_image import create_image
2013-12-10 00:47:21 +00:00
2021-07-26 00:46:14 +00:00
FILTERED_CC_EVENT = ' mot_grg_add_cmd grp_v_ch_grant_updt grp_v_ch_grant_updt_exp ' . split ( )
2013-12-10 00:47:21 +00:00
def crc16 ( dat , len ) : # slow version
2014-12-21 13:59:03 +00:00
poly = ( 1 << 12 ) + ( 1 << 5 ) + ( 1 << 0 )
crc = 0
for i in range ( len ) :
bits = ( dat >> ( ( ( len - 1 ) - i ) * 8 ) ) & 0xff
for j in range ( 8 ) :
bit = ( bits >> ( 7 - j ) ) & 1
crc = ( ( crc << 1 ) | bit ) & 0x1ffff
if crc & 0x10000 :
crc = ( crc & 0xffff ) ^ poly
crc = crc ^ 0xffff
return crc
2013-12-10 00:47:21 +00:00
2020-02-24 01:03:43 +00:00
def get_ordinals ( s ) :
t = 0
if type ( s ) is not str and isinstance ( s , bytes ) :
for c in s :
t = ( t << 8 ) + c
else :
for c in s :
t = ( t << 8 ) + ord ( c )
return t
2013-12-10 00:47:21 +00:00
class trunked_system ( object ) :
2021-07-26 00:46:14 +00:00
def __init__ ( self , debug = 0 , config = None , send_event = None , nac = None ) :
2014-12-21 13:59:03 +00:00
self . debug = debug
self . freq_table = { }
self . stats = { }
self . stats [ ' tsbks ' ] = 0
self . stats [ ' crc ' ] = 0
self . tsbk_cache = { }
self . secondary = { }
self . adjacent = { }
2018-01-24 03:23:45 +00:00
self . adjacent_data = { }
2014-12-21 13:59:03 +00:00
self . rfss_syid = 0
self . rfss_rfid = 0
self . rfss_stid = 0
self . rfss_chan = 0
self . rfss_txchan = 0
self . ns_syid = - 1
self . ns_wacn = - 1
self . ns_chan = 0
self . voice_frequencies = { }
self . blacklist = { }
self . whitelist = None
2021-07-26 00:46:14 +00:00
self . tgid_map = None
self . unit_id_map = None
2014-12-21 13:59:03 +00:00
self . offset = 0
self . sysname = 0
2021-07-26 00:46:14 +00:00
self . tgid_tags_tsv = " "
self . unit_id_tags_tsv = " "
2014-12-21 13:59:03 +00:00
self . trunk_cc = 0
2018-12-26 02:10:06 +00:00
self . last_trunk_cc = 0
2014-12-21 13:59:03 +00:00
self . cc_list = [ ]
self . cc_list_index = 0
2014-12-20 00:16:15 +00:00
self . CC_HUNT_TIME = 5.0
2014-12-21 13:59:03 +00:00
self . center_frequency = 0
self . last_tsbk = 0
2014-12-20 00:16:15 +00:00
self . cc_timeouts = 0
2021-07-26 00:46:14 +00:00
self . next_hunt_cc = time . time ( ) + 8.0
self . last_voice_time = 0.0
2014-12-20 00:16:15 +00:00
2014-12-21 13:59:03 +00:00
self . talkgroups = { }
2021-07-26 00:46:14 +00:00
self . frequency_table = { }
self . CALL_TIMEOUT = 0.7 # call expiration time (sec.)
self . CHECK_INTERVAL = 0.1 # freq tracking check interval
self . next_frequency_tracking_expire = 0
2013-12-10 00:47:21 +00:00
if config :
self . blacklist = config [ ' blacklist ' ]
self . whitelist = config [ ' whitelist ' ]
self . tgid_map = config [ ' tgid_map ' ]
2021-07-26 00:46:14 +00:00
self . unit_id_map = config [ ' unit_id_map ' ]
2013-12-10 00:47:21 +00:00
self . offset = config [ ' offset ' ]
self . sysname = config [ ' sysname ' ]
self . trunk_cc = config [ ' cclist ' ] [ 0 ] # TODO: scan thru list
2014-12-20 00:16:15 +00:00
self . cc_list = config [ ' cclist ' ]
2014-02-07 02:54:59 +00:00
self . center_frequency = config [ ' center_frequency ' ]
2014-12-20 00:16:15 +00:00
self . modulation = config [ ' modulation ' ]
2013-12-10 00:47:21 +00:00
2018-12-26 02:10:06 +00:00
self . current_srcaddr = 0
self . current_grpaddr = 0 # from P25 LCW
self . current_alg = " "
self . current_algid = 128
self . current_keyid = 0
2021-07-26 00:46:14 +00:00
self . send_event = send_event
self . harris_sgs = { }
self . nac = nac
2018-12-26 02:10:06 +00:00
2021-07-26 00:46:14 +00:00
def to_dict ( self ) :
2017-04-25 23:59:35 +00:00
d = { }
d [ ' syid ' ] = self . rfss_syid
2018-12-26 02:10:06 +00:00
d [ ' sysname ' ] = self . sysname
2017-04-25 23:59:35 +00:00
d [ ' rfid ' ] = self . rfss_rfid
d [ ' stid ' ] = self . rfss_stid
d [ ' sysid ' ] = self . ns_syid
d [ ' rxchan ' ] = self . rfss_chan
d [ ' txchan ' ] = self . rfss_txchan
d [ ' wacn ' ] = self . ns_wacn
2020-02-24 01:03:43 +00:00
d [ ' secondary ' ] = [ k for k in self . secondary . keys ( ) ]
2017-04-25 23:59:35 +00:00
d [ ' tsbks ' ] = self . stats [ ' tsbks ' ]
d [ ' frequencies ' ] = { }
2018-01-24 03:23:45 +00:00
d [ ' frequency_data ' ] = { }
2017-04-25 23:59:35 +00:00
d [ ' last_tsbk ' ] = self . last_tsbk
2018-12-26 02:10:06 +00:00
d [ ' srcaddr ' ] = self . current_srcaddr
d [ ' grpaddr ' ] = self . current_grpaddr
d [ ' algid ' ] = self . current_algid
d [ ' alg ' ] = self . current_alg
d [ ' keyid ' ] = self . current_keyid
2017-04-25 23:59:35 +00:00
t = time . time ( )
for f in self . voice_frequencies . keys ( ) :
tgs = ' %s %s ' % ( self . voice_frequencies [ f ] [ ' tgid ' ] [ 0 ] , self . voice_frequencies [ f ] [ ' tgid ' ] [ 1 ] )
d [ ' frequencies ' ] [ f ] = ' voice frequency %f tgid(s) %s %4.1f s ago count %d ' % ( f / 1000000.0 , tgs , t - self . voice_frequencies [ f ] [ ' time ' ] , self . voice_frequencies [ f ] [ ' counter ' ] )
2018-01-24 03:23:45 +00:00
d [ ' frequency_data ' ] [ f ] = { ' tgids ' : self . voice_frequencies [ f ] [ ' tgid ' ] , ' last_activity ' : ' %7.1f ' % ( t - self . voice_frequencies [ f ] [ ' time ' ] ) , ' counter ' : self . voice_frequencies [ f ] [ ' counter ' ] }
2021-07-26 00:46:14 +00:00
for s in ' srcaddr tg_tag tg_color srcaddr_tag srcaddr_color ' . split ( ) :
d [ ' frequency_data ' ] [ f ] [ s ] = self . voice_frequencies [ f ] [ s ]
2020-02-24 01:03:43 +00:00
d [ ' adjacent_data ' ] = self . adjacent_data
2021-07-26 00:46:14 +00:00
d [ ' talkgroup_data ' ] = self . talkgroups
self . frequency_tracking_expire ( always = True )
d [ ' frequency_tracking ' ] = self . frequency_table
d [ ' harris_supergroups ' ] = self . harris_sgs
return d
def to_json ( self ) :
return json . dumps ( self . to_dict ( ) )
2017-04-25 23:59:35 +00:00
2013-12-10 00:47:21 +00:00
def to_string ( self ) :
s = [ ]
s . append ( ' rf: syid %x rfid %d stid %d frequency %f uplink %f ' % ( self . rfss_syid , self . rfss_rfid , self . rfss_stid , float ( self . rfss_chan ) / 1000000.0 , float ( self . rfss_txchan ) / 1000000.0 ) )
s . append ( ' net: syid %x wacn %x frequency %f ' % ( self . ns_syid , self . ns_wacn , float ( self . ns_chan ) / 1000000.0 ) )
s . append ( ' secondary control channel(s): %s ' % ' , ' . join ( [ ' %f ' % ( float ( k ) / 1000000.0 ) for k in self . secondary . keys ( ) ] ) )
s . append ( ' stats: tsbks %d crc %d ' % ( self . stats [ ' tsbks ' ] , self . stats [ ' crc ' ] ) )
2014-12-21 13:59:03 +00:00
s . append ( ' ' )
2013-12-10 00:47:21 +00:00
t = time . time ( )
for f in self . voice_frequencies :
2014-12-20 00:16:15 +00:00
tgs = ' %s %s ' % ( self . voice_frequencies [ f ] [ ' tgid ' ] [ 0 ] , self . voice_frequencies [ f ] [ ' tgid ' ] [ 1 ] )
s . append ( ' voice frequency %f tgid(s) %s %4.1f s ago count %d ' % ( f / 1000000.0 , tgs , t - self . voice_frequencies [ f ] [ ' time ' ] , self . voice_frequencies [ f ] [ ' counter ' ] ) )
2014-12-21 13:59:03 +00:00
s . append ( ' ' )
2013-12-10 00:47:21 +00:00
for table in self . freq_table :
a = self . freq_table [ table ] [ ' frequency ' ] / 1000000.0
b = self . freq_table [ table ] [ ' step ' ] / 1000000.0
c = self . freq_table [ table ] [ ' offset ' ] / 1000000.0
s . append ( ' tbl-id: %x frequency: %f step %f offset %f ' % ( table , a , b , c ) )
#self.freq_table[table]['frequency'] / 1000000.0, self.freq_table[table]['step'] / 1000000.0, self.freq_table[table]['offset']) / 1000000.0)
for f in self . adjacent :
s . append ( ' adjacent %f : %s ' % ( float ( f ) / 1000000.0 , self . adjacent [ f ] ) )
return ' \n ' . join ( s )
2021-07-26 00:46:14 +00:00
def post_event ( self , d ) :
if self . send_event is None :
return
if d [ ' cc_event ' ] in FILTERED_CC_EVENT :
return
d [ ' json_type ' ] = ' cc_event '
d [ ' sysid ' ] = self . rfss_syid if self . rfss_syid else 0
d [ ' sysname ' ] = self . sysname
d [ ' time ' ] = time . time ( )
d [ ' nac ' ] = self . nac
self . send_event ( d )
def mk_tg_dict ( self , tgid ) :
return { ' tg_id ' : tgid , ' tag ' : self . get_tag ( tgid ) , ' priority ' : self . get_prio ( tgid ) , ' color ' : self . get_tag_color ( tgid ) }
def mk_src_dict ( self , srcaddr ) :
return { ' unit_id ' : srcaddr , ' tag ' : self . get_unit_id_tag ( srcaddr ) , ' color ' : self . get_unit_id_color ( srcaddr ) }
2014-08-07 22:57:24 +00:00
def get_tdma_slot ( self , id ) :
2014-12-21 13:59:03 +00:00
table = ( id >> 12 ) & 0xf
channel = id & 0xfff
if table not in self . freq_table :
return None
if ' tdma ' not in self . freq_table [ table ] :
return None
return channel & 1
2013-12-10 00:47:21 +00:00
2014-08-07 22:57:24 +00:00
# return frequency in Hz
def channel_id_to_frequency ( self , id ) :
2014-12-21 13:59:03 +00:00
table = ( id >> 12 ) & 0xf
channel = id & 0xfff
if table not in self . freq_table :
return None
if ' tdma ' not in self . freq_table [ table ] :
return self . freq_table [ table ] [ ' frequency ' ] + self . freq_table [ table ] [ ' step ' ] * channel
return self . freq_table [ table ] [ ' frequency ' ] + self . freq_table [ table ] [ ' step ' ] * int ( channel / self . freq_table [ table ] [ ' tdma ' ] )
2014-08-07 22:57:24 +00:00
def channel_id_to_string ( self , id ) :
f = self . channel_id_to_frequency ( id )
if f is None :
2014-12-21 13:59:03 +00:00
return " ID-0x %x " % ( id )
2014-08-07 22:57:24 +00:00
return " %f " % ( f / 1000000.0 )
2013-12-10 00:47:21 +00:00
2021-07-26 00:46:14 +00:00
def get_unit_id_color ( self , unit_id ) :
if not unit_id :
return 0
elif self . unit_id_map is None :
return 0
return self . unit_id_map . get_color ( unit_id )
def get_unit_id_tag ( self , unit_id ) :
if not unit_id :
return " "
elif self . unit_id_map is None :
return " Unit %d " % unit_id
return self . unit_id_map . get_tag ( unit_id )
2013-12-10 00:47:21 +00:00
def get_tag ( self , tgid ) :
if not tgid :
return " "
2021-07-26 00:46:14 +00:00
elif self . tgid_map is None :
return " Talkgroup %d " % tgid
return self . tgid_map . get_tag ( tgid )
2018-12-26 02:10:06 +00:00
2021-07-26 00:46:14 +00:00
def get_tag_color ( self , tgid ) :
if not tgid :
return 0
elif self . tgid_map is None :
return 0
return self . tgid_map . get_color ( tgid ) % 100
# the third col in the tsv file performs two separate roles
# low-order 2 decimal digits are taken as the tag color code
# remaining upper digits are priority - this is incompatibile
# with previous tsv files (if priorities are in use).
2018-12-26 02:10:06 +00:00
def get_prio ( self , tgid ) :
2021-07-26 00:46:14 +00:00
if not tgid :
return 0
elif self . tgid_map is None :
return 0
return int ( self . tgid_map . get_color ( tgid ) / / 100 )
def end_call ( self , call , code ) :
d = { ' cc_event ' : ' end_call ' , ' code ' : code , ' srcaddr ' : call [ ' srcaddr ' ] , ' tgid ' : call [ ' tgid ' ] , ' duration ' : call [ ' end_time ' ] - call [ ' start_time ' ] , ' count ' : call [ ' count ' ] , ' opcode ' : - 1 }
self . post_event ( d )
return
def frequency_tracking_expire ( self , always = False ) :
current_time = time . time ( )
if current_time < self . next_frequency_tracking_expire and not always :
return
self . next_frequency_tracking_expire = current_time + self . CHECK_INTERVAL
for f in self . frequency_table . keys ( ) :
freq = self . frequency_table [ f ]
for i in range ( len ( freq [ ' calls ' ] ) ) :
call = freq [ ' calls ' ] [ i ]
if call is not None and call [ ' end_time ' ] == 0 and call [ ' last_active ' ] + self . CALL_TIMEOUT < current_time :
call [ ' end_time ' ] = current_time
self . end_call ( call , 1 )
def frequency_tracking ( self , frequency , tgid , tdma_slot , srcaddr , protected ) :
current_time = time . time ( )
is_tdma = tdma_slot is not None
slot = tdma_slot if is_tdma else 0
if frequency not in self . frequency_table . keys ( ) :
self . frequency_table [ frequency ] = { ' counter ' : 0 ,
' calls ' : [ None , None ] ,
' tgids ' : [ None , None ] ,
' last_active ' : current_time }
self . frequency_table [ frequency ] [ ' tdma ' ] = is_tdma
call = { ' srcaddr ' : self . mk_src_dict ( srcaddr ) ,
' protected ' : protected ,
' tgid ' : self . mk_tg_dict ( tgid ) ,
' count ' : 0 ,
' start_time ' : current_time ,
' last_active ' : current_time ,
' end_time ' : 0 }
self . frequency_table [ frequency ] [ ' tgids ' ] [ slot ] = tgid
self . frequency_table [ frequency ] [ ' calls ' ] [ slot ] = call
return
self . frequency_table [ frequency ] [ ' counter ' ] + = 1
self . frequency_table [ frequency ] [ ' last_active ' ] = current_time
found = 0
for f in self . frequency_table . keys ( ) :
freq = self . frequency_table [ f ]
for i in range ( len ( freq [ ' tgids ' ] ) ) :
tg = freq [ ' tgids ' ] [ i ]
call = freq [ ' calls ' ] [ i ]
if tg is None or call is None or tg != tgid :
continue
# general housekeeping: expire calls
if call [ ' end_time ' ] == 0 and call [ ' last_active ' ] + self . CALL_TIMEOUT < current_time :
call [ ' end_time ' ] = current_time
self . end_call ( call , 2 )
if f == frequency and freq [ ' tdma ' ] == is_tdma and i == slot :
found = 1
call [ ' last_active ' ] = current_time
call [ ' end_time ' ] = 0
call [ ' count ' ] + = 1
if srcaddr is not None :
call [ ' srcaddr ' ] = self . mk_src_dict ( srcaddr )
if protected is not None :
call [ ' protected ' ] = protected
else : # found other entry with matching tgid but freq and/or tdma is wrong
if call [ ' end_time ' ] == 0 :
call [ ' end_time ' ] = current_time
self . end_call ( call , 3 )
if found :
return
call = { ' srcaddr ' : self . mk_src_dict ( srcaddr ) ,
' protected ' : protected ,
' tgid ' : self . mk_tg_dict ( tgid ) ,
' count ' : 0 ,
' start_time ' : current_time ,
' last_active ' : current_time ,
' end_time ' : 0 }
self . frequency_table [ frequency ] [ ' tdma ' ] = is_tdma
self . frequency_table [ frequency ] [ ' tgids ' ] [ slot ] = tgid
self . frequency_table [ frequency ] [ ' calls ' ] [ slot ] = call
if not is_tdma :
self . frequency_table [ frequency ] [ ' tgids ' ] [ 1 ] = None
call = self . frequency_table [ frequency ] [ ' calls ' ] [ 1 ]
if call and call [ ' end_time ' ] == 0 :
call [ ' end_time ' ] = current_time
self . end_call ( call , 4 )
self . frequency_table [ frequency ] [ ' calls ' ] [ 1 ] = None
2018-12-26 02:10:06 +00:00
def update_talkgroup ( self , frequency , tgid , tdma_slot , srcaddr ) :
if self . debug > = 5 :
sys . stderr . write ( ' %f set tgid= %s , srcaddr= %s \n ' % ( time . time ( ) , tgid , srcaddr ) )
2013-12-10 00:47:21 +00:00
2014-12-20 00:16:15 +00:00
if tgid not in self . talkgroups :
self . talkgroups [ tgid ] = { ' counter ' : 0 }
2018-12-26 02:10:06 +00:00
if self . debug > = 5 :
sys . stderr . write ( ' %f new tgid: %s %s prio %d \n ' % ( time . time ( ) , tgid , self . get_tag ( tgid ) , self . get_prio ( tgid ) ) )
2014-12-20 00:16:15 +00:00
self . talkgroups [ tgid ] [ ' time ' ] = time . time ( )
self . talkgroups [ tgid ] [ ' frequency ' ] = frequency
self . talkgroups [ tgid ] [ ' tdma_slot ' ] = tdma_slot
2018-12-26 02:10:06 +00:00
self . talkgroups [ tgid ] [ ' prio ' ] = self . get_prio ( tgid )
2021-07-26 00:46:14 +00:00
self . talkgroups [ tgid ] [ ' tag_color ' ] = self . get_tag_color ( tgid )
if srcaddr is None or not srcaddr :
self . talkgroups [ tgid ] [ ' srcaddr ' ] = 0
self . talkgroups [ tgid ] [ ' srcaddr_tag ' ] = " "
self . talkgroups [ tgid ] [ ' srcaddr_color ' ] = 0
else :
self . talkgroups [ tgid ] [ ' srcaddr ' ] = srcaddr
self . talkgroups [ tgid ] [ ' srcaddr_tag ' ] = self . get_unit_id_tag ( srcaddr )
self . talkgroups [ tgid ] [ ' srcaddr_color ' ] = self . get_unit_id_color ( srcaddr )
2014-12-20 00:16:15 +00:00
2021-07-26 00:46:14 +00:00
def update_voice_frequency ( self , frequency , tgid = None , tdma_slot = None , srcaddr = None , protected = None ) :
2014-12-20 00:16:15 +00:00
if not frequency : # e.g., channel identifier not yet known
2013-12-10 00:47:21 +00:00
return
2021-07-26 00:46:14 +00:00
self . frequency_tracking ( frequency , tgid , tdma_slot , srcaddr , protected )
2018-12-26 02:10:06 +00:00
self . update_talkgroup ( frequency , tgid , tdma_slot , srcaddr )
2013-12-10 00:47:21 +00:00
if frequency not in self . voice_frequencies :
self . voice_frequencies [ frequency ] = { ' counter ' : 0 }
2014-12-21 13:59:03 +00:00
sorted_freqs = collections . OrderedDict ( sorted ( self . voice_frequencies . items ( ) ) )
self . voice_frequencies = sorted_freqs
2018-12-26 02:10:06 +00:00
if self . debug > = 5 :
sys . stderr . write ( ' %f new freq: %f \n ' % ( time . time ( ) , frequency / 1000000.0 ) )
2014-12-20 00:16:15 +00:00
if tdma_slot is None :
tdma_slot = 0
2021-07-26 00:46:14 +00:00
for s in ' tgid srcaddr tg_tag tg_color srcaddr_tag srcaddr_color ' . split ( ) :
if s not in self . voice_frequencies [ frequency ] . keys ( ) :
self . voice_frequencies [ frequency ] [ s ] = [ None , None ]
2014-12-20 00:16:15 +00:00
self . voice_frequencies [ frequency ] [ ' tgid ' ] [ tdma_slot ] = tgid
2013-12-10 00:47:21 +00:00
self . voice_frequencies [ frequency ] [ ' counter ' ] + = 1
self . voice_frequencies [ frequency ] [ ' time ' ] = time . time ( )
2021-07-26 00:46:14 +00:00
self . voice_frequencies [ frequency ] [ ' tg_tag ' ] [ tdma_slot ] = self . get_tag ( tgid )
self . voice_frequencies [ frequency ] [ ' tg_color ' ] [ tdma_slot ] = self . get_tag_color ( tgid )
if srcaddr is not None :
self . voice_frequencies [ frequency ] [ ' srcaddr ' ] [ tdma_slot ] = srcaddr
self . voice_frequencies [ frequency ] [ ' srcaddr_tag ' ] [ tdma_slot ] = self . get_unit_id_tag ( srcaddr )
self . voice_frequencies [ frequency ] [ ' srcaddr_color ' ] [ tdma_slot ] = self . get_unit_id_color ( srcaddr )
2014-11-30 16:32:28 +00:00
2014-12-20 00:16:15 +00:00
def get_updated_talkgroups ( self , start_time ) :
return [ tgid for tgid in self . talkgroups if (
self . talkgroups [ tgid ] [ ' time ' ] > = start_time and
tgid not in self . blacklist and
not ( self . whitelist and tgid not in self . whitelist ) ) ]
2015-10-02 13:40:41 +00:00
def blacklist_update ( self , start_time ) :
expired_tgs = [ tg for tg in self . blacklist . keys ( )
if self . blacklist [ tg ] is not None
and self . blacklist [ tg ] < start_time ]
for tg in expired_tgs :
self . blacklist . pop ( tg )
2018-12-26 02:10:06 +00:00
def find_talkgroup ( self , start_time , tgid = None , hold = False ) :
tgt_tgid = None
2015-10-02 13:40:41 +00:00
self . blacklist_update ( start_time )
2018-12-26 02:10:06 +00:00
if tgid is not None and tgid in self . talkgroups :
tgt_tgid = tgid
2014-12-20 00:16:15 +00:00
for active_tgid in self . talkgroups :
2018-12-26 02:10:06 +00:00
if hold :
break
2014-12-20 00:16:15 +00:00
if self . talkgroups [ active_tgid ] [ ' time ' ] < start_time :
2013-12-10 00:47:21 +00:00
continue
if active_tgid in self . blacklist :
continue
if self . whitelist and active_tgid not in self . whitelist :
continue
2014-12-20 00:16:15 +00:00
if self . talkgroups [ active_tgid ] [ ' tdma_slot ' ] is not None and ( self . ns_syid < 0 or self . ns_wacn < 0 ) :
2014-08-13 21:02:23 +00:00
continue
2018-12-26 02:10:06 +00:00
if tgt_tgid is None :
tgt_tgid = active_tgid
elif self . talkgroups [ active_tgid ] [ ' prio ' ] < self . talkgroups [ tgt_tgid ] [ ' prio ' ] :
tgt_tgid = active_tgid
if tgt_tgid is not None and self . talkgroups [ tgt_tgid ] [ ' time ' ] > = start_time :
return self . talkgroups [ tgt_tgid ] [ ' frequency ' ] , tgt_tgid , self . talkgroups [ tgt_tgid ] [ ' tdma_slot ' ] , self . talkgroups [ tgt_tgid ] [ ' srcaddr ' ]
return None , None , None , None
def dump_tgids ( self ) :
sys . stderr . write ( " Known tgids: { " )
for tgid in sorted ( self . talkgroups . keys ( ) ) :
sys . stderr . write ( " %d " % tgid ) ;
sys . stderr . write ( " } \n " )
2013-12-10 00:47:21 +00:00
2015-10-02 13:40:41 +00:00
def add_blacklist ( self , tgid , end_time = None ) :
2013-12-10 00:47:21 +00:00
if not tgid :
return
2015-10-02 13:40:41 +00:00
self . blacklist [ tgid ] = end_time
2013-12-10 00:47:21 +00:00
2018-12-26 02:10:06 +00:00
def decode_mbt_data ( self , opcode , src , header , mbt_data ) :
2014-12-20 00:16:15 +00:00
self . cc_timeouts = 0
self . last_tsbk = time . time ( )
2016-08-26 14:03:11 +00:00
updated = 0
2013-12-10 00:47:21 +00:00
if self . debug > 10 :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' decode_mbt_data: %x %x \n ' % ( opcode , mbt_data ) )
2014-12-21 13:59:03 +00:00
if opcode == 0x0 : # grp voice channel grant
2021-07-26 00:46:14 +00:00
srcaddr = ( header >> 48 ) & 0xffffff
opts = ( header >> 24 ) & 0xff
2014-12-21 13:59:03 +00:00
ch1 = ( mbt_data >> 64 ) & 0xffff
ch2 = ( mbt_data >> 48 ) & 0xffff
ga = ( mbt_data >> 32 ) & 0xffff
2016-08-25 21:31:02 +00:00
f = self . channel_id_to_frequency ( ch1 )
2021-07-26 00:46:14 +00:00
if self . debug > 0 and src != srcaddr :
sys . stderr . write ( ' decode_mbt_data: grp_v_ch_grant: src %d does not match srcaddr %d \n ' % ( src , srcaddr ) )
d = { ' cc_event ' : ' grp_v_ch_grant_mbt ' , ' options ' : opts , ' frequency ' : f , ' group ' : self . mk_tg_dict ( ga ) , ' srcaddr ' : self . mk_src_dict ( srcaddr ) , ' opcode ' : opcode , ' tdma_slot ' : self . get_tdma_slot ( ch1 ) }
self . post_event ( d )
self . update_voice_frequency ( f , tgid = ga , tdma_slot = self . get_tdma_slot ( ch1 ) , srcaddr = srcaddr , protected = opts & 64 == 64 )
2016-08-25 21:31:02 +00:00
if f :
updated + = 1
2014-12-21 13:59:03 +00:00
if self . debug > 10 :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' mbt00 voice grant ch1 %x ch2 %x addr 0x %x \n ' % ( ch1 , ch2 , ga ) )
2014-12-21 13:59:03 +00:00
elif opcode == 0x3c : # adjacent status
syid = ( header >> 48 ) & 0xfff
rfid = ( header >> 24 ) & 0xff
stid = ( header >> 16 ) & 0xff
ch1 = ( mbt_data >> 80 ) & 0xffff
ch2 = ( mbt_data >> 64 ) & 0xffff
f1 = self . channel_id_to_frequency ( ch1 )
f2 = self . channel_id_to_frequency ( ch2 )
if f1 and f2 :
self . adjacent [ f1 ] = ' rfid: %d stid: %d uplink: %f ' % ( rfid , stid , f2 / 1000000.0 )
2018-03-15 18:43:57 +00:00
self . adjacent_data [ f1 ] = { ' rfid ' : rfid , ' stid ' : stid , ' uplink ' : f2 , ' table ' : None , ' sysid ' : syid }
2014-12-21 13:59:03 +00:00
if self . debug > 10 :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' mbt3c adjacent sys %x rfid %x stid %x ch1 %x ch2 %x f1 %s f2 %s \n ' % ( syid , rfid , stid , ch1 , ch2 , self . channel_id_to_string ( ch1 ) , self . channel_id_to_string ( ch2 ) ) )
2014-12-21 13:59:03 +00:00
elif opcode == 0x3b : # network status
syid = ( header >> 48 ) & 0xfff
wacn = ( mbt_data >> 76 ) & 0xfffff
ch1 = ( mbt_data >> 56 ) & 0xffff
ch2 = ( mbt_data >> 40 ) & 0xffff
f1 = self . channel_id_to_frequency ( ch1 )
f2 = self . channel_id_to_frequency ( ch2 )
if f1 and f2 :
self . ns_syid = syid
self . ns_wacn = wacn
self . ns_chan = f1
if self . debug > 10 :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' mbt3b net stat sys %x wacn %x ch1 %s ch2 %s \n ' % ( syid , wacn , self . channel_id_to_string ( ch1 ) , self . channel_id_to_string ( ch2 ) ) )
2014-12-21 13:59:03 +00:00
elif opcode == 0x3a : # rfss status
syid = ( header >> 48 ) & 0xfff
rfid = ( mbt_data >> 88 ) & 0xff
stid = ( mbt_data >> 80 ) & 0xff
ch1 = ( mbt_data >> 64 ) & 0xffff
ch2 = ( mbt_data >> 48 ) & 0xffff
f1 = self . channel_id_to_frequency ( ch1 )
f2 = self . channel_id_to_frequency ( ch2 )
if f1 and f2 :
self . rfss_syid = syid
self . rfss_rfid = rfid
self . rfss_stid = stid
self . rfss_chan = f1
self . rfss_txchan = f2
if self . debug > 10 :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' mbt3a rfss stat sys %x rfid %x stid %x ch1 %s ch2 %s \n ' % ( syid , rfid , stid , self . channel_id_to_string ( ch1 ) , self . channel_id_to_string ( ch2 ) ) )
else :
sys . stderr . write ( ' decode_mbt_data(): received unsupported mbt opcode %x \n ' % opcode )
2016-08-26 14:03:11 +00:00
return updated
2013-12-10 00:47:21 +00:00
2021-07-26 00:46:14 +00:00
def decode_tdma_blk ( self , blk ) :
self . stats [ ' tsbks ' ] + = 1
2022-01-04 20:29:01 +00:00
msg0 = get_ordinals ( blk [ : 1 ] )
op = ( msg0 >> 6 ) & 3
moc = msg0 & 0x3f
2021-07-26 00:46:14 +00:00
if self . debug > 1 :
2022-01-04 20:29:01 +00:00
sys . stderr . write ( ' tdma_cc: decode_blk: op %x moc %x len %d msg0 0x %x \n ' % ( op , moc , len ( blk ) , msg0 ) )
if op == 0 and moc == 0 : # null msg
if self . debug > 10 :
sys . stderr . write ( ' tdma_cc: null block \n ' )
return - 1 ;
if op == 0 and moc == 8 : # null msg zero bias
if self . debug > 10 :
sys . stderr . write ( ' tdma_cc: null block zero bias \n ' )
return - 1 ;
if op == 1 and moc == 0x38 : # system service broadcast
if self . debug > 10 :
sys . stderr . write ( ' tdma_cc: system service broadcast \n ' )
msglen = 9
elif op == 3 and moc == 0x16 : # SNDCP data ch
if self . debug > 10 :
sys . stderr . write ( ' tdma_cc: SNDCP data channel announce \n ' )
msglen = 9
elif op == 0 and moc == 1 : # group voice channel user
msglen = 7
msg = get_ordinals ( blk [ : msglen ] )
options = ( msg >> 40 ) & 0xff
ga1 = ( msg >> 24 ) & 0xffff
sa1 = ( msg ) & 0xffffff
if self . debug > 1 :
sys . stderr . write ( ' decode_blk: group voice channel user opts %x ga %x sa %x \n ' % ( options , ga1 , sa1 ) )
elif op == 0 and moc == 0x30 : # power control sq
msglen = 5
if self . debug > 1 :
sys . stderr . write ( ' decode_blk: power control sq \n ' )
elif op == 1 and moc == 0x30 : # sync broadcast
msglen = 9
msg = get_ordinals ( blk [ : msglen ] )
year = ( msg >> 33 ) & 0x7f
month = ( msg >> 29 ) & 0xf
day = ( msg >> 24 ) & 0x1f
if self . debug > 1 :
sys . stderr . write ( ' decode_blk: synchronization broadcast %d %d %d \n ' % ( year , month , day ) )
elif op == 1 and moc == 2 : # group voice channel grant update
msglen = 9
msg = get_ordinals ( blk [ : msglen ] )
ch1 = ( msg >> 48 ) & 0xffff
ga1 = ( msg >> 32 ) & 0xffff
ch2 = ( msg >> 16 ) & 0xffff
ga2 = ( msg ) & 0xffff
f1 = self . channel_id_to_frequency ( ch1 )
f2 = self . channel_id_to_frequency ( ch2 )
if self . debug > 1 :
sys . stderr . write ( ' decode_blk: group voice channel grant update ga1 %d ch1 %s ga2 %d ch2 %s \n ' % ( ga1 , f1 , ga2 , f2 ) )
opcode = 2 # (p25p1 opcode)
d = { ' cc_event ' : ' grp_v_ch_grant_updt ' , ' mfrid ' : 0 , ' frequency1 ' : f1 , ' group1 ' : self . mk_tg_dict ( ga1 ) , ' opcode ' : opcode , ' tdma_slot ' : self . get_tdma_slot ( ch1 ) }
self . update_voice_frequency ( f1 , tgid = ga1 , tdma_slot = self . get_tdma_slot ( ch1 ) )
if f1 != f2 :
self . update_voice_frequency ( f2 , tgid = ga2 , tdma_slot = self . get_tdma_slot ( ch2 ) )
d [ ' frequency2 ' ] = f2
d [ ' group2 ' ] = self . mk_tg_dict ( ga2 )
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' phase 2 tsbk02 grant update: chan %s %d %s %d \n ' % ( self . channel_id_to_string ( ch1 ) , ga1 , self . channel_id_to_string ( ch2 ) , ga2 ) )
elif op == 1 and moc == 0x3c : # adjacent
2021-07-26 00:46:14 +00:00
msglen = 9
msg = get_ordinals ( blk [ : msglen ] )
syid = ( msg >> 40 ) & 0xfff
rfid = ( msg >> 32 ) & 0xff
stid = ( msg >> 24 ) & 0xff
ch1 = ( msg >> 8 ) & 0xffff
table = ( ch1 >> 12 ) & 0xf
cls = msg & 0xff
f1 = self . channel_id_to_frequency ( ch1 )
if f1 and table in self . freq_table :
self . adjacent [ f1 ] = ' rfid: %d stid: %d uplink: %f tbl: %d sysid:0x %x ' % ( rfid , stid , ( f1 + self . freq_table [ table ] [ ' offset ' ] ) / 1000000.0 , table , syid )
self . adjacent_data [ f1 ] = { ' rfid ' : rfid , ' stid ' : stid , ' uplink ' : f1 + self . freq_table [ table ] [ ' offset ' ] , ' table ' : table , ' sysid ' : syid }
if self . debug > 10 :
sys . stderr . write ( ' tsbk3c adjacent: rfid %x stid %d ch1 %x ( %s ) \n ' % ( rfid , stid , ch1 , self . channel_id_to_string ( ch1 ) ) )
if table in self . freq_table :
sys . stderr . write ( ' tsbk3c : %s %s \n ' % ( self . freq_table [ table ] [ ' frequency ' ] , self . freq_table [ table ] [ ' step ' ] ) )
elif op == 1 and moc == 0x33 : # iden up tdma
msglen = 9
opcode = 0x33
msg = get_ordinals ( blk [ : msglen ] )
iden = ( msg >> 60 ) & 0xf
channel_type = ( msg >> 56 ) & 0xf
toff0 = ( msg >> 42 ) & 0x3fff
toff_sign = ( toff0 >> 13 ) & 1
toff = toff0 & 0x1fff
if toff_sign == 0 :
toff = 0 - toff
spac = ( msg >> 32 ) & 0x3ff
f1 = ( msg ) & 0xffffffff
slots_per_carrier = [ 1 , 1 , 1 , 2 , 4 , 2 ]
self . freq_table [ iden ] = { }
self . freq_table [ iden ] [ ' offset ' ] = toff * spac * 125
self . freq_table [ iden ] [ ' step ' ] = spac * 125
self . freq_table [ iden ] [ ' frequency ' ] = f1 * 5
self . freq_table [ iden ] [ ' tdma ' ] = slots_per_carrier [ channel_type ]
d = { ' cc_event ' : ' iden_up_tdma ' , ' iden ' : iden , ' offset ' : self . freq_table [ iden ] [ ' offset ' ] , ' step ' : self . freq_table [ iden ] [ ' step ' ] , ' freq ' : self . freq_table [ iden ] [ ' frequency ' ] , ' slots ' : self . freq_table [ iden ] [ ' tdma ' ] , ' opcode ' : opcode }
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' tsbk33 iden up tdma id %d f %d offset %d spacing %d slots/carrier %d \n ' % ( iden , self . freq_table [ iden ] [ ' frequency ' ] , self . freq_table [ iden ] [ ' offset ' ] , self . freq_table [ iden ] [ ' step ' ] , self . freq_table [ iden ] [ ' tdma ' ] ) )
elif op == 1 and moc == 0x3b : # network status
msglen = 11
opcode = 0x3b
msg = get_ordinals ( blk [ : msglen ] )
wacn = ( msg >> 52 ) & 0xfffff
syid = ( msg >> 40 ) & 0xfff
ch1 = ( msg >> 24 ) & 0xffff
color = ( msg ) & 0xfff
sys . stderr . write ( ' tsbk3b net stat: color: 0x %x \n ' % color )
f1 = self . channel_id_to_frequency ( ch1 )
if f1 :
self . ns_syid = syid
self . ns_wacn = wacn
self . ns_chan = f1
if self . debug > 10 :
sys . stderr . write ( ' tsbk3b net stat: wacn %x syid %x ch1 %x ( %s ) \n ' % ( wacn , syid , ch1 , self . channel_id_to_string ( ch1 ) ) )
elif op == 1 and moc == 0x3a : # rfss status
msglen = 9
opcode = 0x3a
msg = get_ordinals ( blk [ : msglen ] )
syid = ( msg >> 40 ) & 0xfff
rfid = ( msg >> 32 ) & 0xff
stid = ( msg >> 24 ) & 0xff
chan = ( msg >> 8 ) & 0xffff
f1 = self . channel_id_to_frequency ( chan )
if f1 :
self . rfss_syid = syid
self . rfss_rfid = rfid
self . rfss_stid = stid
self . rfss_chan = f1
self . rfss_txchan = f1 + self . freq_table [ chan >> 12 ] [ ' offset ' ]
if self . debug > 10 :
sys . stderr . write ( ' tsbk3a rfss status: syid: %x rfid %x stid %d ch1 %x ( %s ) \n ' % ( syid , rfid , stid , chan , self . channel_id_to_string ( chan ) ) )
elif op == 1 and moc == 0x39 : # secondary cc
msglen = 9
opcode = 0x39
msg = get_ordinals ( blk [ : msglen ] )
rfid = ( msg >> 56 ) & 0xff
stid = ( msg >> 48 ) & 0xff
ch1 = ( msg >> 32 ) & 0xffff
ch2 = ( msg >> 8 ) & 0xffff
f1 = self . channel_id_to_frequency ( ch1 )
f2 = self . channel_id_to_frequency ( ch2 )
if f1 and f2 :
self . secondary [ f1 ] = 1
self . secondary [ f2 ] = 1
sorted_freqs = collections . OrderedDict ( sorted ( self . secondary . items ( ) ) )
self . secondary = sorted_freqs
if self . debug > 10 :
sys . stderr . write ( ' tsbk39 secondary cc: rfid %x stid %d ch1 %x ( %s ) ch2 %x ( %s ) \n ' % ( rfid , stid , ch1 , self . channel_id_to_string ( ch1 ) , ch2 , self . channel_id_to_string ( ch2 ) ) )
elif op == 1 and moc == 0x3d : # iden_up
msglen = 9
opcode = 0x3d
msg = get_ordinals ( blk [ : msglen ] )
iden = ( msg >> 60 ) & 0xf
bw = ( msg >> 51 ) & 0x1ff
toff0 = ( msg >> 42 ) & 0x1ff
spac = ( msg >> 32 ) & 0x3ff
freq = msg & 0xffffffff
toff_sign = ( toff0 >> 8 ) & 1
toff = toff0 & 0xff
if toff_sign == 0 :
toff = 0 - toff
txt = [ " mob xmit < recv " , " mob xmit > recv " ]
self . freq_table [ iden ] = { }
self . freq_table [ iden ] [ ' offset ' ] = toff * 250000
self . freq_table [ iden ] [ ' step ' ] = spac * 125
self . freq_table [ iden ] [ ' frequency ' ] = freq * 5
d = { ' cc_event ' : ' iden_up ' , ' iden ' : iden , ' offset ' : self . freq_table [ iden ] [ ' offset ' ] , ' step ' : self . freq_table [ iden ] [ ' step ' ] , ' freq ' : self . freq_table [ iden ] [ ' frequency ' ] , ' opcode ' : opcode }
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' tsbk3d iden id %d toff %f spac %f freq %f \n ' % ( iden , toff * 0.25 , spac * 0.125 , freq * 0.000005 ) )
else :
msglen = - 1
if self . debug > 0 :
2022-01-04 20:29:01 +00:00
sys . stderr . write ( ' tdma_cc unknown request: %x %x %x \n ' % ( op , moc , get_ordinals ( blk ) ) )
2021-07-26 00:46:14 +00:00
return msglen
def decode_tdma_cc ( self , blk ) :
2022-01-04 20:29:01 +00:00
while True :
rc = self . decode_tdma_blk ( blk )
if rc < 1 :
break
if rc > = len ( blk ) :
break
blk = blk [ rc : ]
2021-07-26 00:46:14 +00:00
def decode_tsbk_harris ( self , tsbk , opcode , mfrid ) :
HARRIS_SGS_EXPIRES = 5.0 # sec.
updated = 0
if opcode != 0x30 : # GRG_EXENC_CMD
sys . stderr . write ( ' decode_tsbk: unsupported opcode %02x mfrid %02x \n ' % ( opcode , mfrid ) )
return updated
grg_options = ( tsbk >> 72 ) & 0xff
opt_2way = ( grg_options & 0x80 ) == 0
opt_group = ( grg_options & 0x40 ) != 0
opt_act = ( grg_options & 0x20 ) != 0
opt_ssn = grg_options & 0x1f
sg = ( tsbk >> 56 ) & 0xffff
keyid = ( tsbk >> 40 ) & 0xffff
target = ( tsbk >> 16 ) & 0xffffff
d = { ' cc_event ' : ' grg_exenc_cmd ' , ' mfrid ' : mfrid , ' opt_2way ' : opt_2way , ' opt_group ' : opt_group , ' opt_act ' : opt_act , ' opt_ssn ' : opt_ssn , ' sg ' : self . mk_tg_dict ( sg ) , ' keyid ' : keyid }
if opt_group : # target is a group
algid = ( target >> 16 ) & 0xff
target = target & 0xffff
d [ ' target_group ' ] = self . mk_tg_dict ( target )
else : # target is a subscriber unit id
2021-09-07 23:32:11 +00:00
algid = 128
2021-07-26 00:46:14 +00:00
d [ ' target_unit ' ] = self . mk_src_dict ( target )
d [ ' algid ' ] = algid
if opt_act and opt_group : # only group id type supported
sgkey = ' %d - %d ' % ( sg , target )
self . harris_sgs [ sgkey ] = { ' supergroup ' : self . mk_tg_dict ( sg ) , ' target_group ' : self . mk_tg_dict ( target ) , ' algid ' : algid , ' keyid ' : keyid , ' expires ' : time . time ( ) + HARRIS_SGS_EXPIRES }
return updated
def decode_tsbk_mot ( self , tsbk , opcode , mfrid ) :
updated = 0
if opcode == 0x00 : # MOT_GRG_ADD_CMD
sg = ( tsbk >> 64 ) & 0xffff
ga1 = ( tsbk >> 48 ) & 0xffff
ga2 = ( tsbk >> 32 ) & 0xffff
ga3 = ( tsbk >> 16 ) & 0xffff
d = { ' cc_event ' : ' mot_grg_add_cmd ' , ' mfrid ' : mfrid , ' sg ' : self . mk_tg_dict ( sg ) , ' ga1 ' : self . mk_tg_dict ( ga1 ) , ' ga2 ' : self . mk_tg_dict ( ga2 ) , ' ga3 ' : self . mk_tg_dict ( ga3 ) , ' opcode ' : opcode }
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' MOT_GRG_ADD_CMD(0x00): sg: %d ga1: %d ga2: %d ga3: %d \n ' % ( sg , ga1 , ga2 , ga3 ) )
elif opcode == 0x01 : #MOT_GRG_DEL_CMD
sg = ( tsbk >> 64 ) & 0xffff
ga1 = ( tsbk >> 48 ) & 0xffff
ga2 = ( tsbk >> 32 ) & 0xffff
ga3 = ( tsbk >> 16 ) & 0xffff
d = { ' cc_event ' : ' mot_grg_del_cmd ' , ' mfrid ' : mfrid , ' sg ' : self . mk_tg_dict ( sg ) , ' ga1 ' : self . mk_tg_dict ( ga1 ) , ' ga2 ' : self . mk_tg_dict ( ga2 ) , ' ga3 ' : self . mk_tg_dict ( ga3 ) , ' opcode ' : opcode }
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' MOT_GRG_DEL_CMD(0x01): sg: %d ga1: %d ga2: %d ga3: %d \n ' % ( sg , ga1 , ga2 , ga3 ) )
elif opcode == 0x02 : # MOT_GRG_CN_CRANT
ch = ( tsbk >> 56 ) & 0xffff
sg = ( tsbk >> 40 ) & 0xffff
sa = ( tsbk >> 16 ) & 0xffffff
f = self . channel_id_to_frequency ( ch )
d = { ' cc_event ' : ' mot_grg_cn_grant ' , ' mfrid ' : mfrid , ' frequency ' : f , ' sg ' : self . mk_tg_dict ( sg ) , ' sa ' : self . mk_src_dict ( sa ) , ' opcode ' : opcode }
self . post_event ( d )
self . update_voice_frequency ( f , tgid = sg , tdma_slot = self . get_tdma_slot ( ch ) , srcaddr = sa )
if f :
updated + = 1
if self . debug > 10 :
sys . stderr . write ( ' MOT_GRG_CN_GRANT(0x02): freq %s sg: %d sa: %d \n ' % ( self . channel_id_to_string ( ch ) , sg , sa ) )
elif opcode == 0x03 : #MOT_GRG_CN_GRANT_UPDT
ch1 = ( tsbk >> 64 ) & 0xffff
sg1 = ( tsbk >> 48 ) & 0xffff
ch2 = ( tsbk >> 32 ) & 0xffff
sg2 = ( tsbk >> 16 ) & 0xffff
f1 = self . channel_id_to_frequency ( ch1 )
f2 = self . channel_id_to_frequency ( ch2 )
d = { ' cc_event ' : ' mot_grg_cn_grant_updt ' , ' mfrid ' : mfrid , ' frequency1 ' : f1 , ' sg1 ' : self . mk_tg_dict ( sg1 ) , ' opcode ' : opcode }
self . update_voice_frequency ( f1 , tgid = sg1 , tdma_slot = self . get_tdma_slot ( ch1 ) )
if f1 != f2 :
self . update_voice_frequency ( f2 , tgid = sg2 , tdma_slot = self . get_tdma_slot ( ch2 ) )
d [ ' sg2 ' ] = self . mk_tg_dict ( sg2 )
d [ ' frequency2 ' ] = f2
self . post_event ( d )
if f1 :
updated + = 1
if f2 :
updated + = 1
if self . debug > 10 :
sys . stderr . write ( ' MOT_GRG_CN_GRANT_UPDT(0x03): freq %s sg1: %d freq %s sg2: %d \n ' % ( self . channel_id_to_string ( ch1 ) , sg1 , self . channel_id_to_string ( ch2 ) , sg2 ) )
else :
if self . debug > 10 :
sys . stderr . write ( ' decode_tsbk: unsupported opcode %02x mfrid %02x \n ' % ( opcode , mfrid ) )
return updated
2013-12-10 00:47:21 +00:00
def decode_tsbk ( self , tsbk ) :
2014-12-20 00:16:15 +00:00
self . cc_timeouts = 0
2017-04-25 23:59:35 +00:00
self . last_tsbk = time . time ( )
2014-12-21 13:59:03 +00:00
self . stats [ ' tsbks ' ] + = 1
2013-12-10 00:47:21 +00:00
updated = 0
2014-12-21 13:59:03 +00:00
tsbk = tsbk << 16 # for missing crc
opcode = ( tsbk >> 88 ) & 0x3f
2021-07-26 00:46:14 +00:00
mfrid = ( tsbk >> 80 ) & 0xff # mfrid
2014-12-21 13:59:03 +00:00
if self . debug > 10 :
2021-07-26 00:46:14 +00:00
sys . stderr . write ( ' TSBK: 0x %02x 0x %024x mfrid %02x \n ' % ( opcode , tsbk , mfrid ) )
if mfrid == 0x90 :
return self . decode_tsbk_mot ( tsbk , opcode , mfrid )
elif mfrid == 0xa4 :
return self . decode_tsbk_harris ( tsbk , opcode , mfrid )
elif mfrid != 0 :
sys . stderr . write ( ' unsupported tsbk mfrid: 0x %02x opcode 0x %02x \n ' % ( mfrid , opcode ) )
return updated
2014-12-21 13:59:03 +00:00
if opcode == 0x00 : # group voice chan grant
2021-07-26 00:46:14 +00:00
opts = ( tsbk >> 72 ) & 0xff
ch = ( tsbk >> 56 ) & 0xffff
ga = ( tsbk >> 40 ) & 0xffff
sa = ( tsbk >> 16 ) & 0xffffff
f = self . channel_id_to_frequency ( ch )
d = { ' cc_event ' : ' grp_v_ch_grant ' , ' mfrid ' : mfrid , ' options ' : opts , ' frequency ' : f , ' group ' : self . mk_tg_dict ( ga ) , ' srcaddr ' : self . mk_src_dict ( sa ) , ' opcode ' : opcode , ' tdma_slot ' : self . get_tdma_slot ( ch ) }
self . post_event ( d )
self . update_voice_frequency ( f , tgid = ga , tdma_slot = self . get_tdma_slot ( ch ) , srcaddr = sa , protected = opts & 64 == 64 )
if f :
updated + = 1
if self . debug > 10 :
sys . stderr . write ( ' tsbk00 grant freq %s ga %d sa %d \n ' % ( self . channel_id_to_string ( ch ) , ga , sa ) )
2014-12-21 13:59:03 +00:00
elif opcode == 0x02 : # group voice chan grant update
2021-07-26 00:46:14 +00:00
ch1 = ( tsbk >> 64 ) & 0xffff
ga1 = ( tsbk >> 48 ) & 0xffff
ch2 = ( tsbk >> 32 ) & 0xffff
ga2 = ( tsbk >> 16 ) & 0xffff
f1 = self . channel_id_to_frequency ( ch1 )
f2 = self . channel_id_to_frequency ( ch2 )
d = { ' cc_event ' : ' grp_v_ch_grant_updt ' , ' mfrid ' : mfrid , ' frequency1 ' : f1 , ' group1 ' : self . mk_tg_dict ( ga1 ) , ' opcode ' : opcode , ' tdma_slot ' : self . get_tdma_slot ( ch1 ) }
self . update_voice_frequency ( f1 , tgid = ga1 , tdma_slot = self . get_tdma_slot ( ch1 ) )
if f1 != f2 :
self . update_voice_frequency ( f2 , tgid = ga2 , tdma_slot = self . get_tdma_slot ( ch2 ) )
d [ ' frequency2 ' ] = f2
d [ ' group2 ' ] = self . mk_tg_dict ( ga2 )
if f1 :
updated + = 1
if f2 :
updated + = 1
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' tsbk02 grant update: chan %s %d %s %d \n ' % ( self . channel_id_to_string ( ch1 ) , ga1 , self . channel_id_to_string ( ch2 ) , ga2 ) )
2015-02-23 19:03:13 +00:00
elif opcode == 0x03 : # group voice chan grant update exp : TIA.102-AABC-B-2005 page 56
2021-07-26 00:46:14 +00:00
opts = ( tsbk >> 72 ) & 0xff
ch1 = ( tsbk >> 48 ) & 0xffff
ch2 = ( tsbk >> 32 ) & 0xffff
ga = ( tsbk >> 16 ) & 0xffff
f = self . channel_id_to_frequency ( ch1 )
d = { ' cc_event ' : ' grp_v_ch_grant_updt_exp ' , ' mfrid ' : mfrid , ' options ' : opts , ' frequency ' : f , ' group ' : self . mk_tg_dict ( ga ) , ' opcode ' : opcode , ' tdma_slot ' : self . get_tdma_slot ( ch1 ) }
self . post_event ( d )
self . update_voice_frequency ( f , tgid = ga , tdma_slot = self . get_tdma_slot ( ch1 ) )
if f :
updated + = 1
if self . debug > 10 :
sys . stderr . write ( ' tsbk03: freq-t %s freq-r %s ga: %d \n ' % ( self . channel_id_to_string ( ch1 ) , self . channel_id_to_string ( ch2 ) , ga ) )
2016-08-25 21:31:02 +00:00
2014-12-21 13:59:03 +00:00
elif opcode == 0x16 : # sndcp data ch
ch1 = ( tsbk >> 48 ) & 0xffff
ch2 = ( tsbk >> 32 ) & 0xffff
if self . debug > 10 :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' tsbk16 sndcp data ch: chan %x %x \n ' % ( ch1 , ch2 ) )
2014-12-21 13:59:03 +00:00
elif opcode == 0x34 : # iden_up vhf uhf
iden = ( tsbk >> 76 ) & 0xf
bwvu = ( tsbk >> 72 ) & 0xf
toff0 = ( tsbk >> 58 ) & 0x3fff
spac = ( tsbk >> 48 ) & 0x3ff
freq = ( tsbk >> 16 ) & 0xffffffff
toff_sign = ( toff0 >> 13 ) & 1
toff = toff0 & 0x1fff
if toff_sign == 0 :
toff = 0 - toff
txt = [ " mob Tx- " , " mob Tx+ " ]
self . freq_table [ iden ] = { }
self . freq_table [ iden ] [ ' offset ' ] = toff * spac * 125
self . freq_table [ iden ] [ ' step ' ] = spac * 125
self . freq_table [ iden ] [ ' frequency ' ] = freq * 5
2021-07-26 00:46:14 +00:00
d = { ' cc_event ' : ' iden_up_vu ' , ' iden ' : iden , ' bwvu ' : bwvu , ' offset ' : self . freq_table [ iden ] [ ' offset ' ] , ' step ' : self . freq_table [ iden ] [ ' step ' ] , ' freq ' : self . freq_table [ iden ] [ ' frequency ' ] , ' opcode ' : opcode }
self . post_event ( d )
2014-12-21 13:59:03 +00:00
if self . debug > 10 :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' tsbk34 iden vhf/uhf id %d toff %f spac %f freq %f [ %s ] \n ' % ( iden , toff * spac * 0.125 * 1e-3 , spac * 0.125 , freq * 0.000005 , txt [ toff_sign ] ) )
2014-12-21 13:59:03 +00:00
elif opcode == 0x33 : # iden_up_tdma
2021-07-26 00:46:14 +00:00
iden = ( tsbk >> 76 ) & 0xf
channel_type = ( tsbk >> 72 ) & 0xf
toff0 = ( tsbk >> 58 ) & 0x3fff
spac = ( tsbk >> 48 ) & 0x3ff
toff_sign = ( toff0 >> 13 ) & 1
toff = toff0 & 0x1fff
if toff_sign == 0 :
toff = 0 - toff
f1 = ( tsbk >> 16 ) & 0xffffffff
slots_per_carrier = [ 1 , 1 , 1 , 2 , 4 , 2 ]
self . freq_table [ iden ] = { }
self . freq_table [ iden ] [ ' offset ' ] = toff * spac * 125
self . freq_table [ iden ] [ ' step ' ] = spac * 125
self . freq_table [ iden ] [ ' frequency ' ] = f1 * 5
self . freq_table [ iden ] [ ' tdma ' ] = slots_per_carrier [ channel_type ]
d = { ' cc_event ' : ' iden_up_tdma ' , ' iden ' : iden , ' offset ' : self . freq_table [ iden ] [ ' offset ' ] , ' step ' : self . freq_table [ iden ] [ ' step ' ] , ' freq ' : self . freq_table [ iden ] [ ' frequency ' ] , ' slots ' : self . freq_table [ iden ] [ ' tdma ' ] , ' opcode ' : opcode }
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' tsbk33 iden up tdma id %d f %d offset %d spacing %d slots/carrier %d \n ' % ( iden , self . freq_table [ iden ] [ ' frequency ' ] , self . freq_table [ iden ] [ ' offset ' ] , self . freq_table [ iden ] [ ' step ' ] , self . freq_table [ iden ] [ ' tdma ' ] ) )
2014-12-21 13:59:03 +00:00
elif opcode == 0x3d : # iden_up
iden = ( tsbk >> 76 ) & 0xf
bw = ( tsbk >> 67 ) & 0x1ff
toff0 = ( tsbk >> 58 ) & 0x1ff
spac = ( tsbk >> 48 ) & 0x3ff
freq = ( tsbk >> 16 ) & 0xffffffff
toff_sign = ( toff0 >> 8 ) & 1
toff = toff0 & 0xff
if toff_sign == 0 :
toff = 0 - toff
txt = [ " mob xmit < recv " , " mob xmit > recv " ]
self . freq_table [ iden ] = { }
self . freq_table [ iden ] [ ' offset ' ] = toff * 250000
self . freq_table [ iden ] [ ' step ' ] = spac * 125
self . freq_table [ iden ] [ ' frequency ' ] = freq * 5
2021-07-26 00:46:14 +00:00
d = { ' cc_event ' : ' iden_up ' , ' iden ' : iden , ' offset ' : self . freq_table [ iden ] [ ' offset ' ] , ' step ' : self . freq_table [ iden ] [ ' step ' ] , ' freq ' : self . freq_table [ iden ] [ ' frequency ' ] , ' opcode ' : opcode }
self . post_event ( d )
2014-12-21 13:59:03 +00:00
if self . debug > 10 :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' tsbk3d iden id %d toff %f spac %f freq %f \n ' % ( iden , toff * 0.25 , spac * 0.125 , freq * 0.000005 ) )
2014-12-21 13:59:03 +00:00
elif opcode == 0x3a : # rfss status
syid = ( tsbk >> 56 ) & 0xfff
rfid = ( tsbk >> 48 ) & 0xff
stid = ( tsbk >> 40 ) & 0xff
chan = ( tsbk >> 24 ) & 0xffff
f1 = self . channel_id_to_frequency ( chan )
if f1 :
self . rfss_syid = syid
self . rfss_rfid = rfid
self . rfss_stid = stid
self . rfss_chan = f1
self . rfss_txchan = f1 + self . freq_table [ chan >> 12 ] [ ' offset ' ]
if self . debug > 10 :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' tsbk3a rfss status: syid: %x rfid %x stid %d ch1 %x ( %s ) \n ' % ( syid , rfid , stid , chan , self . channel_id_to_string ( chan ) ) )
2014-12-21 13:59:03 +00:00
elif opcode == 0x39 : # secondary cc
rfid = ( tsbk >> 72 ) & 0xff
stid = ( tsbk >> 64 ) & 0xff
ch1 = ( tsbk >> 48 ) & 0xffff
ch2 = ( tsbk >> 24 ) & 0xffff
f1 = self . channel_id_to_frequency ( ch1 )
f2 = self . channel_id_to_frequency ( ch2 )
if f1 and f2 :
self . secondary [ f1 ] = 1
self . secondary [ f2 ] = 1
sorted_freqs = collections . OrderedDict ( sorted ( self . secondary . items ( ) ) )
self . secondary = sorted_freqs
if self . debug > 10 :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' tsbk39 secondary cc: rfid %x stid %d ch1 %x ( %s ) ch2 %x ( %s ) \n ' % ( rfid , stid , ch1 , self . channel_id_to_string ( ch1 ) , ch2 , self . channel_id_to_string ( ch2 ) ) )
2014-12-21 13:59:03 +00:00
elif opcode == 0x3b : # network status
wacn = ( tsbk >> 52 ) & 0xfffff
syid = ( tsbk >> 40 ) & 0xfff
ch1 = ( tsbk >> 24 ) & 0xffff
f1 = self . channel_id_to_frequency ( ch1 )
if f1 :
self . ns_syid = syid
self . ns_wacn = wacn
self . ns_chan = f1
if self . debug > 10 :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' tsbk3b net stat: wacn %x syid %x ch1 %x ( %s ) \n ' % ( wacn , syid , ch1 , self . channel_id_to_string ( ch1 ) ) )
2014-12-21 13:59:03 +00:00
elif opcode == 0x3c : # adjacent status
2018-03-15 18:43:57 +00:00
syid = ( tsbk >> 56 ) & 0xfff
2014-12-21 13:59:03 +00:00
rfid = ( tsbk >> 48 ) & 0xff
stid = ( tsbk >> 40 ) & 0xff
ch1 = ( tsbk >> 24 ) & 0xffff
table = ( ch1 >> 12 ) & 0xf
f1 = self . channel_id_to_frequency ( ch1 )
if f1 and table in self . freq_table :
self . adjacent [ f1 ] = ' rfid: %d stid: %d uplink: %f tbl: %d ' % ( rfid , stid , ( f1 + self . freq_table [ table ] [ ' offset ' ] ) / 1000000.0 , table )
2018-03-15 18:43:57 +00:00
self . adjacent_data [ f1 ] = { ' rfid ' : rfid , ' stid ' : stid , ' uplink ' : f1 + self . freq_table [ table ] [ ' offset ' ] , ' table ' : table , ' sysid ' : syid }
2014-12-21 13:59:03 +00:00
if self . debug > 10 :
2021-07-26 00:46:14 +00:00
sys . stderr . write ( ' tsbk3c adjacent: rfid %x stid %d ch1 %x ( %s ) sysid 0x %x \n ' % ( rfid , stid , ch1 , self . channel_id_to_string ( ch1 ) , syid ) )
2014-12-21 13:59:03 +00:00
if table in self . freq_table :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' tsbk3c : %s %s \n ' % ( self . freq_table [ table ] [ ' frequency ' ] , self . freq_table [ table ] [ ' step ' ] ) )
2021-07-26 00:46:14 +00:00
elif opcode == 0x20 : # ACK_RESP_FNE
aiv = ( tsbk >> 79 ) & 1
ex = ( tsbk >> 78 ) & 1
addl = ( tsbk >> 40 ) & 0xffffffff
wacn = None
sysid = None
srcaddr = None
if ex :
wacn = ( addl > 12 ) & 0xfffff
sysid = addl & 0xfff
else :
srcaddr = addl & 0xffffff
target = ( tsbk >> 16 ) & 0xffffff
d = { ' cc_event ' : ' ack_resp_fne ' , ' aiv ' : aiv , ' ex ' : ex , ' addl ' : addl , ' wacn ' : wacn , ' tsbk_sysid ' : sysid , ' source ' : self . mk_src_dict ( srcaddr ) , ' target ' : self . mk_src_dict ( target ) , ' opcode ' : opcode }
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' tsbk20 ack_resp_fne: aiv %d ex %d wacn %s sysid %s src %s \n ' % ( aiv , ex , wacn , sysid , srcaddr ) )
elif opcode == 0x27 : # DENY_RESP
aiv = ( tsbk >> 79 ) & 1
reason = ( tsbk >> 64 ) & 0xff
addl = ( tsbk >> 40 ) & 0xffffff
target = ( tsbk >> 16 ) & 0xffffff
d = { ' cc_event ' : ' deny_resp ' , ' aiv ' : aiv , ' reason ' : reason , ' additional ' : addl , ' target ' : self . mk_src_dict ( target ) , ' opcode ' : opcode }
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' tsbk27 deny_resp: aiv %d reason %02x additional %x target %d \n ' % ( aiv , reason , addl , target ) )
elif opcode == 0x28 : # grp_aff_rsp
lg = ( tsbk >> 79 ) & 0x01
gav = ( tsbk >> 72 ) & 0x03
aga = ( tsbk >> 56 ) & 0xffff
ga = ( tsbk >> 40 ) & 0xffff
ta = ( tsbk >> 16 ) & 0xffffff
d = { ' cc_event ' : ' grp_aff_resp ' , ' affiliation ' : [ ' local ' , ' global ' ] [ lg ] , ' group_aff_value ' : gav , ' announce_group ' : self . mk_tg_dict ( aga ) , ' group ' : self . mk_tg_dict ( ga ) , ' target ' : self . mk_src_dict ( ta ) , ' opcode ' : opcode }
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' tsbk28 grp_aff_resp: mfrid: 0x %x , gav: %d , aga: %d , ga: %d , ta: %d \n ' % ( mfrid , gav , aga , ga , ta ) )
elif opcode == 0x2a : # GRP_AFF_Q
target = ( tsbk >> 40 ) & 0xffffff
source = ( tsbk >> 16 ) & 0xffffff
d = { ' cc_event ' : ' grp_aff_q ' , ' source ' : self . mk_src_dict ( source ) , ' target ' : self . mk_src_dict ( target ) , ' opcode ' : opcode }
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' tsbk2a grp_aff_q: mfrid: 0x %x , target %d source %d \n ' % ( mfrid , target , source ) )
elif opcode == 0x2b : # LOC_REG_RESP
rv = ( tsbk >> 72 ) & 3
ga = ( tsbk >> 56 ) & 0xffff
rfss = ( tsbk >> 48 ) & 0xff
siteid = ( tsbk >> 40 ) & 0xff
target = ( tsbk >> 16 ) & 0xffffff
d = { ' cc_event ' : ' loc_reg_resp ' , ' rv ' : rv , ' rfss ' : rfss , ' siteid ' : siteid , ' group ' : self . mk_tg_dict ( ga ) , ' target ' : self . mk_src_dict ( target ) , ' opcode ' : opcode }
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' tsbk2b loc_reg_resp: mfrid: 0x %x , rv %d group %d rfss 0x %x siteid 0x %x target %d \n ' % ( mfrid , rv , ga , rfss , siteid , target ) )
elif opcode == 0x2c : # U_REG_RESP
rv = ( tsbk >> 76 ) & 1
sysid = ( tsbk >> 64 ) & 0xfff
target = ( tsbk >> 40 ) & 0xffffff
source = ( tsbk >> 16 ) & 0xffffff
d = { ' cc_event ' : ' u_reg_resp ' , ' rv ' : rv , ' tsbk_sysid ' : sysid , ' source ' : self . mk_src_dict ( source ) , ' target ' : self . mk_src_dict ( target ) , ' opcode ' : opcode }
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' tsbk2c u_reg_resp: mfrid: 0x %x , rv %d sysid %x target %d source %d \n ' % ( mfrid , rv , sysid , target , source ) )
elif opcode == 0x2d : # U_REG_CMD
target = ( tsbk >> 40 ) & 0xffffff
source = ( tsbk >> 16 ) & 0xffffff
d = { ' cc_event ' : ' u_reg_cmd ' , ' source ' : self . mk_src_dict ( source ) , ' target ' : self . mk_src_dict ( target ) , ' opcode ' : opcode }
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' tsbk2d u_reg_cmd: mfrid: 0x %x , target %d source %d \n ' % ( mfrid , target , source ) )
elif opcode == 0x2f : # U_DE_REG_ACK
wacn = ( tsbk >> 52 ) & 0xfffff
sysid = ( tsbk >> 40 ) & 0xfff
source = ( tsbk >> 16 ) & 0xffffff
d = { ' cc_event ' : ' u_de_reg_ack ' , ' wacn ' : wacn , ' tsbk_sysid ' : sysid , ' source ' : self . mk_src_dict ( source ) , ' opcode ' : opcode }
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' tsbk2f u_de_reg_ack: mfrid: 0x %x , wacn 0x %x sysid 0x %x source %d \n ' % ( mfrid , wacn , sysid , source ) )
elif opcode == 0x24 : # EXT_FNCT_CMD
efclass = ( tsbk >> 72 ) & 0xff
efoperand = ( tsbk >> 64 ) & 0xff
efargs = ( tsbk >> 40 ) & 0xffffff
target = ( tsbk >> 16 ) & 0xffffff
d = { ' cc_event ' : ' ext_fnct_cmd ' , ' mfrid ' : mfrid , ' efclass ' : efclass , ' efoperand ' : efoperand , ' efargs ' : self . mk_src_dict ( efargs ) , ' target ' : target , ' opcode ' : opcode }
self . post_event ( d )
if self . debug > 10 :
sys . stderr . write ( ' tsbk24 ext_fnct_cmd: efclass %d efoperand %d efargs %s sysid %s target %s \n ' % ( efclass , efoperand , efargs , sysid , target ) )
#else:
# sys.stderr.write('tsbk other %x\n' % opcode)
2013-12-10 00:47:21 +00:00
return updated
2014-12-20 00:16:15 +00:00
def hunt_cc ( self , curr_time ) :
2021-07-26 00:46:14 +00:00
# return True if a tune request for frequency=self.trunk_cc should be issued
HUNT_HOLD_TIME = 8.0
#if self.cc_timeouts < 6:
# return False
if self . last_tsbk + HUNT_HOLD_TIME > time . time ( ) :
2018-12-26 02:10:06 +00:00
return False
2021-07-27 00:49:41 +00:00
if self . last_voice_time + HUNT_HOLD_TIME > time . time ( ) :
return False
2021-07-26 00:46:14 +00:00
if time . time ( ) < self . next_hunt_cc :
return False
self . next_hunt_cc = time . time ( ) + HUNT_HOLD_TIME
2014-12-20 00:16:15 +00:00
self . cc_timeouts = 0
self . cc_list_index + = 1
if self . cc_list_index > = len ( self . cc_list ) :
self . cc_list_index = 0
self . trunk_cc = self . cc_list [ self . cc_list_index ]
2021-07-27 00:49:41 +00:00
sys . stderr . write ( ' %f %s : cycling to next trunk_cc: %s \n ' % ( curr_time , self . sysname , self . trunk_cc ) )
2018-12-26 02:10:06 +00:00
if self . trunk_cc != self . last_trunk_cc :
self . last_trunk_cc = self . trunk_cc
if self . debug > = 5 :
2021-07-26 00:46:14 +00:00
sys . stderr . write ( ' %f %s : control channel change: %f \n ' % ( curr_time , self . sysname , self . trunk_cc / 1000000.0 ) )
2018-12-26 02:10:06 +00:00
return True
2021-07-26 00:46:14 +00:00
return True
def frequency_change_params ( self , current_tgid , new_frequency , nac , new_slot , new_frequency_type , curr_time ) :
params = {
' freq ' : new_frequency ,
' tgid ' : current_tgid ,
' offset ' : self . offset ,
' tag ' : self . get_tag ( current_tgid ) ,
' nac ' : nac ,
' system ' : self . sysname ,
' center_frequency ' : self . center_frequency ,
' tdma ' : new_slot ,
' wacn ' : self . ns_wacn ,
' sysid ' : self . ns_syid ,
' srcaddr ' : self . current_srcaddr ,
' grpaddr ' : self . current_grpaddr ,
' alg ' : self . current_alg ,
' algid ' : self . current_algid ,
' channel_type ' : new_frequency_type ,
' keyid ' : self . current_keyid ,
' prio ' : self . get_prio ( current_tgid ) ,
' tag_color ' : self . get_tag_color ( current_tgid ) ,
' srcaddr_color ' : self . get_unit_id_color ( self . current_srcaddr ) ,
' srcaddr_tag ' : self . get_unit_id_tag ( self . current_srcaddr ) ,
' effective_time ' : curr_time }
return params
2014-12-20 00:16:15 +00:00
2013-12-10 00:47:21 +00:00
class rx_ctl ( object ) :
2021-07-26 00:46:14 +00:00
def __init__ ( self , debug = 0 , frequency_set = None , conf_file = None , logfile_workers = None , send_event = None ) :
2013-12-10 00:47:21 +00:00
class _states ( object ) :
ACQ = 0
CC = 1
TO_VC = 2
VC = 3
self . states = _states
2017-09-05 20:42:55 +00:00
self . current_state = self . states . CC
2013-12-10 00:47:21 +00:00
self . trunked_systems = { }
self . frequency_set = frequency_set
self . debug = debug
self . tgid_hold = None
self . tgid_hold_until = time . time ( )
2017-04-29 16:30:33 +00:00
self . hold_mode = False
2013-12-10 00:47:21 +00:00
self . TGID_HOLD_TIME = 2.0 # TODO: make more configurable
2015-10-07 20:08:29 +00:00
self . TGID_SKIP_TIME = 1.0 # TODO: make more configurable
2013-12-10 00:47:21 +00:00
self . current_nac = None
self . current_id = 0
2017-09-05 20:42:55 +00:00
self . current_tgid = None
self . current_slot = None
2013-12-10 00:47:21 +00:00
self . TSYS_HOLD_TIME = 3.0 # TODO: make more configurable
self . wait_until = time . time ( )
self . configs = { }
2017-04-25 23:59:35 +00:00
self . nacs = [ ]
2014-11-30 16:32:28 +00:00
self . logfile_workers = logfile_workers
self . active_talkgroups = { }
2014-12-20 00:16:15 +00:00
self . working_frequencies = { }
self . xor_cache = { }
self . last_garbage_collect = 0
2018-03-15 18:43:57 +00:00
self . last_command = { ' command ' : None , ' time ' : time . time ( ) }
2014-12-20 00:16:15 +00:00
if self . logfile_workers :
self . input_rate = self . logfile_workers [ 0 ] [ ' demod ' ] . input_rate
2018-12-26 02:10:06 +00:00
self . enabled_nacs = None
2021-02-14 16:56:45 +00:00
self . next_status_png = time . time ( )
2021-07-26 00:46:14 +00:00
self . send_event = send_event
self . status_msg = ' '
self . next_hunt_time = time . time ( )
2013-12-10 00:47:21 +00:00
if conf_file :
2014-01-03 01:31:26 +00:00
if conf_file . endswith ( ' .tsv ' ) :
self . build_config_tsv ( conf_file )
2018-04-03 19:19:57 +00:00
elif conf_file . endswith ( ' .json ' ) :
self . build_config_json ( conf_file )
2014-01-03 01:31:26 +00:00
else :
self . build_config ( conf_file )
2020-02-24 01:03:43 +00:00
self . nacs = [ int ( x ) for x in self . configs . keys ( ) ]
2018-12-26 02:10:06 +00:00
self . current_nac = self . find_next_tsys ( )
2013-12-10 00:47:21 +00:00
self . current_state = self . states . CC
2014-11-30 16:32:28 +00:00
tsys = self . trunked_systems [ self . current_nac ]
2014-12-20 00:16:15 +00:00
if self . logfile_workers and tsys . modulation == ' c4fm ' :
for worker in self . logfile_workers :
worker [ ' demod ' ] . connect_chain ( ' fsk4 ' )
2014-11-30 16:32:28 +00:00
self . set_frequency ( {
2014-12-21 13:59:03 +00:00
' freq ' : tsys . trunk_cc ,
2021-07-26 00:46:14 +00:00
' channel_type ' : ' cc ' ,
2014-12-21 13:59:03 +00:00
' tgid ' : None ,
' offset ' : tsys . offset ,
' tag ' : " " ,
' nac ' : self . current_nac ,
' system ' : tsys . sysname ,
' center_frequency ' : tsys . center_frequency ,
' tdma ' : None ,
' wacn ' : None ,
2021-07-26 00:46:14 +00:00
' sysid ' : None ,
' prio ' : 0 ,
' tag_color ' : None } )
2014-11-30 16:32:28 +00:00
2018-04-03 19:19:57 +00:00
def build_config_json ( self , conf_file ) :
d = json . loads ( open ( conf_file ) . read ( ) )
chans = [ x for x in d [ ' channels ' ] if x [ ' active ' ] and x [ ' trunked ' ] ]
self . configs = { chan [ ' nac ' ] : { ' cclist ' : chan [ ' cclist ' ] ,
' offset ' : 0 ,
2018-12-26 02:10:06 +00:00
' blacklist ' : { int ( tgid ) : None for tgid in chan [ ' blacklist ' ] } ,
' whitelist ' : { int ( tgid ) : None for tgid in chan [ ' whitelist ' ] } ,
2018-04-03 19:19:57 +00:00
' sysname ' : chan [ ' name ' ] ,
' center_frequency ' : chan [ ' frequency ' ] ,
' modulation ' : chan [ ' demod_type ' ] ,
2018-12-26 02:10:06 +00:00
' tgid_map ' : { int ( tgid ) : chan [ ' tgids ' ] [ tgid ] for tgid in chan [ ' tgids ' ] . keys ( ) } }
2018-04-03 19:19:57 +00:00
for chan in chans }
for nac in self . configs . keys ( ) :
self . add_trunked_system ( nac )
2013-12-10 00:47:21 +00:00
def set_frequency ( self , params ) :
frequency = params [ ' freq ' ]
if frequency and self . frequency_set :
self . frequency_set ( params )
2018-12-26 02:10:06 +00:00
def enable_status ( self , s ) :
if self . debug > = 10 :
sys . stderr . write ( ' rx_ctl: enable_status: %s \n ' % s )
nacs = s . split ( ' , ' )
if s and len ( nacs ) :
nacs = [ int ( x ) for x in nacs ]
else :
sys . stderr . write ( ' cannot disable all NACs - request ignored \n ' )
return
self . enabled_nacs = nacs
2013-12-10 00:47:21 +00:00
def add_trunked_system ( self , nac ) :
assert nac not in self . trunked_systems # duplicate nac not allowed
cfg = None
if nac in self . configs :
cfg = self . configs [ nac ]
2021-07-26 00:46:14 +00:00
self . trunked_systems [ nac ] = trunked_system ( debug = self . debug , config = cfg , send_event = self . send_event , nac = nac )
2013-12-10 00:47:21 +00:00
2014-01-03 01:31:26 +00:00
def build_config_tsv ( self , tsv_filename ) :
2018-03-15 18:43:57 +00:00
self . setup_config ( load_tsv ( tsv_filename ) )
2014-01-03 01:31:26 +00:00
2013-12-10 00:47:21 +00:00
def build_config ( self , config_filename ) :
import ConfigParser
config = ConfigParser . ConfigParser ( )
config . read ( config_filename )
configs = { }
for section in config . sections ( ) :
2014-12-21 13:59:03 +00:00
nac = int ( config . get ( section , ' nac ' ) , 0 ) # nac required
assert nac != 0 # nac=0 not allowed
assert nac not in configs # duplicate nac not allowed
2013-12-10 00:47:21 +00:00
configs [ nac ] = { }
for option in config . options ( section ) :
configs [ nac ] [ option ] = config . get ( section , option ) . lower ( )
configs [ nac ] [ ' sysname ' ] = section
2014-01-03 01:31:26 +00:00
self . setup_config ( configs )
2013-12-10 00:47:21 +00:00
2021-07-26 00:46:14 +00:00
def reload_tags ( self , nac ) :
if nac not in self . trunked_systems . keys ( ) :
return
tsys = self . trunked_systems [ nac ]
tgid_tags_file = self . configs [ nac ] [ ' tgid_tags_file ' ]
new_reg = id_registry ( )
read_tags_file ( tgid_tags_file , new_reg )
tsys . tgid_map = new_reg
sys . stderr . write ( ' reloaded %s nac 0x %x \n ' % ( tgid_tags_file , nac ) )
unit_id_tags_file = self . configs [ nac ] [ ' unit_id_tags_file ' ]
if unit_id_tags_file is None :
return
new_reg = id_registry ( )
read_tags_file ( unit_id_tags_file , new_reg )
tsys . unit_id_map = new_reg
sys . stderr . write ( ' reloaded %s nac 0x %x \n ' % ( unit_id_tags_file , nac ) )
def add_default_config ( self , nac , cclist = [ ] , offset = 0 , whitelist = None , blacklist = { } , tgid_map = None , unit_id_map = None , sysname = None , center_frequency = None , modulation = ' cqpsk ' ) :
2017-04-25 23:59:35 +00:00
if nac in self . configs . keys ( ) :
return
if nac not in self . trunked_systems . keys ( ) :
return
tsys = self . trunked_systems [ nac ]
if not tsys . rfss_chan :
return
if not tsys . ns_chan :
return
if tsys . ns_wacn < 0 :
return
if tsys . ns_syid < 0 :
return
if not sysname :
sysname = ' NAC 0x %x ' % nac
if not cclist :
cclist = [ tsys . rfss_chan ]
cclist . extend ( tsys . secondary . keys ( ) )
tsys . cc_list = cclist
2021-07-26 00:46:14 +00:00
self . configs [ nac ] = { ' cclist ' : cclist , ' offset ' : offset , ' whitelist ' : whitelist , ' blacklist ' : blacklist , ' tgid_map ' : tgid_map , ' unit_id_map ' : unit_id_map , ' sysname ' : sysname , ' center_frequency ' : center_frequency , ' modulation ' : modulation }
2017-04-25 23:59:35 +00:00
self . current_nac = nac
self . current_state = self . states . CC
if nac not in self . nacs :
self . nacs . append ( nac )
2014-01-03 01:31:26 +00:00
def setup_config ( self , configs ) :
2018-03-15 18:43:57 +00:00
self . configs = make_config ( configs )
for nac in self . configs . keys ( ) :
2013-12-10 00:47:21 +00:00
self . add_trunked_system ( nac )
def find_next_tsys ( self ) :
2018-12-26 02:10:06 +00:00
wrap = 0
while True :
self . current_id + = 1
if self . current_id > = len ( self . nacs ) :
if wrap :
break
self . current_id = 0
wrap = 1
if self . enabled_nacs is not None and self . nacs [ self . current_id ] not in self . enabled_nacs :
continue
return self . nacs [ self . current_id ]
return self . nacs [ 0 ] ## should not occur
2013-12-10 00:47:21 +00:00
2017-04-25 23:59:35 +00:00
def to_json ( self ) :
2018-03-15 18:43:57 +00:00
current_time = time . time ( )
2017-04-25 23:59:35 +00:00
d = { ' json_type ' : ' trunk_update ' }
for nac in self . trunked_systems . keys ( ) :
2021-07-26 00:46:14 +00:00
d [ nac ] = self . trunked_systems [ nac ] . to_dict ( )
if nac in self . configs . keys ( ) and ' tgid_tags_file ' in self . configs [ nac ] :
d [ nac ] [ ' tgid_tags_file ' ] = self . configs [ nac ] [ ' tgid_tags_file ' ]
if nac in self . configs . keys ( ) and ' unit_id_tags_file ' in self . configs [ nac ] :
d [ nac ] [ ' unit_id_tags_file ' ] = self . configs [ nac ] [ ' unit_id_tags_file ' ]
2018-03-15 18:43:57 +00:00
d [ ' data ' ] = { ' last_command ' : self . last_command [ ' command ' ] ,
' last_command_time ' : int ( self . last_command [ ' time ' ] - current_time ) ,
' tgid_hold ' : self . tgid_hold ,
' tgid_hold_until ' : int ( self . tgid_hold_until - current_time ) ,
' hold_mode ' : self . hold_mode }
2021-07-26 00:46:14 +00:00
d [ ' time ' ] = current_time
2017-04-25 23:59:35 +00:00
return json . dumps ( d )
2021-02-14 16:56:45 +00:00
def make_status_png ( self ) :
PNG_UPDATE_INTERVAL = 1.0
output_file = ' ../www/images/status.png '
tmp_output_file = ' ../www/images/tmp-status.png '
if time . time ( ) < self . next_status_png :
return
self . next_status_png = time . time ( ) + PNG_UPDATE_INTERVAL
status_str = ' OP25-hls hacks (c) Copyright 2020, 2021, KA1RBI \n '
2021-07-26 00:46:14 +00:00
status_str + = self . status_msg
2021-02-14 16:56:45 +00:00
status_str + = self . to_string ( )
status = status_str . split ( ' \n ' )
status = [ s for s in status if not s . startswith ( ' tbl-id ' ) ]
create_image ( status , imgfile = tmp_output_file , bgcolor = " #c0c0c0 " , windowsize = ( 640 , 480 ) )
2021-07-26 00:46:14 +00:00
if not os . access ( tmp_output_file , os . R_OK ) :
return
2021-02-14 16:56:45 +00:00
os . rename ( tmp_output_file , output_file )
2021-07-26 00:46:14 +00:00
def frequency_tracking_expire ( self ) :
for nac in self . trunked_systems . keys ( ) :
self . trunked_systems [ nac ] . frequency_tracking_expire ( )
def in_voice_state ( self ) :
rc = self . current_state == self . states . TO_VC or self . current_state == self . states . VC
return rc
2018-12-26 02:10:06 +00:00
def dump_tgids ( self ) :
for nac in self . trunked_systems . keys ( ) :
self . trunked_systems [ nac ] . dump_tgids ( )
2013-12-10 00:47:21 +00:00
def to_string ( self ) :
s = ' '
for nac in self . trunked_systems :
s + = ' \n ====== NAC 0x %x ====== %s ====== \n ' % ( nac , self . trunked_systems [ nac ] . sysname )
s + = self . trunked_systems [ nac ] . to_string ( )
return s
def process_qmsg ( self , msg ) :
2021-07-26 00:46:14 +00:00
self . frequency_tracking_expire ( )
if self . send_event is not None :
self . send_event ( None ) # periodically sends general status info
2020-02-24 01:03:43 +00:00
mtype = msg . type ( )
2013-12-10 00:47:21 +00:00
updated = 0
curr_time = time . time ( )
2021-07-26 00:46:14 +00:00
msgtext = msg . to_string ( )
aa55 = get_ordinals ( msgtext [ : 2 ] )
2022-01-04 22:17:29 +00:00
if mtype > = 0 or mtype in [ - 1 , - 3 , - 5 , - 6 , - 7 ] :
2021-07-26 00:46:14 +00:00
assert aa55 == 0xaa55
msgq_id = get_ordinals ( msgtext [ 2 : 4 ] )
msgtext = msgtext [ 4 : ]
else :
assert aa55 != 0xaa55
msgq_id = None
2022-01-04 22:17:29 +00:00
if mtype == - 7 : # P25 phase 1 link control
nac = get_ordinals ( msgtext [ : 2 ] )
msgtext = msgtext [ 2 : ]
msg = get_ordinals ( msgtext )
if self . debug > 10 :
sys . stderr . write ( " %f process_qmsg: LC info: %x \n " % ( time . time ( ) , msg ) )
opcode = ( msg >> 64 ) & 0x3f
if opcode == 40 : # rfss
lra = ( msg >> 56 ) & 0xff
ch1 = ( msg >> 40 ) & 0xffff
rfss = ( msg >> 32 ) & 0xff
stid = ( msg >> 24 ) & 0xff
ch2 = ( msg >> 8 ) & 0xffff
ssclass = msg & 0xff
if self . debug > 10 :
sys . stderr . write ( " %f p25p1 LC: LCRSBX: nac 0x %x lra 0x %x ch1 %x rfss 0x %x stid 0x %x ch2 %x ssclass 0x %x \n " % ( time . time ( ) , nac , lra , ch1 , rfss , stid , ch2 , ssclass ) )
elif opcode == 39 : # adjacent
lra = ( msg >> 56 ) & 0xff
ch1 = ( msg >> 40 ) & 0xffff
rfss = ( msg >> 32 ) & 0xff
stid = ( msg >> 24 ) & 0xff
ch2 = ( msg >> 8 ) & 0xffff
flags = msg & 0xff
if self . debug > 10 :
sys . stderr . write ( " %f p25p1 LC: LCASBX: nac 0x %x lra 0x %x ch1 %x rfss 0x %x stid 0x %x ch2 %x flags 0x %x \n " % ( time . time ( ) , nac , lra , ch1 , rfss , stid , ch2 , flags ) )
else :
if self . debug > 0 :
sys . stderr . write ( " %f p25p1 LC: unhandled LCO: nac 0x %x 0x %x \n " % ( time . time ( ) , nac , opcode ) )
return
elif mtype == - 3 : # P25 call signalling data
2018-12-26 02:10:06 +00:00
if self . debug > 10 :
2021-07-26 00:46:14 +00:00
sys . stderr . write ( " %f process_qmsg: P25 info: %s \n " % ( time . time ( ) , msgtext ) )
js = json . loads ( msgtext )
2018-12-26 02:10:06 +00:00
nac = js [ ' nac ' ]
if nac != self . current_nac :
2021-07-26 00:46:14 +00:00
sys . stderr . write ( ' warning: nac mismatch: nac %x current_nac %x js %s \n ' % ( nac , self . current_nac , msgtext ) )
# return
if nac not in self . trunked_systems . keys ( ) :
2018-12-26 02:10:06 +00:00
return
tsys = self . trunked_systems [ nac ]
2021-07-27 00:49:41 +00:00
if self . current_state != self . states . CC :
tsys . last_voice_time = curr_time
2018-12-26 02:10:06 +00:00
if ' srcaddr ' in js . keys ( ) :
tsys . current_srcaddr = js [ ' srcaddr ' ]
if ' grpaddr ' in js . keys ( ) :
tsys . current_grpaddr = js [ ' grpaddr ' ]
if ' algid ' in js . keys ( ) :
tsys . current_algid = js [ ' algid ' ]
if ' alg ' in js . keys ( ) :
tsys . current_alg = js [ ' alg ' ]
if ' keyid ' in js . keys ( ) :
tsys . current_keyid = js [ ' keyid ' ]
return
2020-02-24 01:03:43 +00:00
elif mtype == - 2 : # request from gui
2021-07-26 00:46:14 +00:00
cmd = msgtext
2013-12-10 00:47:21 +00:00
if self . debug > 10 :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' process_qmsg: command: %s \n ' % cmd )
2020-12-06 23:48:10 +00:00
self . update_state ( cmd , curr_time , int ( msg . arg1 ( ) ) ) # self.update_state(cmd, curr_time)
2013-12-10 00:47:21 +00:00
return
2020-02-24 01:03:43 +00:00
elif mtype == - 1 : # timeout
2021-08-11 19:41:49 +00:00
if self . debug > 0 :
sys . stderr . write ( ' %f process_data_unit timeout, channel %s \n ' % ( time . time ( ) , msgq_id ) )
2013-12-10 00:47:21 +00:00
self . update_state ( ' timeout ' , curr_time )
2014-12-20 00:16:15 +00:00
if self . logfile_workers :
self . logging_scheduler ( curr_time )
2013-12-10 00:47:21 +00:00
return
2021-07-26 00:46:14 +00:00
elif mtype == - 6 : # p25 tdma cc
# nac is always 1st two bytes
nac = get_ordinals ( msgtext [ : 2 ] )
msgtext = msgtext [ 2 : ]
2022-01-04 20:29:01 +00:00
if self . debug > 1 :
sys . stderr . write ( ' tdma_cc message nac 0x %x text 0x %x len %d \n ' % ( nac , get_ordinals ( msgtext ) , len ( msgtext ) ) )
2021-07-26 00:46:14 +00:00
if nac not in self . trunked_systems . keys ( ) :
sys . stderr . write ( ' tdma_cc received from unexpected NAC 0x %x \n ' % nac )
return
tsys = self . trunked_systems [ nac ]
tsys . decode_tdma_cc ( msgtext [ 1 : ] )
return
2020-02-24 01:03:43 +00:00
elif mtype < 0 :
sys . stderr . write ( ' unknown message type %d \n ' % ( mtype ) )
2014-08-13 21:02:23 +00:00
return
2021-07-26 00:46:14 +00:00
s = msgtext
2013-12-10 00:47:21 +00:00
# nac is always 1st two bytes
2020-02-24 01:03:43 +00:00
nac = get_ordinals ( s [ : 2 ] )
2014-08-13 21:02:23 +00:00
if nac == 0xffff :
2021-07-26 00:46:14 +00:00
sys . stderr . write ( ' received invalid nac 0xffff, mtype %d msgq_id %s \n ' % ( mtype , msgq_id ) )
return
2013-12-10 00:47:21 +00:00
s = s [ 2 : ]
if self . debug > 10 :
2020-02-24 01:03:43 +00:00
sys . stderr . write ( ' nac %x type %d at %f state %d len %d \n ' % ( nac , mtype , time . time ( ) , self . current_state , len ( s ) ) )
if ( mtype == 7 or mtype == 12 ) and nac not in self . trunked_systems :
2013-12-10 00:47:21 +00:00
if not self . configs :
# TODO: allow whitelist/blacklist rather than blind automatic-add
self . add_trunked_system ( nac )
else :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( " %f NAC %x not configured \n " % ( time . time ( ) , nac ) )
2013-12-10 00:47:21 +00:00
return
2021-07-26 00:46:14 +00:00
if nac not in self . trunked_systems . keys ( ) :
sys . stderr . write ( ' received unknown nac 0x %x , mtype %d len configs %d msgq_id %d \n ' % ( nac , mtype , len ( self . configs . keys ( ) ) , msgq_id ) )
return
tsys = self . trunked_systems [ nac ]
if mtype == 0 or mtype == 5 or mtype == 10 : # HDU or LDU1 or LDU2 i.e. voice
2021-07-27 00:49:41 +00:00
if self . current_state != self . states . CC :
tsys . last_voice_time = curr_time
2021-07-26 00:46:14 +00:00
elif mtype == 7 : # trunk: TSBK
2020-02-24 01:03:43 +00:00
t = get_ordinals ( s )
2021-07-26 00:46:14 +00:00
updated + = tsys . decode_tsbk ( t )
2020-02-24 01:03:43 +00:00
elif mtype == 12 : # trunk: MBT
2018-12-26 02:10:06 +00:00
s1 = s [ : 10 ] # header without crc
s2 = s [ 12 : ]
2020-02-24 01:03:43 +00:00
header = get_ordinals ( s1 )
mbt_data = get_ordinals ( s2 )
2018-12-26 02:10:06 +00:00
fmt = ( header >> 72 ) & 0x1f
sap = ( header >> 64 ) & 0x3f
src = ( header >> 48 ) & 0xffffff
if fmt != 0x17 : # only Extended Format MBT presently supported
return
2013-12-10 00:47:21 +00:00
opcode = ( header >> 16 ) & 0x3f
if self . debug > 10 :
2020-02-24 01:03:43 +00:00
sys . stderr . write ( ' type %d at %f state %d len %d / %d opcode %x [ %x / %x ] \n ' % ( mtype , time . time ( ) , self . current_state , len ( s1 ) , len ( s2 ) , opcode , header , mbt_data ) )
2021-07-26 00:46:14 +00:00
updated + = tsys . decode_mbt_data ( opcode , src , header << 16 , mbt_data << 32 )
2013-12-10 00:47:21 +00:00
2021-02-14 16:56:45 +00:00
self . make_status_png ( )
2021-07-26 00:46:14 +00:00
#if nac != self.current_nac:
# if self.debug > 10: # this is occasionally expected if cycling between different tsys
# cnac = self.current_nac
# if cnac is None:
# cnac = 0
# sys.stderr.write("%f received NAC %x does not match expected NAC %x\n" % (time.time(), nac, cnac))
# return
2013-12-10 00:47:21 +00:00
2014-11-30 16:32:28 +00:00
if self . logfile_workers :
2014-12-20 00:16:15 +00:00
self . logging_scheduler ( curr_time )
2014-11-30 16:32:28 +00:00
return
2013-12-10 00:47:21 +00:00
if updated :
self . update_state ( ' update ' , curr_time )
else :
2020-02-24 01:03:43 +00:00
self . update_state ( ' duid %d ' % mtype , curr_time )
2013-12-10 00:47:21 +00:00
2014-11-30 16:32:28 +00:00
def find_available_worker ( self ) :
for worker in self . logfile_workers :
if not worker [ ' active ' ] :
worker [ ' active ' ] = True
return worker
return None
2014-12-20 00:16:15 +00:00
def free_frequency ( self , frequency , curr_time ) :
assert not self . working_frequencies [ frequency ] [ ' tgids ' ]
self . working_frequencies [ frequency ] [ ' worker ' ] [ ' demod ' ] . set_relative_frequency ( 0 )
self . working_frequencies [ frequency ] [ ' worker ' ] [ ' active ' ] = False
self . working_frequencies . pop ( frequency )
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' %f release worker frequency %d \n ' % ( curr_time , frequency ) )
2014-12-20 00:16:15 +00:00
def free_talkgroup ( self , frequency , tgid , curr_time ) :
decoder = self . working_frequencies [ frequency ] [ ' worker ' ] [ ' decoder ' ]
tdma_slot = self . working_frequencies [ frequency ] [ ' tgids ' ] [ tgid ] [ ' tdma_slot ' ]
index = tdma_slot
if tdma_slot is None :
index = 0
self . working_frequencies [ frequency ] [ ' tgids ' ] . pop ( tgid )
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' %f release tgid %d frequency %d \n ' % ( curr_time , tgid , frequency ) )
2014-12-20 00:16:15 +00:00
def logging_scheduler ( self , curr_time ) :
2014-11-30 16:32:28 +00:00
tsys = self . trunked_systems [ self . current_nac ]
2014-12-20 00:16:15 +00:00
for tgid in tsys . get_updated_talkgroups ( curr_time ) :
frequency = tsys . talkgroups [ tgid ] [ ' frequency ' ]
tdma_slot = tsys . talkgroups [ tgid ] [ ' tdma_slot ' ]
# see if this tgid active on any other freq(s)
other_freqs = [ f for f in self . working_frequencies if f != frequency and tgid in self . working_frequencies [ f ] [ ' tgids ' ] ]
if other_freqs :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' %f tgid %d slot %s frequency %d found on other frequencies %s \n ' % ( curr_time , tgid , tdma_slot , frequency , ' , ' . join ( [ ' %s ' % f for f in other_freqs ] ) ) )
2014-12-20 00:16:15 +00:00
for f in other_freqs :
self . free_talkgroup ( f , tgid , curr_time )
if not self . working_frequencies [ f ] [ ' tgids ' ] :
self . free_frequency ( f , curr_time )
diff = abs ( tsys . center_frequency - frequency )
if diff > self . input_rate / 2 :
2018-12-26 02:10:06 +00:00
#sys.stderr.write('%f request for frequency %d tgid %d failed, offset %d exceeds maximum %d\n' % (curr_time, frequency, tgid, diff, self.input_rate/2))
2014-11-30 16:32:28 +00:00
continue
2014-12-20 00:16:15 +00:00
update = True
if frequency in self . working_frequencies :
tgids = self . working_frequencies [ frequency ] [ ' tgids ' ]
if tgid in tgids :
if tgids [ tgid ] [ ' tdma_slot ' ] == tdma_slot :
update = False
else :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' %f slot switch %s was %s tgid %d frequency %d \n ' % ( curr_time , tdma_slot , tgids [ tgid ] [ ' tdma_slot ' ] , tgid , frequency ) )
2014-12-20 00:16:15 +00:00
worker = self . working_frequencies [ frequency ] [ ' worker ' ]
else :
#active_tdma_slots = [tgids[tg]['tdma_slot'] for tg in tgids]
2018-12-26 02:10:06 +00:00
sys . stderr . write ( " %f new tgid %d slot %s arriving on already active frequency %d \n " % ( curr_time , tgid , tdma_slot , frequency ) )
2018-01-08 16:30:15 +00:00
previous_tgid = [ id for id in tgids if tgids [ id ] [ ' tdma_slot ' ] == tdma_slot ]
assert len ( previous_tgid ) == 1 ## check for logic error
self . free_talkgroup ( frequency , previous_tgid [ 0 ] , curr_time )
2014-12-20 00:16:15 +00:00
worker = self . working_frequencies [ frequency ] [ ' worker ' ]
else :
worker = self . find_available_worker ( )
if worker is None :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' *** error, no free demodulators, freq %d tgid %d \n ' % ( frequency , tgid ) )
2014-12-20 00:16:15 +00:00
continue
self . working_frequencies [ frequency ] = { ' tgids ' : { } , ' worker ' : worker }
worker [ ' demod ' ] . set_relative_frequency ( tsys . center_frequency - frequency )
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' %f starting worker frequency %d tg %d slot %s \n ' % ( curr_time , frequency , tgid , tdma_slot ) )
2014-12-20 00:16:15 +00:00
self . working_frequencies [ frequency ] [ ' tgids ' ] [ tgid ] = { ' updated ' : curr_time , ' tdma_slot ' : tdma_slot }
if not update :
2014-11-30 16:32:28 +00:00
continue
2014-12-20 00:16:15 +00:00
filename = ' tgid- %d - %f .wav ' % ( tgid , curr_time )
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' %f update frequency %d tg %d slot %s file %s \n ' % ( curr_time , frequency , tgid , tdma_slot , filename ) )
2014-12-20 00:16:15 +00:00
# set demod speed, decoder slot, output file name
2014-11-30 16:32:28 +00:00
demod = worker [ ' demod ' ]
decoder = worker [ ' decoder ' ]
symbol_rate = 4800
2014-12-20 00:16:15 +00:00
if tdma_slot is None :
index = 0
else :
index = tdma_slot
2014-11-30 16:32:28 +00:00
symbol_rate = 6000
2014-12-20 00:16:15 +00:00
xorhash = ' %x %x %x ' % ( self . current_nac , tsys . ns_syid , tsys . ns_wacn )
if xorhash not in self . xor_cache :
self . xor_cache [ xorhash ] = lfsr . p25p2_lfsr ( self . current_nac , tsys . ns_syid , tsys . ns_wacn ) . xor_chars
decoder . set_xormask ( self . xor_cache [ xorhash ] , xorhash , index = index )
2018-12-26 02:10:06 +00:00
decoder . set_nac ( self . current_nac , index = index )
2014-11-30 16:32:28 +00:00
demod . set_omega ( symbol_rate )
2014-12-20 00:16:15 +00:00
decoder . set_output ( filename , index = index )
2014-11-30 16:32:28 +00:00
2014-12-20 00:16:15 +00:00
# garbage collection
if self . last_garbage_collect + 1 > curr_time :
return
self . last_garbage_collect = curr_time
gc_frequencies = [ ]
gc_tgids = [ ]
for frequency in self . working_frequencies :
tgids = self . working_frequencies [ frequency ] [ ' tgids ' ]
inactive_tgids = [ [ frequency , tgid ] for tgid in tgids if tgids [ tgid ] [ ' updated ' ] + self . TGID_HOLD_TIME < curr_time ]
if len ( inactive_tgids ) == len ( tgids ) :
gc_frequencies + = [ frequency ]
gc_tgids + = inactive_tgids
for frequency , tgid in gc_tgids : # expire talkgroups
self . free_talkgroup ( frequency , tgid , curr_time )
for frequency in gc_frequencies : # expire working frequencies
self . free_frequency ( frequency , curr_time )
2014-11-30 16:32:28 +00:00
2020-12-06 23:48:10 +00:00
def update_state ( self , command , curr_time , cmd_data = 0 ) : # def update_state(self, command, curr_time):
2013-12-10 00:47:21 +00:00
if not self . configs :
return # run in "manual mode" if no conf
2020-02-24 01:03:43 +00:00
if type ( command ) is not str and isinstance ( command , bytes ) :
command = command . decode ( )
2013-12-10 00:47:21 +00:00
nac = self . current_nac
tsys = self . trunked_systems [ nac ]
new_frequency = None
new_tgid = None
new_state = None
new_nac = None
2014-08-13 21:02:23 +00:00
new_slot = None
2021-07-26 00:46:14 +00:00
new_frequency_type = None
2013-12-10 00:47:21 +00:00
2017-09-05 20:42:55 +00:00
if command == ' timeout ' :
2014-12-20 00:16:15 +00:00
if self . current_state == self . states . CC :
2017-09-05 20:42:55 +00:00
if self . debug > 0 :
2021-07-27 00:49:41 +00:00
sys . stderr . write ( " %f control channel timeout, current CC %d \n " % ( time . time ( ) , tsys . trunk_cc ) )
2014-12-20 00:16:15 +00:00
tsys . cc_timeouts + = 1
2021-07-26 00:46:14 +00:00
elif self . current_state != self . states . CC and tsys . last_voice_time + 1.0 < curr_time :
2021-07-27 00:49:41 +00:00
if self . debug > 0 :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( " %f voice timeout \n " % time . time ( ) )
if self . hold_mode is False :
self . current_tgid = None
2021-07-26 00:46:14 +00:00
tsys . current_srcaddr = 0
2013-12-10 00:47:21 +00:00
new_state = self . states . CC
new_frequency = tsys . trunk_cc
2021-07-26 00:46:14 +00:00
new_frequency_type = ' cc '
2013-12-10 00:47:21 +00:00
elif command == ' update ' :
if self . current_state == self . states . CC :
desired_tgid = None
2018-12-26 02:10:06 +00:00
if ( self . tgid_hold is not None ) and ( self . tgid_hold_until > curr_time ) :
if self . debug > 1 :
sys . stderr . write ( " %f hold active tg( %s ) \n " % ( time . time ( ) , self . tgid_hold ) )
2013-12-10 00:47:21 +00:00
desired_tgid = self . tgid_hold
2018-12-26 02:10:06 +00:00
elif ( self . tgid_hold is not None ) and ( self . hold_mode == False ) :
self . tgid_hold = None
new_frequency , new_tgid , tdma_slot , srcaddr = tsys . find_talkgroup ( curr_time , tgid = desired_tgid , hold = self . hold_mode )
2013-12-10 00:47:21 +00:00
if new_frequency :
2021-07-26 00:46:14 +00:00
new_frequency_type = ' vc '
2017-09-05 20:42:55 +00:00
if self . debug > 0 :
2018-12-26 02:10:06 +00:00
tslot = tdma_slot if tdma_slot is not None else ' - '
sys . stderr . write ( " %f voice update: tg( %s ), freq( %s ), slot( %s ), prio( %d ) \n " % ( time . time ( ) , new_tgid , new_frequency , tslot , tsys . get_prio ( new_tgid ) ) )
2013-12-10 00:47:21 +00:00
new_state = self . states . TO_VC
self . current_tgid = new_tgid
2018-12-26 02:10:06 +00:00
tsys . current_srcaddr = srcaddr
self . tgid_hold = new_tgid
self . tgid_hold_until = max ( curr_time + self . TGID_HOLD_TIME , self . tgid_hold_until )
self . wait_until = curr_time + self . TSYS_HOLD_TIME
2014-08-13 21:02:23 +00:00
new_slot = tdma_slot
2021-07-26 00:46:14 +00:00
elif 0 : # # # # # else: # check for priority tgid preemption
2018-12-26 02:10:06 +00:00
new_frequency , new_tgid , tdma_slot , srcaddr = tsys . find_talkgroup ( tsys . talkgroups [ self . current_tgid ] [ ' time ' ] , tgid = self . current_tgid , hold = self . hold_mode )
if new_tgid != self . current_tgid :
if self . debug > 0 :
tslot = tdma_slot if tdma_slot is not None else ' - '
sys . stderr . write ( " %f voice preempt: tg( %s ), freq( %s ), slot( %s ), prio( %d ) \n " % ( time . time ( ) , new_tgid , new_frequency , tslot , tsys . get_prio ( new_tgid ) ) )
2021-07-26 00:46:14 +00:00
new_frequency_type = ' vc '
2018-12-26 02:10:06 +00:00
new_state = self . states . TO_VC
self . current_tgid = new_tgid
tsys . current_srcaddr = srcaddr
self . tgid_hold = new_tgid
self . tgid_hold_until = max ( curr_time + self . TGID_HOLD_TIME , self . tgid_hold_until )
self . wait_until = curr_time + self . TSYS_HOLD_TIME
new_slot = tdma_slot
else :
new_frequency = None
elif command == ' duid3 ' or command == ' tdma_duid3 ' : # termination, no channel release
2017-09-05 20:42:55 +00:00
if self . current_state != self . states . CC :
self . tgid_hold = self . current_tgid
self . tgid_hold_until = max ( curr_time + self . TGID_HOLD_TIME , self . tgid_hold_until )
self . wait_until = curr_time + self . TSYS_HOLD_TIME
2018-12-26 02:10:06 +00:00
elif command == ' duid15 ' or command == ' tdma_duid15 ' : # termination with channel release
2013-12-10 00:47:21 +00:00
if self . current_state != self . states . CC :
2018-12-26 02:10:06 +00:00
if self . debug > 1 :
sys . stderr . write ( " %f %s , tg( %d ) \n " % ( time . time ( ) , command , self . current_tgid ) )
tsys . current_srcaddr = 0
tsys . current_grpaddr = 0
self . wait_until = curr_time + self . TSYS_HOLD_TIME
self . tgid_hold = self . current_tgid
self . tgid_hold_until = max ( curr_time + self . TGID_HOLD_TIME , self . tgid_hold_until )
if self . hold_mode is False :
self . current_tgid = None
2013-12-10 00:47:21 +00:00
new_state = self . states . CC
new_frequency = tsys . trunk_cc
2021-07-26 00:46:14 +00:00
new_frequency_type = ' cc '
2014-08-18 16:52:04 +00:00
elif command == ' duid0 ' or command == ' duid5 ' or command == ' duid10 ' or command == ' tdma_duid5 ' :
2017-09-05 20:42:55 +00:00
if self . current_state == self . states . TO_VC :
2013-12-10 00:47:21 +00:00
new_state = self . states . VC
self . tgid_hold = self . current_tgid
self . tgid_hold_until = max ( curr_time + self . TGID_HOLD_TIME , self . tgid_hold_until )
self . wait_until = curr_time + self . TSYS_HOLD_TIME
2018-12-26 02:10:06 +00:00
elif command == ' duid7 ' or command == ' duid12 ' : # tsbk/pdu should never arrive here...
2013-12-10 00:47:21 +00:00
pass
2017-04-29 16:30:33 +00:00
elif command == ' hold ' :
2018-03-15 18:43:57 +00:00
self . last_command = { ' command ' : command , ' time ' : curr_time }
2021-07-26 00:46:14 +00:00
if self . hold_mode :
new_hold_mode = False
else :
new_hold_mode = True
if new_hold_mode and ( cmd_data is None or cmd_data < 1 ) :
if self . debug > 0 :
sys . stderr . write ( ' %f set hold failed, unable to determine TGID \n ' % ( time . time ( ) ) )
new_hold_mode = False
if new_hold_mode is False : # unset hold
self . current_tgid = None
self . tgid_hold = None
self . tgid_hold_until = curr_time
self . hold_mode = new_hold_mode
else : # set hold
2020-12-06 23:48:10 +00:00
self . tgid_hold = cmd_data
2017-04-29 16:30:33 +00:00
self . tgid_hold_until = curr_time + 86400 * 10000
2021-07-26 00:46:14 +00:00
self . hold_mode = new_hold_mode
if self . debug > 0 :
sys . stderr . write ( ' %f set hold tg( %s ) until %f \n ' % ( time . time ( ) , self . tgid_hold , self . tgid_hold_until ) )
2020-12-06 23:48:10 +00:00
if self . debug > 0 :
2021-07-26 00:46:14 +00:00
sys . stderr . write ( ' %f set hold tg( %s ) until %f mode %s current tgid %s \n ' % ( time . time ( ) , self . tgid_hold , self . tgid_hold_until , self . hold_mode , self . current_tgid ) )
2020-12-06 23:48:10 +00:00
if self . current_tgid != self . tgid_hold :
self . current_tgid = self . tgid_hold
self . current_srcaddr = 0
self . current_grpaddr = 0
self . current_alg = " "
self . current_algid = 128
self . current_keyid = 0
new_state = self . states . CC
new_frequency = tsys . trunk_cc
2021-07-26 00:46:14 +00:00
new_frequency_type = ' cc '
2013-12-10 00:47:21 +00:00
elif command == ' set_hold ' :
2018-03-15 18:43:57 +00:00
self . last_command = { ' command ' : command , ' time ' : curr_time }
2013-12-10 00:47:21 +00:00
if self . current_tgid :
self . tgid_hold = self . current_tgid
self . tgid_hold_until = curr_time + 86400 * 10000
2017-04-29 16:30:33 +00:00
self . hold_mode = True
2022-01-06 20:15:22 +00:00
if self . debug > 0 :
sys . stderr . write ( ' %f set hold until %f \n ' % ( time . time ( ) , self . tgid_hold_until ) )
2013-12-10 00:47:21 +00:00
elif command == ' unset_hold ' :
2018-03-15 18:43:57 +00:00
self . last_command = { ' command ' : command , ' time ' : curr_time }
2013-12-10 00:47:21 +00:00
if self . current_tgid :
2018-12-26 02:10:06 +00:00
if self . debug > 0 :
sys . stderr . write ( ' %f clear hold tg( %s ) \n ' % ( time . time ( ) , self . tgid_hold ) )
2013-12-10 00:47:21 +00:00
self . current_tgid = None
self . tgid_hold = None
self . tgid_hold_until = curr_time
2017-04-29 16:30:33 +00:00
self . hold_mode = False
2015-10-02 13:40:41 +00:00
elif command == ' skip ' or command == ' lockout ' :
2018-03-15 18:43:57 +00:00
self . last_command = { ' command ' : command , ' time ' : curr_time }
2013-12-10 00:47:21 +00:00
if self . current_tgid :
2015-10-02 13:40:41 +00:00
end_time = None
if command == ' skip ' :
end_time = curr_time + self . TGID_SKIP_TIME
tsys . add_blacklist ( self . current_tgid , end_time = end_time )
2013-12-10 00:47:21 +00:00
self . current_tgid = None
self . tgid_hold = None
self . tgid_hold_until = curr_time
2017-04-29 16:30:33 +00:00
self . hold_mode = False
2021-07-26 00:46:14 +00:00
tsys . current_srcaddr = 0
2013-12-10 00:47:21 +00:00
if self . current_state != self . states . CC :
new_state = self . states . CC
new_frequency = tsys . trunk_cc
2021-07-26 00:46:14 +00:00
new_frequency_type = ' cc '
2013-12-10 00:47:21 +00:00
else :
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' update_state: unknown command: %s \n ' % command )
2013-12-10 00:47:21 +00:00
assert 0 == 1
2021-07-26 00:46:14 +00:00
if new_frequency is not None and tsys . trunk_cc != tsys . rfss_chan :
sys . stderr . write ( ' warning: trunk control channel frequency %f does not match rfss frequency %f \n ' % ( tsys . trunk_cc / 1000000.0 , tsys . rfss_chan / 1000000.0 ) )
2018-12-26 02:10:06 +00:00
hunted_cc = tsys . hunt_cc ( curr_time )
2014-12-20 00:16:15 +00:00
2018-12-26 02:10:06 +00:00
if self . enabled_nacs is not None and self . current_nac not in self . enabled_nacs :
tsys . current_srcaddr = 0
tsys . current_grpaddr = 0
new_nac = self . find_next_tsys ( )
new_state = self . states . CC
elif self . current_state != self . states . CC and self . tgid_hold_until < = curr_time and self . hold_mode is False and new_state is None :
if self . debug > 1 :
sys . stderr . write ( " %f release tg( %s ) \n " % ( time . time ( ) , self . current_tgid ) )
self . tgid_hold = None
self . current_tgid = None
tsys . current_srcaddr = 0
tsys . current_grpaddr = 0
new_state = self . states . CC
new_frequency = tsys . trunk_cc
2021-07-26 00:46:14 +00:00
new_frequency_type = ' cc '
2018-12-26 02:10:06 +00:00
elif self . wait_until < = curr_time and self . tgid_hold_until < = curr_time and self . hold_mode is False and new_state is None :
2013-12-10 00:47:21 +00:00
self . wait_until = curr_time + self . TSYS_HOLD_TIME
2018-12-26 02:10:06 +00:00
tsys . current_srcaddr = 0
tsys . current_grpaddr = 0
2013-12-10 00:47:21 +00:00
new_nac = self . find_next_tsys ( )
2014-02-07 23:25:04 +00:00
new_state = self . states . CC
2013-12-10 00:47:21 +00:00
2018-12-26 02:10:06 +00:00
if new_nac is not None :
2013-12-10 00:47:21 +00:00
nac = self . current_nac = new_nac
tsys = self . trunked_systems [ nac ]
new_frequency = tsys . trunk_cc
2021-07-26 00:46:14 +00:00
new_frequency_type = ' cc '
2018-12-26 02:10:06 +00:00
tsys . current_srcaddr = 0
tsys . current_grpaddr = 0
2013-12-10 00:47:21 +00:00
self . current_tgid = None
2018-12-26 02:10:06 +00:00
if new_frequency is not None :
2021-07-26 00:46:14 +00:00
params = tsys . frequency_change_params ( self . current_tgid , new_frequency , nac , new_slot , new_frequency_type , curr_time )
self . status_msg = ' F %f TG %s %s at %s \n ' % ( params [ ' freq ' ] / 1000000.0 , params [ ' tgid ' ] , params [ ' tag ' ] , time . asctime ( ) )
2021-02-14 16:56:45 +00:00
self . set_frequency ( params )
2018-12-26 02:10:06 +00:00
if new_state is not None :
2013-12-10 00:47:21 +00:00
self . current_state = new_state
2021-07-26 00:46:14 +00:00
def parallel_hunt_cc ( self ) :
curr_time = time . time ( )
if curr_time < self . next_hunt_time :
return
self . next_hunt_time = curr_time + 1.0
for nac in self . trunked_systems . keys ( ) :
tsys = self . trunked_systems [ nac ]
rc = tsys . hunt_cc ( curr_time )
if not rc :
continue
tgid = None
freq = tsys . trunk_cc
new_slot = None
new_frequency_type = ' cc '
params = tsys . frequency_change_params ( tgid , freq , nac , new_slot , new_frequency_type , curr_time )
self . status_msg = ' F %f TG %s %s at %s \n ' % ( params [ ' freq ' ] / 1000000.0 , params [ ' tgid ' ] , params [ ' tag ' ] , time . asctime ( ) )
self . set_frequency ( params )
2013-12-10 00:47:21 +00:00
def main ( ) :
2014-12-21 13:59:03 +00:00
q = 0x3a000012ae01013348704a54
rc = crc16 ( q , 12 )
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' should be zero: %x \n ' % rc )
2014-12-21 13:59:03 +00:00
assert rc == 0
2013-12-10 00:47:21 +00:00
2014-12-21 13:59:03 +00:00
q = 0x3a001012ae01013348704a54
rc = crc16 ( q , 12 )
2018-12-26 02:10:06 +00:00
sys . stderr . write ( ' should be nonzero: %x \n ' % rc )
2014-12-21 13:59:03 +00:00
assert rc != 0
2013-12-10 00:47:21 +00:00
2014-12-21 13:59:03 +00:00
t = trunked_system ( debug = 255 )
q = 0x3a000012ae0101334870
t . decode_tsbk ( q )
2013-12-10 00:47:21 +00:00
2014-12-21 13:59:03 +00:00
q = 0x02900031210020018e7c
t . decode_tsbk ( q )
2013-12-10 00:47:21 +00:00
if __name__ == ' __main__ ' :
2014-12-21 13:59:03 +00:00
main ( )