Merge pull request #38 from pyopower/ta-iso8

Ta iso8
pull/39/head
Esteban Mackay Q. 2 months ago committed by GitHub
commit 619f27fd57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -162,6 +162,7 @@ def build_config(_config_file):
'TALKER_ALIAS': config.getboolean(section, 'TALKER_ALIAS', fallback=False),
'TALKER_ALIAS_MODE': config.get(section, 'TALKER_ALIAS_MODE', fallback='both'),
'TALKER_ALIAS_FORMAT': config.get(section, 'TALKER_ALIAS_FORMAT', fallback='{callsign} {fname}'),
'TALKER_ALIAS_TEXT_FORMAT': config.get(section, 'TALKER_ALIAS_TEXT_FORMAT', fallback='utf8'),
})
if not CONFIG['GLOBAL']['ANNOUNCEMENT_LANGUAGES']:
CONFIG['GLOBAL']['ANNOUNCEMENT_LANGUAGES'] = languages

@ -55,10 +55,14 @@ VALIDATE_SERVER_IDS: True
# TALKER_ALIAS - True para activar el reenvio/insercion de Talker Alias (DMRA + LC embebida)
# TALKER_ALIAS_MODE - passthrough (solo reenvia el TA recibido), inject (genera el TA desde el alias del suscriptor)
# o both (reenvia si llega completo, si no lo genera). Por defecto: both
# TALKER_ALIAS_FORMAT - plantilla del texto generado en modo inject/both. Campos: {callsign} {fname} {surname} {id}
# TALKER_ALIAS_FORMAT - plantilla del texto generado en modo inject/both. Campos: {callsign} {fname} {surname} {city} {state} {country} {id}
# TALKER_ALIAS_TEXT_FORMAT - codificacion(es) del TA, separadas por coma: utf8 (lo muestra Motorola),
# iso8 (ISO-8859-1, lo muestra Hytera), 7bit. Una lista como 'utf8,iso8' emite el
# TA en ambos formatos seguidos, asi Motorola y Hytera lo ven a la vez. Defecto: utf8
TALKER_ALIAS: False
TALKER_ALIAS_MODE: both
TALKER_ALIAS_FORMAT: {callsign} {fname}
TALKER_ALIAS_TEXT_FORMAT: utf8
# Servidor de seguridad centralizado
URL_SECURITY:

