Files
viktor b5a8e92e45 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

75 строки
3.1 KiB
Python

#!/usr/bin/env python3
"""Hook sending into the interface: a Reply button on an incoming message, and a
working New msg entry.
For this first step the text is a fixed quick reply, so the whole transmit path can be
exercised on air before the keypad editor is written.
"""
W = "/home/viktor/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application"
# ---- Reply button on the incoming message popup -------------------------
p = W + "/source/applicationMain.c"
s = open(p).read()
if "smsReplyValidator" in s:
print("applicationMain.c: reply already wired")
else:
# remember who to answer, and send the quick reply when the user confirms
anchor = "\t\tif (smsHasUnread())\n"
assert anchor in s, "unread block not found"
helper = """static uint32_t smsReplyToId = 0;
static bool smsReplyIsPrivate = true;
// Called when the user confirms the Reply prompt on an incoming message.
static bool smsReplyValidator(void)
{
smsSendText(smsReplyToId, (smsReplyIsPrivate == false), "OK");
return true;
}
"""
# place the helper just before the function that contains the unread block
fn_start = s.rindex("\nvoid ", 0, s.index(anchor))
s = s[:fn_start + 1] + helper + s[fn_start + 1:]
old_show = """ uiDataGlobal.MessageBox.buttons = MESSAGEBOX_BUTTONS_OK;
uiDataGlobal.MessageBox.validatorCallback = NULL;"""
assert old_show in s, "message box setup not found"
s = s.replace(old_show, """ // Yes replies to the sender, No just dismisses the message.
uiDataGlobal.MessageBox.buttons = MESSAGEBOX_BUTTONS_YESNO;
uiDataGlobal.MessageBox.validatorCallback = smsReplyValidator;
smsReplyToId = msg->srcId;
smsReplyIsPrivate = msg->isPrivate;""", 1)
open(p, "w").write(s)
print("applicationMain.c: Reply wired into the incoming message popup")
# ---- New msg actually sends --------------------------------------------
p = W + "/source/user_interface/menuMessages.c"
s = open(p).read()
if "smsSendText" in s:
print("menuMessages.c: already sends")
else:
old = """ case MESSAGES_ITEM_NEW:
// Sending is not implemented yet; say so rather than opening a dead screen.
uiNotificationShow(NOTIFICATION_TYPE_MESSAGE, NOTIFICATION_ID_MESSAGE, 2000, "Not yet", true);
break;"""
assert old in s, "New msg handler not found"
s = s.replace(old, """ case MESSAGES_ITEM_NEW:
// First step: send a fixed text to the current talkgroup, so the transmit
// path can be exercised on air. The keypad editor comes next.
if (smsSendText((trxTalkGroupOrPcId & 0x00FFFFFF), ((trxTalkGroupOrPcId >> 24) == TG_CALL_FLAG), "TEST FROM RADIO"))
{
uiNotificationShow(NOTIFICATION_TYPE_MESSAGE, NOTIFICATION_ID_MESSAGE, 2000, "Sending...", true);
}
else
{
uiNotificationShow(NOTIFICATION_TYPE_MESSAGE, NOTIFICATION_ID_MESSAGE, 2000, "Busy", true);
}
break;""", 1)
s = s.replace('#include "functions/sound.h"',
'#include "functions/sound.h"\n#include "functions/trx.h"\n#include "user_interface/uiUtilities.h"', 1)
open(p, "w").write(s)
print("menuMessages.c: New msg now transmits")