diff --git a/AMI.py b/AMI.py new file mode 100644 index 0000000..d6d4086 --- /dev/null +++ b/AMI.py @@ -0,0 +1,77 @@ + +import sys + +from time import time + +from twisted.internet import reactor,task +from twisted.internet.defer import Deferred +from twisted.internet.protocol import ClientFactory,ClientFactory,Protocol +from twisted.protocols.basic import LineReceiver + +class AMI(): + def __init__(self,host,port,username,secret,nodenum): + self._AMIClient = self.AMIClient + self.host = host + self.port = port + self.username = username.encode('utf-8') + self.secret = secret.encode('utf-8') + self.nodenum = str(nodenum) + + def send_command(self,command): + self._AMIClient.command = command.encode('utf-8') + self._AMIClient.username = self.username + self._AMIClient.secret = self.secret + self._AMIClient.nodenum = self.nodenum.encode('utf-8') + self.command = command + + self.CF = reactor.connectTCP(self.host, self.port, self.AMIClientFactory(self._AMIClient)) + + def closeConnection(self): + self.transport.loseConnection() + + class AMIClient(LineReceiver): + + delimiter = b'\r\n' + + def connectionMade(self): + self.sendLine(b'Action: login') + self.sendLine(b''.join([b'Username: ',self.username])) + self.sendLine(b''.join([b'Secret: ',self.secret])) + self.sendLine(self.delimiter) + + def lineReceived(self,line): + print(line) + if line == b'Asterisk Call Manager/1.0': + return + + if line == b'Response: Success': + self.sendLine(b'Action: command') + #print(b''.join([b'Command: ',b'rpt cmd ',self.nodenum,b' ',self.command])) + self.sendLine(b''.join([b'Command: ',b'rpt cmd ',self.nodenum,b' ',self.command])) + #self.sendLine(b'Command: ' + b'rpt cmd 29177 ilink 3 2001') + self.sendLine(self.delimiter) + self.transport.loseConnection() + + + + class AMIClientFactory(ClientFactory): + def __init__(self,AMIClient): + #self.command = command + self.done = Deferred() + self.protocol = AMIClient + #self.protocol.command = command + + def clientConnectionFailed(self, connector, reason): + ClientFactory.clientConnectionLost(self, connector, reason) + + def clientConnectionLost(self, connector, reason): + ClientFactory.clientConnectionLost(self, connector, reason) + + +if __name__ == '__main__': + + + a = AMI(sys.argv[1],int(sys.argv[2]),'admin','llcgi',29177) + #AMIOBJ.AMIClientFactory(AMIOBJ.AMIClient,'rpt cmd 29177 ilink 3 2001') + a.send_command(sys.argv[3]) + reactor.run() diff --git a/Audio/en_GB/all-star-link-mode.ambe b/Audio/en_GB/all-star-link-mode.ambe new file mode 100644 index 0000000..043e5ad Binary files /dev/null and b/Audio/en_GB/all-star-link-mode.ambe differ diff --git a/Audio/en_GB/ondemand/9992.ambe b/Audio/en_GB/ondemand/9992.ambe new file mode 100644 index 0000000..d539d9c Binary files /dev/null and b/Audio/en_GB/ondemand/9992.ambe differ diff --git a/Audio/en_GB/ondemand/9993.ambe b/Audio/en_GB/ondemand/9993.ambe new file mode 100644 index 0000000..6407eb1 Binary files /dev/null and b/Audio/en_GB/ondemand/9993.ambe differ diff --git a/Audio/en_GB/tone.ambe b/Audio/en_GB/tone.ambe new file mode 100644 index 0000000..65f6fa8 --- /dev/null +++ b/Audio/en_GB/tone.ambe @@ -0,0 +1 @@ +̚B}" D D D D DΊ̈Ί̈ȼ D.+yۑUxũH7[4IDPyTg!ܨX#+x \ No newline at end of file diff --git a/bridge_master.py b/bridge_master.py index ec7bc6a..c9e7280 100644 --- a/bridge_master.py +++ b/bridge_master.py @@ -80,6 +80,7 @@ import re from binascii import b2a_hex as ahex +from AMI import AMI ##from hmac import new as hmac_new, compare_digest ##from hashlib import sha256, hash @@ -1828,7 +1829,8 @@ class routerHBP(HBSYSTEM): 'lastSeq': False, 'lastData': False, 'packets': 0, - 'crcs': set() + 'crcs': set(), + '_allStarMode': False }, 2: { 'RX_START': time(), @@ -1858,7 +1860,8 @@ class routerHBP(HBSYSTEM): 'lastSeq': False, 'lastData': False, 'packets': 0, - 'crcs': set() + 'crcs': set(), + '_allStarMode': False } } @@ -2116,6 +2119,10 @@ class routerHBP(HBSYSTEM): #Add system to SUB_MAP SUB_MAP[_rf_src] = (self._system,_slot,pkt_time) + def resetallStarMode(): + self.STATUS[_slot]['_allStarMode'] = False + logger.info('(%s) Reset all star mode -> dial mode',self._system) + #Rewrite GPS Data comming in as a group call to a unit call #if (_call_type == 'group' or _call_type == 'vcsbk') and _int_dst_id == 900999: #_bits = header(_slot,'unit',_bits) @@ -2164,7 +2171,7 @@ class routerHBP(HBSYSTEM): for system in systems: if system == self._system: continue - #We only want to send data calls to individual IDs via OpenBridge + #We only want to send data calls to individual IDs via FreeBridge (not OpenBridge) if CONFIG['SYSTEMS'][system]['MODE'] == 'OPENBRIDGE' and CONFIG['SYSTEMS'][system]['VER'] > 1 and (_int_dst_id >= 1000000): self.sendDataToOBP(system,_data,dmrpkt,pkt_time,_stream_id,_dst_id,_peer_id,_rf_src,_bits,_slot) @@ -2226,10 +2233,41 @@ class routerHBP(HBSYSTEM): else: logger.info('(%s) UNIT Data not bridged to HBP on slot %s - target busy: %s DST_ID: %s',self._system,_d_slot,_d_system,_int_dst_id) - + #Handle AMI private calls + if _call_type == 'unit' and not _data_call and self.STATUS[_slot]['_allStarMode'] and CONFIG['ALLSTAR']['ENABLED']: + if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): + logger.info('(%s) AMI: Private call from %s to %s',self._system, int_id(_rf_src), _int_dst_id) + + + if (_frame_type == HBPF_DATA_SYNC) and (_dtype_vseq == HBPF_SLT_VTERM) and (self.STATUS[_slot]['RX_TYPE'] != HBPF_SLT_VTERM): + + if _int_dst_id == 4000: + logger.info('(%s) AMI: Private call from %s to %s (Disconnect)',self._system, int_id(_rf_src), _int_dst_id) + AMIOBJ.send_command('ilink 6 0') + elif _int_dst_id == 5000: + logger.info('(%s) AMI: Private call from %s to %s (Status)',self._system, int_id(_rf_src), _int_dst_id) + AMIOBJ.send_command('ilink 5 0') + else: + logger.info('(%s) AMI: Private call from %s to %s (Link)',self._system, int_id(_rf_src), _int_dst_id) + AMIOBJ.send_command('ilink 6 0') + AMIOBJ.send_command('ilink 3 ' + str(_int_dst_id)) + + # Mark status variables for use later + self.STATUS[_slot]['RX_PEER'] = _peer_id + self.STATUS[_slot]['RX_SEQ'] = _seq + self.STATUS[_slot]['RX_RFS'] = _rf_src + self.STATUS[_slot]['RX_TYPE'] = _dtype_vseq + self.STATUS[_slot]['RX_TGID'] = _dst_id + self.STATUS[_slot]['RX_TIME'] = pkt_time + self.STATUS[_slot]['RX_STREAM_ID'] = _stream_id + self.STATUS[_slot]['VOICE_STREAM'] = _voice_call + + self.STATUS[_slot]['packets'] = self.STATUS[_slot]['packets'] +1 + + #Handle private voice calls (for reflectors) - if _call_type == 'unit' and not _data_call: + elif _call_type == 'unit' and not _data_call and not self.STATUS[_slot]['_allStarMode']: if (_stream_id != self.STATUS[_slot]['RX_STREAM_ID']): self.STATUS[_slot]['packets'] = 0 @@ -2238,7 +2276,7 @@ class routerHBP(HBSYSTEM): self.STATUS[_slot]['_stopTgAnnounce'] = False logger.info('(%s) Reflector: Private call from %s to %s',self._system, int_id(_rf_src), _int_dst_id) - if _int_dst_id >= 5 and _int_dst_id != 9 and _int_dst_id <= 999999: + if _int_dst_id >= 5 and _int_dst_id != 8 and _int_dst_id != 9 and _int_dst_id <= 999999: _bridgename = '#'+ str(_int_dst_id) if _bridgename not in BRIDGES and not (_int_dst_id >= 4000 and _int_dst_id <= 5000) and not (_int_dst_id >=9991 and _int_dst_id <= 9999): logger.info('(%s) [A] Reflector for TG %s does not exist. Creating as User Activated. Timeout: %s',self._system, _int_dst_id,CONFIG['SYSTEMS'][self._system]['DEFAULT_UA_TIMER']) @@ -2304,6 +2342,15 @@ class routerHBP(HBSYSTEM): _say.append(words[_lang]['busy']) _say.append(words[_lang]['silence']) self.STATUS[_slot]['_stopTgAnnounce'] = True + + #Allstar mode switch + if CONFIG['ALLSTAR']['ENABLED'] and _int_dst_id == 8: + logger.info('(%s) Reflector: voice called - TG 8 AllStar"', self._system) + _say.append(words[_lang]['all-star-link-mode']) + _say.append(words[_lang]['silence']) + self.STATUS[_slot]['_stopTgAnnounce'] = True + self.STATUS[_slot]['_allStarMode'] = True + reactor.callLater(30,resetallStarMode) #If disconnection called if _int_dst_id == 4000: @@ -2375,7 +2422,7 @@ class routerHBP(HBSYSTEM): self.STATUS[_slot]['VOICE_STREAM'] = _voice_call self.STATUS[_slot]['packets'] = self.STATUS[_slot]['packets'] +1 - + #Handle group calls if _call_type == 'group' or _call_type == 'vcsbk': @@ -2695,17 +2742,17 @@ if __name__ == '__main__': #If MySQL is enabled, read master config from MySQL too if CONFIG['MYSQL']['USE_MYSQL'] == True: - logger.debug('(MYSQL) MySQL config enabled') + logger.info('(MYSQL) MySQL config enabled') SQLCONFIG = {} sql = useMYSQL(CONFIG['MYSQL']['SERVER'], CONFIG['MYSQL']['USER'], CONFIG['MYSQL']['PASS'], CONFIG['MYSQL']['DB'],CONFIG['MYSQL']['TABLE'],logger) #Run it once immediately if sql.con(): - logger.debug('(MYSQL) reading config from database') + logger.info('(MYSQL) reading config from database') try: SQLCONFIG = sql.getConfig() #Add MySQL config data to config dict except: - logger.debug('(MYSQL) problem with SQL query, aborting') + logger.warning('(MYSQL) problem with SQL query, aborting') sql.close() logger.debug('(MYSQL) building ACLs') # Build ACLs @@ -2716,8 +2763,13 @@ if __name__ == '__main__': CONFIG['SYSTEMS'].update(SQLCONFIG) else: - logger.debug('(MYSQL) problem connecting to SQL server, aborting') + logger.warning('(MYSQL) problem connecting to SQL server, aborting') + if CONFIG['ALLSTAR']['ENABLED']: + logger.info('(AMI) Setting up AMI: Server: %s, Port: %s, User: %s, Pass: %s, Node: %s',CONFIG['ALLSTAR']['SERVER'],CONFIG['ALLSTAR']['PORT'],CONFIG['ALLSTAR']['USER'],CONFIG['ALLSTAR']['PASS'],CONFIG['ALLSTAR']['NODE']) + + AMIOBJ = AMI(CONFIG['ALLSTAR']['SERVER'],CONFIG['ALLSTAR']['PORT'],CONFIG['ALLSTAR']['USER'],CONFIG['ALLSTAR']['PASS'],CONFIG['ALLSTAR']['NODE']) + # Set up the signal handler def sig_handler(_signal, _frame): diff --git a/config.py b/config.py index 4d0d402..f0ac779 100755 --- a/config.py +++ b/config.py @@ -129,6 +129,7 @@ def build_config(_config_file): CONFIG['ALIASES'] = {} CONFIG['SYSTEMS'] = {} CONFIG['MYSQL'] = {} + CONFIG['ALLSTAR'] = {} try: for section in config.sections(): @@ -194,6 +195,17 @@ def build_config(_config_file): 'TABLE': config.get(section, 'TABLE') }) + elif section == 'ALLSTAR': + CONFIG['ALLSTAR'].update({ + 'ENABLED': config.getboolean(section, 'ENABLED'), + 'USER': config.get(section, 'USER'), + 'PASS': config.get(section, 'PASS'), + 'SERVER': gethostbyname(config.get(section, 'SERVER')), + 'PORT': config.getint(section,'PORT'), + 'NODE' : config.getint(section,'NODE') + }) + + elif config.getboolean(section, 'ENABLED'): if config.get(section, 'MODE') == 'PEER': diff --git a/i8n_voice_map.py b/i8n_voice_map.py index 401d2c1..b1654cf 100644 --- a/i8n_voice_map.py +++ b/i8n_voice_map.py @@ -109,13 +109,15 @@ voiceMap = { 'Z': 'zulu', 'to': 'silence', 'notlinked': 'not-linked', - 'linkedto': 'linked-to' + 'linkedto': 'linked-to', + 'allstar-link-mode': 'alpha' }, 'en_US': { 'to': '2', 'freedmr': 'silence', - 'this-is': 'silence' + 'this-is': 'silence', + 'allstar-link-mode': 'alpha' }, 'es_ES': { @@ -156,7 +158,8 @@ voiceMap = { 'Z': 'zulu', 'to': 'silence', 'notlinked': 'not-linked', - 'linkedto': 'linked-to' + 'linkedto': 'linked-to', + 'allstar-link-mode': 'alfa' }, 'es_ES_2': { '1': 'one', @@ -196,7 +199,8 @@ voiceMap = { 'Z': 'zulu', 'to': 'silence', 'notlinked': 'not-linked', - 'linkedto': 'linked-to' + 'linkedto': 'linked-to', + 'allstar-link-mode': 'alpha' }, 'fr_FR': { @@ -228,7 +232,8 @@ voiceMap = { 'Z': 'zulu', 'to': 'silence', 'notlinked': 'not-linked', - 'linkedto': 'linked-to' + 'linkedto': 'linked-to', + 'allstar-link-mode': 'alpha' }, 'pt_PT': { @@ -260,7 +265,8 @@ voiceMap = { 'Z': 'zulu', 'to': 'silence', 'notlinked': 'not-linked', - 'linkedto': 'linked-to' + 'linkedto': 'linked-to', + 'allstar-link-mode': 'alpha' }, 'el_GR': { @@ -292,55 +298,63 @@ voiceMap = { 'Z': 'zulu', 'to': 'silence', 'notlinked': 'not-linked', - 'linkedto': 'linked-to' + 'allstar-link-mode': 'alpha' }, 'de_DE': { 'to': 'silence', + 'allstar-link-mode': 'A' }, 'dk_DK': { 'to': 'silence', 'freedmr': 'silence', - 'this-is': 'silence' + 'this-is': 'silence', + 'allstar-link-mode': 'A' }, 'it_IT': { 'to': 'silence', 'freedmr': 'silence', - 'this-is': 'silence' + 'this-is': 'silence', + 'allstar-link-mode': 'A' }, 'no_NO': { 'to': 'silence', 'freedmr': 'silence', - 'this-is': 'silence' + 'this-is': 'silence', + 'allstar-link-mode': 'A' }, 'pl_PL': { 'to': 'silence', 'freedmr': 'silence', - 'this-is': 'silence' + 'this-is': 'silence', + 'allstar-link-mode': 'A' }, 'se_SE': { 'to': 'silence', 'freedmr': 'silence', - 'this-is': 'silence' + 'this-is': 'silence', + 'allstar-link-mode': 'A' }, 'CW': { 'to': 'silence', 'freedmr': 'silence', 'this-is': 'silence', - 'linkedto': 'silence' + 'linkedto': 'silence', + 'allstar-link-mode': 'T' }, 'th_TH': { 'to': 'silence', + 'allstar-link-mode': 'A' },