Восстановлено из транскрипта сессии после потери рабочего дерева на сервере сборки. Что внутри: - четыре новых модуля прошивки (приём, сборка исходящего, меню Messages); - правки существующих файлов в виде скриптов, с порядком применения; - инструменты стенда: снятие экрана рации по USB, дамп кадров, эталонная сборка пакета; - NOTES.md: что проверено в эфире, что не доделано, где грабли. Работало в эфире в августе 2026: данные ходят в обе стороны в режиме хотспота, рация принимает SMS и показывает на экране. Отправка собрана и сверена с эталоном сервера побайтово, но в эфире не проверялась. Claude-Session: https://claude.ai/code/session_01GJtvG5WwqYNe1b7s9LLtgy
73 строки
2.7 KiB
Python
73 строки
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Expose the SMS diagnostics through a RAM window that can be read over USB.
|
|
|
|
Drawing counters on the screen turned out to be unreliable: the notification is
|
|
overwritten by the normal screen redraw, so a screenshot usually shows nothing even
|
|
when reception is happening. Instead the counters are mirrored into the tail of the
|
|
raw audio buffer, which the CPS protocol can read out at any moment (area 7).
|
|
|
|
Layout at rawBuffer[2376..2399]:
|
|
'S' 'M' 'S' 'D' magic, so a stale buffer cannot be mistaken for data
|
|
u16 bursts data bursts handed to the assembler
|
|
u16 headers data headers parsed
|
|
u16 assembled messages assembled completely
|
|
u16 lastBlocks block count announced by the last header
|
|
u16 collected blocks collected for the message in progress
|
|
u16 state 0 = idle, 1 = collecting
|
|
u16 textLen length of the last extracted text
|
|
"""
|
|
W = "/home/viktor/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application"
|
|
|
|
p = W + "/source/functions/sms.c"
|
|
s = open(p).read()
|
|
|
|
if "SMS_DIAG_OFFSET" in s:
|
|
print("already patched")
|
|
raise SystemExit(0)
|
|
|
|
s = s.replace('#include "functions/ticks.h"\n',
|
|
'#include "functions/ticks.h"\n#include "functions/sound.h"\n', 1)
|
|
|
|
anchor = "void smsInit(void)\n"
|
|
assert anchor in s
|
|
helper = """// Diagnostics mirror: the tail of the raw audio buffer is readable over USB (CPS area 7),
|
|
// which is far more dependable than trying to keep text on the screen.
|
|
#define SMS_DIAG_OFFSET 2376
|
|
|
|
static void smsDiagPublish(void)
|
|
{
|
|
volatile uint8_t *d = &audioAndHotspotDataBuffer.rawBuffer[SMS_DIAG_OFFSET];
|
|
uint16_t values[7];
|
|
|
|
values[0] = diagBursts;
|
|
values[1] = diagHeaders;
|
|
values[2] = diagAssembled;
|
|
values[3] = diagLastBlocks;
|
|
values[4] = blocksCollected;
|
|
values[5] = (uint16_t)rxState;
|
|
values[6] = diagTextLen;
|
|
|
|
d[0] = 'S'; d[1] = 'M'; d[2] = 'S'; d[3] = 'D';
|
|
|
|
for (uint8_t i = 0; i < 7; i++)
|
|
{
|
|
d[4 + (i * 2)] = (uint8_t)(values[i] & 0xFF);
|
|
d[5 + (i * 2)] = (uint8_t)(values[i] >> 8);
|
|
}
|
|
}
|
|
|
|
"""
|
|
s = s.replace(anchor, helper + anchor, 1)
|
|
|
|
# track the extracted text length too
|
|
s = s.replace("static uint8_t diagLastBlocks = 0;\n",
|
|
"static uint8_t diagLastBlocks = 0;\nstatic uint16_t diagTextLen = 0;\n", 1)
|
|
s = s.replace("\tlatest.text[t] = '\\0';\n",
|
|
"\tlatest.text[t] = '\\0';\n\tdiagTextLen = t;\n", 1)
|
|
|
|
# publish on every tick, so a read over USB always sees the current state
|
|
s = s.replace("void smsTick(void)\n{\n", "void smsTick(void)\n{\n\tsmsDiagPublish();\n\n", 1)
|
|
|
|
open(p, "w").write(s)
|
|
print("sms.c: diagnostics mirrored into the readable RAM window")
|