@ -91,6 +91,53 @@ def encode_utf8(text):
return bytes(buf)
def encode_iso8(text):
"""Encode text into a 28-byte TA buffer (format 1 / ISO-8859-1).
More widely displayed than UTF-8 on some radios (notably Hytera, which often
will not render format 2 / UTF-8 Talker Alias)."""
text = truncate_talker_alias(text)
raw = text.encode('latin-1', errors='replace')[:27]
size = len(raw)
header = (TA_FORMAT_ISO8 << 6) | (size << 1) | 0
buf = bytearray(DMRA_BUF_LEN)
buf[0] = header
buf[1:1 + size] = raw
return bytes(buf)
def encode_7bit(text):
"""Encode text into a 28-byte TA buffer (format 0 / 7-bit packed).
Bit stream is format(2) + size(5) + 7 bits per char, MSB-first; this is the
exact inverse of decode_ta()'s 7-bit path. For the oldest radios."""
text = truncate_talker_alias(text)
chars = text.encode('ascii', errors='replace')[:31]
size = len(chars)
bits = [(TA_FORMAT_7BIT >> 1) & 1, TA_FORMAT_7BIT & 1]
for b in range(4, -1, -1):
bits.append((size >> b) & 1)
for ch in chars:
for b in range(6, -1, -1):
bits.append((ch >> b) & 1)
total = DMRA_BUF_LEN * 8
bits = (bits + [0] * total)[:total]
buf = bytearray(DMRA_BUF_LEN)
for i, bit in enumerate(bits):
if bit:
buf[i // 8] |= 1 << (7 - (i % 8))
return bytes(buf)
_TA_ENCODERS = {'utf8': encode_utf8, 'iso8': encode_iso8, '7bit': encode_7bit}
def encode_ta_buffer(text, text_format='utf8'):
"""Encode text into a 28-byte TA buffer in the requested format.
text_format: 'utf8' (default), 'iso8' (ISO-8859-1, best for Hytera), '7bit'."""
return _TA_ENCODERS.get(text_format, encode_utf8)(text)
def blocks_from_buffer(buf):
"""Split a 28-byte encoded buffer into four 7-byte DMRA payloads."""
buf = buf.ljust(DMRA_BUF_LEN, b'\x00')[:DMRA_BUF_LEN]
@ -121,10 +168,10 @@ def buffer_from_blocks(blocks):
# Standalone DMRA packet builders / parser
# ---------------------------------------------------------------------------
def build_dmra_packets(rf_src, text):
def build_dmra_packets(rf_src, text, text_format='utf8'):
"""Build the 1-4 HBP DMRA packets needed to carry 'text' (server injection)."""
rf = rf_src[:3] if len(rf_src) >= 3 else rf_src.ljust(3, b'\x00')[:3]
encoded = encode_utf8(text)
encoded = encode_ta_buffer(text, text_format)
blocks = blocks_from_buffer(encoded)
count = required_ta_block_count(encoded)
packets = []
@ -199,13 +246,20 @@ def _encode_emblc(_lc):
return {1: out[0:32], 2: out[32:64], 3: out[64:96], 4: out[96:128]}
def encode_talker_alias_emblc(text):
"""Embedded-LC dicts for TA blocks 0..N-1 plus block count N (1-4)."""
encoded = encode_utf8(text)
def encode_talker_alias_emblc(text, text_formats=('utf8',)):
"""Embedded-LC dicts for the TA blocks. `text_formats` may be a single
encoding or several; with several, the TA is emitted in each encoding back to
back (e.g. UTF-8 then ISO-8859-1) so radios of different vendors each pick up
the format they support. Returns (list-of-emblc-dicts, total-count)."""
if isinstance(text_formats, str):
text_formats = [text_formats]
emblcs = []
for tf in (list(text_formats) or ['utf8']):
encoded = encode_ta_buffer(text, tf)
blocks = blocks_from_buffer(encoded)
count = required_ta_block_count(encoded)
emblcs = [_encode_emblc(talker_alias_lc_bytes(i, blocks[i])) for i in range(count)]
return emblcs, count
cnt = required_ta_block_count(encoded)
emblcs += [_encode_emblc(talker_alias_lc_bytes(i, blocks[i])) for i in range(cnt)]
return emblcs, len(emblcs)
def encode_talker_alias_emblc_from_blocks(blocks):
@ -263,7 +317,17 @@ def ta_settings(CONFIG, system_name=None):
fmt = sys_cfg.get('TALKER_ALIAS_FORMAT')
if fmt is None:
fmt = g.get('TALKER_ALIAS_FORMAT', '{callsign} {fname}')
return {'enabled': bool(enabled), 'mode': mode, 'format': str(fmt)}
tfmt = sys_cfg.get('TALKER_ALIAS_TEXT_FORMAT')
if tfmt is None:
tfmt = g.get('TALKER_ALIAS_TEXT_FORMAT', 'utf8')
# comma-separated list -> emit the TA in each encoding back to back, so radios
# of different vendors each pick up the format they support (e.g. 'utf8,iso8'
# for Motorola + Hytera coexistence).
formats = [f.strip().lower() for f in str(tfmt).split(',')]
formats = [f for f in formats if f in ('utf8', 'iso8', '7bit')]
if not formats:
formats = ['utf8']
return {'enabled': bool(enabled), 'mode': mode, 'format': str(fmt), 'text_formats': formats}
def ta_enabled(CONFIG, system_name=None):
@ -351,13 +415,13 @@ def resolve_ta(CONFIG, system_name, rf_src, blocks):
'text': decode_ta(buffer_from_blocks(blocks))}
if mode == 'inject':
return {'source': 'inject', 'blocks': None,
'text': format_ta_text(CONFIG, rf_src)}
'text': format_ta_text(CONFIG, rf_src), 'text_formats': settings['text_formats']}
# both
if have_passthrough:
return {'source': 'passthrough', 'blocks': dict(blocks),
'text': decode_ta(buffer_from_blocks(blocks))}
return {'source': 'inject', 'blocks': None,
'text': format_ta_text(CONFIG, rf_src)}
'text': format_ta_text(CONFIG, rf_src), 'text_formats': settings['text_formats']}
def ta_dmra_packets(rf_src, resolved):
@ -366,7 +430,7 @@ def ta_dmra_packets(rf_src, resolved):
return []
if resolved['source'] == 'passthrough':
return passthrough_packets_from_blocks(rf_src, resolved['blocks'])
return build_dmra_packets(rf_src, resolved['text'])
return build_dmra_packets(rf_src, resolved['text'], (resolved.get('text_formats') or ['utf8'])[0])
def ta_emblc(resolved):
@ -375,7 +439,7 @@ def ta_emblc(resolved):
return None
if resolved['source'] == 'passthrough':
return encode_talker_alias_emblc_from_blocks(resolved['blocks'])
return encode_talker_alias_emblc(resolved['text'])
return encode_talker_alias_emblc(resolved['text'], resolved.get('text_formats', ['utf8']))
# ---------------------------------------------------------------------------

Loading…
Cancel
Save

Powered by TurnKey Linux.