DMR-данные (SMS) на TYT MD-UV380 под OpenGD77
Восстановлено из транскрипта сессии после потери рабочего дерева на сервере сборки. Что внутри: - четыре новых модуля прошивки (приём, сборка исходящего, меню Messages); - правки существующих файлов в виде скриптов, с порядком применения; - инструменты стенда: снятие экрана рации по USB, дамп кадров, эталонная сборка пакета; - NOTES.md: что проверено в эфире, что не доделано, где грабли. Работало в эфире в августе 2026: данные ходят в обе стороны в режиме хотспота, рация принимает SMS и показывает на экране. Отправка собрана и сверена с эталоном сервера побайтово, но в эфире не проверялась. Claude-Session: https://claude.ai/code/session_01GJtvG5WwqYNe1b7s9LLtgy
Этот коммит содержится в:
@@ -0,0 +1,448 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fix the Messages menus: wrong language string index, and hand-rolled navigation.
|
||||
|
||||
Two defects:
|
||||
- the main menu entry pointed at string 281 while .messages is index 280, so the
|
||||
entry rendered as "GD77LANG" (it picked up a neighbouring field);
|
||||
- the screens tracked the selection in their own variables and drew entries at a
|
||||
fixed offset, instead of using the menu system's list handling. That makes the
|
||||
cursor behave wrongly (no scrolling, wrong highlight).
|
||||
|
||||
Both screens are rewritten to the pattern used by the stock list menus:
|
||||
menuDataGlobal.currentItemIndex + menuGetMenuOffset() + menuSystemMenuIncrement().
|
||||
"""
|
||||
W = "/home/viktor/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application"
|
||||
|
||||
# ---- 1. correct the main menu string index ------------------------------
|
||||
p = W + "/source/user_interface/menuSystem.c"
|
||||
s = open(p).read()
|
||||
if "{ 281, MENU_MESSAGES" in s:
|
||||
s = s.replace("{ 281, MENU_MESSAGES },", "{ 280, MENU_MESSAGES },", 1)
|
||||
open(p, "w").write(s)
|
||||
print("menuSystem.c: string index corrected 281 -> 280")
|
||||
else:
|
||||
print("menuSystem.c: index already correct")
|
||||
|
||||
# ---- 2. rewrite the screens using the standard list handling ------------
|
||||
p = W + "/source/user_interface/menuMessages.c"
|
||||
src = open(p).read()
|
||||
header_end = src.index("// Entries of the top level Messages menu.")
|
||||
header = src[:header_end]
|
||||
|
||||
body = '''// Entries of the top level Messages menu.
|
||||
enum
|
||||
{
|
||||
MESSAGES_ITEM_NEW = 0,
|
||||
MESSAGES_ITEM_INBOX,
|
||||
MESSAGES_ITEM_OUTBOX,
|
||||
NUM_MESSAGES_ITEMS
|
||||
};
|
||||
|
||||
// Entries of the Inbox / Outbox menus.
|
||||
enum
|
||||
{
|
||||
FOLDER_ITEM_LIST = 0,
|
||||
FOLDER_ITEM_DELETE_ALL,
|
||||
NUM_FOLDER_ITEMS
|
||||
};
|
||||
|
||||
static bool showingOutbox = false; // which folder the list screens act on
|
||||
static bool viewingMessage = false;// a single message is open
|
||||
|
||||
static menuStatus_t exitCode = MENU_STATUS_SUCCESS;
|
||||
|
||||
static void messagesUpdateScreen(void);
|
||||
static void messagesHandleEvent(uiEvent_t *ev);
|
||||
static void folderUpdateScreen(void);
|
||||
static void folderHandleEvent(uiEvent_t *ev);
|
||||
static void listUpdateScreen(void);
|
||||
static void listHandleEvent(uiEvent_t *ev);
|
||||
|
||||
|
||||
// ---------------------------------------------------------------- Messages
|
||||
|
||||
menuStatus_t menuMessages(uiEvent_t *ev, bool isFirstRun)
|
||||
{
|
||||
if (isFirstRun)
|
||||
{
|
||||
menuDataGlobal.numItems = NUM_MESSAGES_ITEMS;
|
||||
menuDataGlobal.currentItemIndex = 0;
|
||||
messagesUpdateScreen();
|
||||
return (MENU_STATUS_LIST_TYPE | MENU_STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
exitCode = MENU_STATUS_SUCCESS;
|
||||
|
||||
if (ev->hasEvent)
|
||||
{
|
||||
messagesHandleEvent(ev);
|
||||
}
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
static void messagesUpdateScreen(void)
|
||||
{
|
||||
char buf[SCREEN_LINE_BUFFER_SIZE];
|
||||
int mNum;
|
||||
|
||||
displayClearBuf();
|
||||
menuDisplayTitle(currentLanguage->messages);
|
||||
|
||||
for (int i = MENU_START_ITERATION_VALUE; i <= MENU_END_ITERATION_VALUE; i++)
|
||||
{
|
||||
mNum = menuGetMenuOffset(NUM_MESSAGES_ITEMS, i);
|
||||
|
||||
if (mNum == MENU_OFFSET_BEFORE_FIRST_ENTRY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (mNum == MENU_OFFSET_AFTER_LAST_ENTRY)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
switch (mNum)
|
||||
{
|
||||
case MESSAGES_ITEM_NEW:
|
||||
snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "New msg");
|
||||
break;
|
||||
|
||||
case MESSAGES_ITEM_INBOX:
|
||||
snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "Inbox (%u)", (unsigned int)smsInboxCount());
|
||||
break;
|
||||
|
||||
case MESSAGES_ITEM_OUTBOX:
|
||||
snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "Outbox (%u)", (unsigned int)smsOutboxCount());
|
||||
break;
|
||||
|
||||
default:
|
||||
buf[0] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
menuDisplayEntry(i, mNum, buf, 0, THEME_ITEM_FG_MENU_ITEM, THEME_ITEM_COLOUR_NONE, THEME_ITEM_BG);
|
||||
}
|
||||
|
||||
displayRender();
|
||||
}
|
||||
|
||||
static void messagesHandleEvent(uiEvent_t *ev)
|
||||
{
|
||||
if (KEYCHECK_PRESS(ev->keys, KEY_DOWN))
|
||||
{
|
||||
menuSystemMenuIncrement(&menuDataGlobal.currentItemIndex, NUM_MESSAGES_ITEMS);
|
||||
messagesUpdateScreen();
|
||||
exitCode |= MENU_STATUS_LIST_TYPE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (KEYCHECK_PRESS(ev->keys, KEY_UP))
|
||||
{
|
||||
menuSystemMenuDecrement(&menuDataGlobal.currentItemIndex, NUM_MESSAGES_ITEMS);
|
||||
messagesUpdateScreen();
|
||||
exitCode |= MENU_STATUS_LIST_TYPE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN))
|
||||
{
|
||||
switch (menuDataGlobal.currentItemIndex)
|
||||
{
|
||||
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;
|
||||
|
||||
case MESSAGES_ITEM_INBOX:
|
||||
showingOutbox = false;
|
||||
menuSystemPushNewMenu(MENU_MESSAGES_FOLDER);
|
||||
break;
|
||||
|
||||
case MESSAGES_ITEM_OUTBOX:
|
||||
showingOutbox = true;
|
||||
menuSystemPushNewMenu(MENU_MESSAGES_FOLDER);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (KEYCHECK_SHORTUP(ev->keys, KEY_RED))
|
||||
{
|
||||
menuSystemPopPreviousMenu();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------- Inbox / Outbox menu
|
||||
|
||||
menuStatus_t menuMessagesFolder(uiEvent_t *ev, bool isFirstRun)
|
||||
{
|
||||
if (isFirstRun)
|
||||
{
|
||||
menuDataGlobal.numItems = NUM_FOLDER_ITEMS;
|
||||
menuDataGlobal.currentItemIndex = 0;
|
||||
folderUpdateScreen();
|
||||
return (MENU_STATUS_LIST_TYPE | MENU_STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
exitCode = MENU_STATUS_SUCCESS;
|
||||
|
||||
if (ev->hasEvent)
|
||||
{
|
||||
folderHandleEvent(ev);
|
||||
}
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
static void folderUpdateScreen(void)
|
||||
{
|
||||
char buf[SCREEN_LINE_BUFFER_SIZE];
|
||||
uint8_t count = (showingOutbox ? smsOutboxCount() : smsInboxCount());
|
||||
int mNum;
|
||||
|
||||
displayClearBuf();
|
||||
menuDisplayTitle(showingOutbox ? "Outbox" : "Inbox");
|
||||
|
||||
for (int i = MENU_START_ITERATION_VALUE; i <= MENU_END_ITERATION_VALUE; i++)
|
||||
{
|
||||
mNum = menuGetMenuOffset(NUM_FOLDER_ITEMS, i);
|
||||
|
||||
if (mNum == MENU_OFFSET_BEFORE_FIRST_ENTRY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (mNum == MENU_OFFSET_AFTER_LAST_ENTRY)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (mNum == FOLDER_ITEM_LIST)
|
||||
{
|
||||
snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "%s (%u)", (showingOutbox ? "Outbox List" : "Inbox List"), (unsigned int)count);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "Delete All");
|
||||
}
|
||||
|
||||
menuDisplayEntry(i, mNum, buf, 0, THEME_ITEM_FG_MENU_ITEM, THEME_ITEM_COLOUR_NONE, THEME_ITEM_BG);
|
||||
}
|
||||
|
||||
displayRender();
|
||||
}
|
||||
|
||||
static void folderHandleEvent(uiEvent_t *ev)
|
||||
{
|
||||
if (KEYCHECK_PRESS(ev->keys, KEY_DOWN))
|
||||
{
|
||||
menuSystemMenuIncrement(&menuDataGlobal.currentItemIndex, NUM_FOLDER_ITEMS);
|
||||
folderUpdateScreen();
|
||||
exitCode |= MENU_STATUS_LIST_TYPE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (KEYCHECK_PRESS(ev->keys, KEY_UP))
|
||||
{
|
||||
menuSystemMenuDecrement(&menuDataGlobal.currentItemIndex, NUM_FOLDER_ITEMS);
|
||||
folderUpdateScreen();
|
||||
exitCode |= MENU_STATUS_LIST_TYPE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN))
|
||||
{
|
||||
if (menuDataGlobal.currentItemIndex == FOLDER_ITEM_LIST)
|
||||
{
|
||||
viewingMessage = false;
|
||||
menuSystemPushNewMenu(MENU_MESSAGES_LIST);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (showingOutbox)
|
||||
{
|
||||
smsOutboxClear();
|
||||
}
|
||||
else
|
||||
{
|
||||
smsInboxClear();
|
||||
}
|
||||
|
||||
soundSetMelody(MELODY_ACK_BEEP);
|
||||
folderUpdateScreen();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (KEYCHECK_SHORTUP(ev->keys, KEY_RED))
|
||||
{
|
||||
menuSystemPopPreviousMenu();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------- message list
|
||||
|
||||
menuStatus_t menuMessagesList(uiEvent_t *ev, bool isFirstRun)
|
||||
{
|
||||
if (isFirstRun)
|
||||
{
|
||||
menuDataGlobal.numItems = (showingOutbox ? smsOutboxCount() : smsInboxCount());
|
||||
menuDataGlobal.currentItemIndex = 0;
|
||||
viewingMessage = false;
|
||||
listUpdateScreen();
|
||||
return (MENU_STATUS_LIST_TYPE | MENU_STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
exitCode = MENU_STATUS_SUCCESS;
|
||||
|
||||
if (ev->hasEvent)
|
||||
{
|
||||
listHandleEvent(ev);
|
||||
}
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
static void listUpdateScreen(void)
|
||||
{
|
||||
char buf[SCREEN_LINE_BUFFER_SIZE];
|
||||
uint8_t count = (showingOutbox ? smsOutboxCount() : smsInboxCount());
|
||||
int mNum;
|
||||
|
||||
displayClearBuf();
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
menuDisplayTitle(showingOutbox ? "Outbox" : "Inbox");
|
||||
displayPrintCentered(32, "Empty", FONT_SIZE_3);
|
||||
displayRender();
|
||||
return;
|
||||
}
|
||||
|
||||
if (viewingMessage)
|
||||
{
|
||||
const smsMessage_t *msg = (showingOutbox
|
||||
? smsOutboxGet((uint8_t)menuDataGlobal.currentItemIndex)
|
||||
: smsInboxGet((uint8_t)menuDataGlobal.currentItemIndex));
|
||||
|
||||
if (msg != NULL)
|
||||
{
|
||||
const int charsPerLine = 20;
|
||||
int len = (int)strlen(msg->text);
|
||||
|
||||
snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "%u", (unsigned int)(showingOutbox ? msg->dstId : msg->srcId));
|
||||
menuDisplayTitle(buf);
|
||||
|
||||
// Wrap the text so long messages are readable instead of being cut off.
|
||||
for (int line = 0; (line < 4) && ((line * charsPerLine) < len); line++)
|
||||
{
|
||||
int n = len - (line * charsPerLine);
|
||||
|
||||
if (n > charsPerLine)
|
||||
{
|
||||
n = charsPerLine;
|
||||
}
|
||||
|
||||
memcpy(buf, &msg->text[line * charsPerLine], n);
|
||||
buf[n] = 0;
|
||||
displayPrintCore(0, (16 + (line * 12)), buf, FONT_SIZE_2, TEXT_ALIGN_LEFT, false);
|
||||
}
|
||||
}
|
||||
|
||||
displayRender();
|
||||
return;
|
||||
}
|
||||
|
||||
menuDisplayTitle(showingOutbox ? "Outbox" : "Inbox");
|
||||
|
||||
for (int i = MENU_START_ITERATION_VALUE; i <= MENU_END_ITERATION_VALUE; i++)
|
||||
{
|
||||
mNum = menuGetMenuOffset(count, i);
|
||||
|
||||
if (mNum == MENU_OFFSET_BEFORE_FIRST_ENTRY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (mNum == MENU_OFFSET_AFTER_LAST_ENTRY)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const smsMessage_t *m = (showingOutbox ? smsOutboxGet((uint8_t)mNum) : smsInboxGet((uint8_t)mNum));
|
||||
|
||||
if (m == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "%s%u %.8s",
|
||||
(m->unread ? "*" : ""),
|
||||
(unsigned int)(showingOutbox ? m->dstId : m->srcId),
|
||||
m->text);
|
||||
|
||||
menuDisplayEntry(i, mNum, buf, 0, THEME_ITEM_FG_MENU_ITEM, THEME_ITEM_COLOUR_NONE, THEME_ITEM_BG);
|
||||
}
|
||||
|
||||
displayRender();
|
||||
}
|
||||
|
||||
static void listHandleEvent(uiEvent_t *ev)
|
||||
{
|
||||
uint8_t count = (showingOutbox ? smsOutboxCount() : smsInboxCount());
|
||||
|
||||
if ((viewingMessage == false) && (count > 0))
|
||||
{
|
||||
if (KEYCHECK_PRESS(ev->keys, KEY_DOWN))
|
||||
{
|
||||
menuSystemMenuIncrement(&menuDataGlobal.currentItemIndex, count);
|
||||
listUpdateScreen();
|
||||
exitCode |= MENU_STATUS_LIST_TYPE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (KEYCHECK_PRESS(ev->keys, KEY_UP))
|
||||
{
|
||||
menuSystemMenuDecrement(&menuDataGlobal.currentItemIndex, count);
|
||||
listUpdateScreen();
|
||||
exitCode |= MENU_STATUS_LIST_TYPE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN) && (count > 0))
|
||||
{
|
||||
if (viewingMessage == false)
|
||||
{
|
||||
viewingMessage = true;
|
||||
|
||||
if (showingOutbox == false)
|
||||
{
|
||||
smsMarkRead();
|
||||
}
|
||||
|
||||
listUpdateScreen();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (KEYCHECK_SHORTUP(ev->keys, KEY_RED))
|
||||
{
|
||||
if (viewingMessage)
|
||||
{
|
||||
viewingMessage = false;
|
||||
listUpdateScreen();
|
||||
return;
|
||||
}
|
||||
|
||||
menuSystemPopPreviousMenu();
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
open(p, "w").write(header + body)
|
||||
print("menuMessages.c: rewritten with standard list navigation")
|
||||
Ссылка в новой задаче
Block a user