|
|
|
|
@ -75,6 +75,7 @@ from utils import load_json, save_json
|
|
|
|
|
|
|
|
|
|
#Read voices
|
|
|
|
|
from read_ambe import readAMBE
|
|
|
|
|
from tts_engine import ensure_tts_ambe
|
|
|
|
|
#Remap some words for certain languages
|
|
|
|
|
from i8n_voice_map import voiceMap
|
|
|
|
|
|
|
|
|
|
@ -877,6 +878,9 @@ def ident():
|
|
|
|
|
_announcement_last_hour = {1: -1, 2: -1, 3: -1, 4: -1}
|
|
|
|
|
_announcement_running = {1: False, 2: False, 3: False, 4: False}
|
|
|
|
|
|
|
|
|
|
_tts_last_hour = {1: -1, 2: -1, 3: -1, 4: -1}
|
|
|
|
|
_tts_running = {1: False, 2: False, 3: False, 4: False}
|
|
|
|
|
|
|
|
|
|
_FRAME_INTERVAL = 0.054
|
|
|
|
|
|
|
|
|
|
_RECORDING_MAX_FRAMES = 2750
|
|
|
|
|
@ -1105,7 +1109,168 @@ def scheduledAnnouncement(_ann_num=1):
|
|
|
|
|
logger.info('(%s) Broadcasting %s packets to %s systems simultaneously: %s', _label, len(_pkts), len(_targets), _sys_names)
|
|
|
|
|
_announcement_running[_ann_num] = True
|
|
|
|
|
reactor.callLater(1.0, _announcementSendBroadcast, _targets, _pkts, 0, _source_id, _dst_id, _tg, _timeslot, _ann_num)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def scheduledTTSAnnouncement(_tts_num=1):
|
|
|
|
|
global _tts_last_hour, _tts_running
|
|
|
|
|
|
|
|
|
|
_prefix = 'TTS_ANNOUNCEMENT{}'.format(_tts_num)
|
|
|
|
|
_label = 'TTS-{}'.format(_tts_num)
|
|
|
|
|
|
|
|
|
|
if not CONFIG['GLOBAL'].get('{}_ENABLED'.format(_prefix), False):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if _tts_running[_tts_num]:
|
|
|
|
|
logger.debug('(%s) Previous TTS announcement still running, skipping', _label)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
_mode = CONFIG['GLOBAL']['{}_MODE'.format(_prefix)]
|
|
|
|
|
|
|
|
|
|
if _mode == 'hourly':
|
|
|
|
|
_now = datetime.now()
|
|
|
|
|
if _now.minute != 0:
|
|
|
|
|
return
|
|
|
|
|
if _now.hour == _tts_last_hour[_tts_num]:
|
|
|
|
|
return
|
|
|
|
|
_tts_last_hour[_tts_num] = _now.hour
|
|
|
|
|
|
|
|
|
|
_file = CONFIG['GLOBAL']['{}_FILE'.format(_prefix)]
|
|
|
|
|
_tg = CONFIG['GLOBAL']['{}_TG'.format(_prefix)]
|
|
|
|
|
_timeslot = CONFIG['GLOBAL']['{}_TIMESLOT'.format(_prefix)]
|
|
|
|
|
_lang = CONFIG['GLOBAL']['{}_LANGUAGE'.format(_prefix)]
|
|
|
|
|
_slot_index = 2 if _timeslot == 2 else 1
|
|
|
|
|
_dst_id = bytes_3(_tg)
|
|
|
|
|
_source_id = bytes_3(5000)
|
|
|
|
|
_peer_id = CONFIG['GLOBAL']['SERVER_ID']
|
|
|
|
|
|
|
|
|
|
_ambe_path = ensure_tts_ambe(CONFIG, _tts_num)
|
|
|
|
|
|
|
|
|
|
if not _ambe_path:
|
|
|
|
|
logger.warning('(%s) No AMBE file available for TTS announcement %s', _label, _file)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
logger.info('(%s) Playing TTS file: %s to TG %s TS%s (mode: %s, lang: %s)', _label, _file, _tg, _timeslot, _mode, _lang)
|
|
|
|
|
|
|
|
|
|
_say = []
|
|
|
|
|
try:
|
|
|
|
|
_relative_path = _ambe_path
|
|
|
|
|
if _relative_path.startswith('./Audio/'):
|
|
|
|
|
_relative_path = _relative_path[len('./Audio'):]
|
|
|
|
|
elif _relative_path.startswith('Audio/'):
|
|
|
|
|
_relative_path = '/' + _relative_path[len('Audio'):]
|
|
|
|
|
_say.append(AMBEobj.readSingleFile(_relative_path))
|
|
|
|
|
except IOError:
|
|
|
|
|
logger.warning('(%s) Cannot read AMBE file: %s', _label, _ambe_path)
|
|
|
|
|
return
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error('(%s) Error reading AMBE file: %s', _label, e)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
logger.debug('(%s) AMBE file loaded, %s words', _label, len(_say))
|
|
|
|
|
|
|
|
|
|
_excluded = ['ECHO', 'D-APRS']
|
|
|
|
|
_targets = []
|
|
|
|
|
|
|
|
|
|
for _sn in list(systems.keys()):
|
|
|
|
|
if _sn in _excluded or any(_sn.startswith(ex + '-') for ex in _excluded):
|
|
|
|
|
continue
|
|
|
|
|
if _sn not in CONFIG['SYSTEMS']:
|
|
|
|
|
continue
|
|
|
|
|
if CONFIG['SYSTEMS'][_sn]['MODE'] != 'MASTER':
|
|
|
|
|
continue
|
|
|
|
|
if 'PEERS' not in CONFIG['SYSTEMS'][_sn]:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
_has_peers = False
|
|
|
|
|
try:
|
|
|
|
|
for _pid in CONFIG['SYSTEMS'][_sn]['PEERS']:
|
|
|
|
|
if CONFIG['SYSTEMS'][_sn]['PEERS'][_pid]['CALLSIGN']:
|
|
|
|
|
_has_peers = True
|
|
|
|
|
break
|
|
|
|
|
except (KeyError, TypeError, RuntimeError):
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if not _has_peers:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if _sn not in systems:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
_slot = systems[_sn].STATUS[_slot_index]
|
|
|
|
|
if (_slot['RX_TYPE'] != HBPF_SLT_VTERM) or (_slot['TX_TYPE'] != HBPF_SLT_VTERM):
|
|
|
|
|
logger.debug('(%s) System %s busy, skipping', _label, _sn)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
_targets.append({
|
|
|
|
|
'sys_obj': systems[_sn],
|
|
|
|
|
'name': _sn,
|
|
|
|
|
'slot': _slot
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if not _targets:
|
|
|
|
|
logger.info('(%s) No systems with connected peers to send to', _label)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
_pkts = list(pkt_gen(_source_id, _dst_id, _peer_id, _slot_index - 1, _say))
|
|
|
|
|
_sys_names = ', '.join([t['name'] for t in _targets[:5]])
|
|
|
|
|
if len(_targets) > 5:
|
|
|
|
|
_sys_names += ', ... +{}'.format(len(_targets) - 5)
|
|
|
|
|
logger.info('(%s) Broadcasting %s packets to %s systems simultaneously: %s', _label, len(_pkts), len(_targets), _sys_names)
|
|
|
|
|
_tts_running[_tts_num] = True
|
|
|
|
|
reactor.callLater(1.0, _ttsSendBroadcast, _targets, _pkts, 0, _source_id, _dst_id, _tg, _timeslot, _tts_num)
|
|
|
|
|
|
|
|
|
|
def _ttsSendBroadcast(_targets, _pkts, _pkt_idx, _source_id, _dst_id, _tg, _ts, _tts_num=1, _next_time=None):
|
|
|
|
|
global _tts_running
|
|
|
|
|
|
|
|
|
|
_label = 'TTS-{}'.format(_tts_num)
|
|
|
|
|
|
|
|
|
|
if _pkt_idx >= len(_pkts):
|
|
|
|
|
for _t in _targets:
|
|
|
|
|
try:
|
|
|
|
|
for _sid in list(_t['sys_obj'].STATUS.keys()):
|
|
|
|
|
if _sid not in (1, 2):
|
|
|
|
|
del _t['sys_obj'].STATUS[_sid]
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
_tts_running[_tts_num] = False
|
|
|
|
|
logger.info('(%s) Broadcast complete: %s packets sent to %s systems', _label, len(_pkts), len(_targets))
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
_now = time()
|
|
|
|
|
pkt = _pkts[_pkt_idx]
|
|
|
|
|
_stream_id = pkt[16:20]
|
|
|
|
|
|
|
|
|
|
for _t in _targets:
|
|
|
|
|
try:
|
|
|
|
|
_sys_obj = _t['sys_obj']
|
|
|
|
|
_slot = _t['slot']
|
|
|
|
|
if _stream_id not in _sys_obj.STATUS:
|
|
|
|
|
_sys_obj.STATUS[_stream_id] = {
|
|
|
|
|
'START': _now,
|
|
|
|
|
'CONTENTION':False,
|
|
|
|
|
'RFS': _source_id,
|
|
|
|
|
'TGID': _dst_id,
|
|
|
|
|
'LAST': _now
|
|
|
|
|
}
|
|
|
|
|
_slot['TX_TGID'] = _dst_id
|
|
|
|
|
else:
|
|
|
|
|
_sys_obj.STATUS[_stream_id]['LAST'] = _now
|
|
|
|
|
_slot['TX_TIME'] = _now
|
|
|
|
|
_sys_obj.send_system(pkt)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error('(%s) Error sending packet %s to %s: %s', _label, _pkt_idx, _t['name'], e)
|
|
|
|
|
|
|
|
|
|
_elapsed = time() - _now
|
|
|
|
|
if _next_time is None:
|
|
|
|
|
_next_time = _now + _FRAME_INTERVAL
|
|
|
|
|
else:
|
|
|
|
|
_next_time = _next_time + _FRAME_INTERVAL
|
|
|
|
|
_delay = max(0.001, _next_time - time())
|
|
|
|
|
|
|
|
|
|
if _pkt_idx < 3:
|
|
|
|
|
logger.debug('(%s) Packet %s/%s broadcast to %s systems (proc: %.1fms, delay: %.1fms)', _label, _pkt_idx + 1, len(_pkts), len(_targets), _elapsed * 1000, _delay * 1000)
|
|
|
|
|
|
|
|
|
|
reactor.callLater(_delay, _ttsSendBroadcast, _targets, _pkts, _pkt_idx + 1, _source_id, _dst_id, _tg, _ts, _tts_num, _next_time)
|
|
|
|
|
|
|
|
|
|
def bridge_reset():
|
|
|
|
|
logger.debug('(BRIDGERESET) Running bridge resetter')
|
|
|
|
|
for _system in CONFIG['SYSTEMS']:
|
|
|
|
|
@ -3384,7 +3549,29 @@ if __name__ == '__main__':
|
|
|
|
|
CONFIG['GLOBAL']['{}_LANGUAGE'.format(_prefix)])
|
|
|
|
|
if _ann_mode == 'interval':
|
|
|
|
|
logger.info('(%s) Interval: every %s seconds', _label, _ann_check_interval)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for _tts_num in range(1, 5):
|
|
|
|
|
_prefix = 'TTS_ANNOUNCEMENT{}'.format(_tts_num)
|
|
|
|
|
_label = 'TTS-{}'.format(_tts_num)
|
|
|
|
|
if CONFIG['GLOBAL'].get('{}_ENABLED'.format(_prefix), False):
|
|
|
|
|
_tts_mode = CONFIG['GLOBAL']['{}_MODE'.format(_prefix)]
|
|
|
|
|
if _tts_mode == 'hourly':
|
|
|
|
|
_tts_check_interval = 30
|
|
|
|
|
else:
|
|
|
|
|
_tts_check_interval = CONFIG['GLOBAL']['{}_INTERVAL'.format(_prefix)]
|
|
|
|
|
_tts_task = task.LoopingCall(scheduledTTSAnnouncement, _tts_num)
|
|
|
|
|
_tts_def = _tts_task.start(_tts_check_interval, now=False)
|
|
|
|
|
_tts_def.addErrback(loopingErrHandle)
|
|
|
|
|
logger.info('(%s) Scheduled TTS announcements enabled - mode: %s, file: %s, TG: %s, TS: %s, lang: %s',
|
|
|
|
|
_label,
|
|
|
|
|
_tts_mode,
|
|
|
|
|
CONFIG['GLOBAL']['{}_FILE'.format(_prefix)],
|
|
|
|
|
CONFIG['GLOBAL']['{}_TG'.format(_prefix)],
|
|
|
|
|
CONFIG['GLOBAL']['{}_TIMESLOT'.format(_prefix)],
|
|
|
|
|
CONFIG['GLOBAL']['{}_LANGUAGE'.format(_prefix)])
|
|
|
|
|
if _tts_mode == 'interval':
|
|
|
|
|
logger.info('(%s) Interval: every %s seconds', _label, _tts_check_interval)
|
|
|
|
|
|
|
|
|
|
#Security downloads from central server
|
|
|
|
|
init_security_downloads(CONFIG)
|
|
|
|
|
|
|
|
|
|
|