Восстановлено из транскрипта сессии после потери рабочего дерева на сервере сборки. Что внутри: - четыре новых модуля прошивки (приём, сборка исходящего, меню Messages); - правки существующих файлов в виде скриптов, с порядком применения; - инструменты стенда: снятие экрана рации по USB, дамп кадров, эталонная сборка пакета; - NOTES.md: что проверено в эфире, что не доделано, где грабли. Работало в эфире в августе 2026: данные ходят в обе стороны в режиме хотспота, рация принимает SMS и показывает на экране. Отправка собрана и сверена с эталоном сервера побайтово, но в эфире не проверялась. Claude-Session: https://claude.ai/code/session_01GJtvG5WwqYNe1b7s9LLtgy
75 строки
3.1 KiB
Python
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")
|