DMR-данные (SMS) на TYT MD-UV380 под OpenGD77

Восстановлено из транскрипта сессии после потери рабочего дерева на сервере сборки.

Что внутри:
- четыре новых модуля прошивки (приём, сборка исходящего, меню Messages);
- правки существующих файлов в виде скриптов, с порядком применения;
- инструменты стенда: снятие экрана рации по USB, дамп кадров, эталонная сборка пакета;
- NOTES.md: что проверено в эфире, что не доделано, где грабли.

Работало в эфире в августе 2026: данные ходят в обе стороны в режиме хотспота,
рация принимает SMS и показывает на экране. Отправка собрана и сверена с эталоном
сервера побайтово, но в эфире не проверялась.

Claude-Session: https://claude.ai/code/session_01GJtvG5WwqYNe1b7s9LLtgy
Этот коммит содержится в:
2026-09-11 02:39:24 +09:00
Коммит b5a8e92e45
32 изменённых файлов: 3812 добавлений и 0 удалений
+105
Просмотреть файл
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""Diagnostic build: make the radio show what its SMS assembler actually sees.
Three counters are surfaced on screen, so we can tell where reception stops:
RX - data bursts handed to the assembler by the driver
HDR - data headers parsed (with the announced block count)
ASM - completed assemblies (all blocks collected)
If RX stays at zero the bursts never reach the module and the problem is in the
driver-side capture conditions. If RX counts up but HDR does not, header parsing is
rejecting them. If HDR appears but ASM does not, blocks are being lost.
"""
W = "/home/viktor/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application"
# ---------------------------------------------------------------- sms.h
p = W + "/include/functions/sms.h"
s = open(p).read()
if "smsGetDiagCounters" not in s:
a = "bool smsHasUnread(void);\n"
assert a in s
s = s.replace(a, a + """
// Diagnostics: how many bursts reached the assembler, how many headers were parsed,
// and how many messages assembled completely.
void smsGetDiagCounters(uint16_t *bursts, uint16_t *headers, uint16_t *assembled, uint8_t *lastBlocks);
""", 1)
open(p, "w").write(s)
print("sms.h: diagnostics declared")
# ---------------------------------------------------------------- sms.c
p = W + "/source/functions/sms.c"
s = open(p).read()
if "diagBursts" in s:
print("sms.c: already instrumented")
else:
a = "static bool unread = false;\n"
assert a in s
s = s.replace(a, a + """
static volatile uint16_t diagBursts = 0;
static uint16_t diagHeaders = 0;
static uint16_t diagAssembled = 0;
static uint8_t diagLastBlocks = 0;
""", 1)
# count every burst the driver hands us
a = "\tmemcpy((uint8_t *)rxQueue[rxQueueWrite], payload, SMS_BLOCK_LENGTH);\n"
assert a in s
s = s.replace(a, "\tdiagBursts++;\n" + a, 1)
# count parsed headers, remembering the announced block count
a = "\tblocksExpected = (uint8_t)(hdr[8] & 0x7FU);\n"
assert a in s
s = s.replace(a, a + "\tdiagHeaders++;\n\tdiagLastBlocks = blocksExpected;\n", 1)
# count completed assemblies
a = "static void finishAssembly(void)\n{\n"
assert a in s
s = s.replace(a, a + "\tdiagAssembled++;\n", 1)
s += """
void smsGetDiagCounters(uint16_t *bursts, uint16_t *headers, uint16_t *assembled, uint8_t *lastBlocks)
{
*bursts = diagBursts;
*headers = diagHeaders;
*assembled = diagAssembled;
*lastBlocks = diagLastBlocks;
}
"""
open(p, "w").write(s)
print("sms.c: counters added")
# ------------------------------------------------------- applicationMain.c
p = W + "/source/applicationMain.c"
s = open(p).read()
if "smsGetDiagCounters" in s:
print("applicationMain.c: already showing diagnostics")
else:
a = "\t\tif (smsHasUnread())\n"
assert a in s, "unread check not found"
s = s.replace(a, """\t\t{
\t\t\t// Diagnostics: show the assembler counters whenever they change, so we can see
\t\t\t// how far a received message gets even when no text is produced.
\t\t\tstatic uint16_t lastShownBursts = 0;
\t\t\tstatic uint16_t lastShownHeaders = 0;
\t\t\tuint16_t dBursts, dHeaders, dAssembled;
\t\t\tuint8_t dBlocks;
\t\t\tsmsGetDiagCounters(&dBursts, &dHeaders, &dAssembled, &dBlocks);
\t\t\tif ((dBursts != lastShownBursts) || (dHeaders != lastShownHeaders))
\t\t\t{
\t\t\t\tchar diagLine[40];
\t\t\t\tlastShownBursts = dBursts;
\t\t\t\tlastShownHeaders = dHeaders;
\t\t\t\tsnprintf(diagLine, sizeof(diagLine), "RX%u HDR%u/%u ASM%u",
\t\t\t\t\t\t(unsigned int)dBursts, (unsigned int)dHeaders,
\t\t\t\t\t\t(unsigned int)dBlocks, (unsigned int)dAssembled);
\t\t\t\tuiNotificationShow(NOTIFICATION_TYPE_MESSAGE, NOTIFICATION_ID_MESSAGE, 8000, diagLine, true);
\t\t\t\tsoundSetMelody(MELODY_KEY_BEEP);
\t\t\t}
\t\t}
""" + a, 1)
open(p, "w").write(s)
print("applicationMain.c: diagnostics displayed")