|
|
|
@ -162,23 +162,58 @@ def talker_alias_lc_bytes(block_id, payload7):
|
|
|
|
return bytes([FLCO_TALKER_ALIAS_HEADER + block, 0x00]) + payload
|
|
|
|
return bytes([FLCO_TALKER_ALIAS_HEADER + block, 0x00]) + payload
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _encode_emblc(_lc):
|
|
|
|
|
|
|
|
"""Spec-correct embedded-LC encoder (BPTC 128/72 + 5-bit checksum).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dmr_utils3.bptc.encode_emblc has a transcription bug: in burst D it uses
|
|
|
|
|
|
|
|
_binlc[24] twice instead of _binlc[25], flipping one bit of byte 2 of every
|
|
|
|
|
|
|
|
embedded LC. Harmless for the group voice LC (the bit lands in Service
|
|
|
|
|
|
|
|
Options) but for Talker Alias byte 2 is the TA header (format/length) of
|
|
|
|
|
|
|
|
block 0 and the first text char of blocks 1-3, so it corrupts the alias.
|
|
|
|
|
|
|
|
The column-major interleave is: out bit (burst b, subrow j, row r) =
|
|
|
|
|
|
|
|
_binlc[(b*4 + j) + 16*r], for b,j in 0..3 and r in 0..7.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
from dmr_utils3 import hamming, crc
|
|
|
|
|
|
|
|
from bitarray import bitarray
|
|
|
|
|
|
|
|
_csum = crc.csum5(_lc)
|
|
|
|
|
|
|
|
_binlc = bitarray(endian="big")
|
|
|
|
|
|
|
|
_binlc.frombytes(_lc)
|
|
|
|
|
|
|
|
_binlc.insert(32, _csum[0])
|
|
|
|
|
|
|
|
_binlc.insert(43, _csum[1])
|
|
|
|
|
|
|
|
_binlc.insert(54, _csum[2])
|
|
|
|
|
|
|
|
_binlc.insert(65, _csum[3])
|
|
|
|
|
|
|
|
_binlc.insert(76, _csum[4])
|
|
|
|
|
|
|
|
for index in range(0, 112, 16):
|
|
|
|
|
|
|
|
for hindex, hbit in zip(range(index + 11, index + 16),
|
|
|
|
|
|
|
|
hamming.enc_16114(_binlc[index:index + 11])):
|
|
|
|
|
|
|
|
_binlc.insert(hindex, hbit)
|
|
|
|
|
|
|
|
for index in range(0, 16):
|
|
|
|
|
|
|
|
_binlc.insert(index + 112,
|
|
|
|
|
|
|
|
_binlc[index] ^ _binlc[index + 16] ^ _binlc[index + 32]
|
|
|
|
|
|
|
|
^ _binlc[index + 48] ^ _binlc[index + 64] ^ _binlc[index + 80]
|
|
|
|
|
|
|
|
^ _binlc[index + 96])
|
|
|
|
|
|
|
|
_idx = [(b * 4 + j) + 16 * r for b in range(4) for j in range(4) for r in range(8)]
|
|
|
|
|
|
|
|
out = bitarray(128, endian="big")
|
|
|
|
|
|
|
|
for k, i in enumerate(_idx):
|
|
|
|
|
|
|
|
out[k] = _binlc[i]
|
|
|
|
|
|
|
|
return {1: out[0:32], 2: out[32:64], 3: out[64:96], 4: out[96:128]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def encode_talker_alias_emblc(text):
|
|
|
|
def encode_talker_alias_emblc(text):
|
|
|
|
"""Embedded-LC dicts for TA blocks 0..N-1 plus block count N (1-4)."""
|
|
|
|
"""Embedded-LC dicts for TA blocks 0..N-1 plus block count N (1-4)."""
|
|
|
|
from dmr_utils3 import bptc
|
|
|
|
|
|
|
|
encoded = encode_utf8(text)
|
|
|
|
encoded = encode_utf8(text)
|
|
|
|
blocks = blocks_from_buffer(encoded)
|
|
|
|
blocks = blocks_from_buffer(encoded)
|
|
|
|
count = required_ta_block_count(encoded)
|
|
|
|
count = required_ta_block_count(encoded)
|
|
|
|
emblcs = [bptc.encode_emblc(talker_alias_lc_bytes(i, blocks[i])) for i in range(count)]
|
|
|
|
emblcs = [_encode_emblc(talker_alias_lc_bytes(i, blocks[i])) for i in range(count)]
|
|
|
|
return emblcs, count
|
|
|
|
return emblcs, count
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def encode_talker_alias_emblc_from_blocks(blocks):
|
|
|
|
def encode_talker_alias_emblc_from_blocks(blocks):
|
|
|
|
"""Build embedded TA LC dicts from buffered (passthrough) DMRA payloads."""
|
|
|
|
"""Build embedded TA LC dicts from buffered (passthrough) DMRA payloads."""
|
|
|
|
from dmr_utils3 import bptc
|
|
|
|
|
|
|
|
buf = buffer_from_blocks(blocks)
|
|
|
|
buf = buffer_from_blocks(blocks)
|
|
|
|
buf_blocks = blocks_from_buffer(buf)
|
|
|
|
buf_blocks = blocks_from_buffer(buf)
|
|
|
|
count = required_ta_block_count(buf)
|
|
|
|
count = required_ta_block_count(buf)
|
|
|
|
emblcs = [bptc.encode_emblc(talker_alias_lc_bytes(i, buf_blocks[i])) for i in range(count)]
|
|
|
|
emblcs = [_encode_emblc(talker_alias_lc_bytes(i, buf_blocks[i])) for i in range(count)]
|
|
|
|
return emblcs, count
|
|
|
|
return emblcs, count
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -186,6 +221,33 @@ def encode_talker_alias_emblc_from_blocks(blocks):
|
|
|
|
# Policy: settings, text formatting, inject vs passthrough
|
|
|
|
# Policy: settings, text formatting, inject vs passthrough
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_ta_profiles(json_path):
|
|
|
|
|
|
|
|
"""Build {id: {callsign, fname, surname, city, state, country}} from a
|
|
|
|
|
|
|
|
radioid-style subscriber_ids.json. Lets inject-mode templates use name/QTH.
|
|
|
|
|
|
|
|
Returns {} on any error (caller can fall back to a callsign-only map)."""
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
out = {}
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
with open(json_path, "r", encoding="latin-1") as fh:
|
|
|
|
|
|
|
|
data = json.load(fh)
|
|
|
|
|
|
|
|
for rec in data.get("results", []):
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
rid = int(rec["id"])
|
|
|
|
|
|
|
|
except (KeyError, ValueError, TypeError):
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
out[rid] = {
|
|
|
|
|
|
|
|
"callsign": (rec.get("callsign") or "").strip(),
|
|
|
|
|
|
|
|
"fname": (rec.get("fname") or "").strip(),
|
|
|
|
|
|
|
|
"surname": (rec.get("surname") or "").strip(),
|
|
|
|
|
|
|
|
"city": (rec.get("city") or "").strip(),
|
|
|
|
|
|
|
|
"state": (rec.get("state") or "").strip(),
|
|
|
|
|
|
|
|
"country": (rec.get("country") or "").strip(),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ta_settings(CONFIG, system_name=None):
|
|
|
|
def ta_settings(CONFIG, system_name=None):
|
|
|
|
"""Effective Talker Alias settings (GLOBAL with optional per-system override)."""
|
|
|
|
"""Effective Talker Alias settings (GLOBAL with optional per-system override)."""
|
|
|
|
g = CONFIG.get('GLOBAL', {})
|
|
|
|
g = CONFIG.get('GLOBAL', {})
|
|
|
|
@ -218,11 +280,16 @@ def format_ta_text(CONFIG, rf_src):
|
|
|
|
callsign = profile.get('callsign') or ''
|
|
|
|
callsign = profile.get('callsign') or ''
|
|
|
|
fname = profile.get('fname') or ''
|
|
|
|
fname = profile.get('fname') or ''
|
|
|
|
surname = profile.get('surname') or ''
|
|
|
|
surname = profile.get('surname') or ''
|
|
|
|
|
|
|
|
city = profile.get('city') or ''
|
|
|
|
|
|
|
|
state = profile.get('state') or ''
|
|
|
|
|
|
|
|
country = profile.get('country') or ''
|
|
|
|
if profile.get('talker_alias'):
|
|
|
|
if profile.get('talker_alias'):
|
|
|
|
text = str(profile['talker_alias'])
|
|
|
|
text = str(profile['talker_alias'])
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
text = template.format(callsign=callsign, fname=fname, surname=surname, id=rid)
|
|
|
|
text = template.format(callsign=callsign, fname=fname,
|
|
|
|
|
|
|
|
surname=surname, city=city, state=state,
|
|
|
|
|
|
|
|
country=country, id=rid)
|
|
|
|
except (KeyError, ValueError, IndexError):
|
|
|
|
except (KeyError, ValueError, IndexError):
|
|
|
|
text = callsign or 'DMR ID:{}'.format(rid)
|
|
|
|
text = callsign or 'DMR ID:{}'.format(rid)
|
|
|
|
text = ' '.join(text.split())
|
|
|
|
text = ' '.join(text.split())
|
|
|
|
|