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 удалений
+56
Просмотреть файл
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""Leave modem mode when the USB host goes away, instead of after a fixed timeout.
Stock firmware dropped out of hotspot mode if the host was quiet for 20 seconds, which
made the radio unusable as a permanently attached modem - so that timeout was disabled
on request. The side effect was that the radio then stayed a modem forever and had to be
power cycled by hand after every test.
This ties the exit to the actual USB connection state instead: while the host is present
the radio stays a modem no matter how quiet it is, and as soon as the cable is pulled (or
the host stops enumerating it) the radio returns to normal operation by itself.
"""
W = "/home/viktor/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application"
p = W + "/source/functions/hotspot.c"
s = open(p).read()
if "usbHostIsAttached" in s:
print("already patched")
raise SystemExit(0)
# a small helper next to the other statics
anchor = "static const uint32_t MMDVMHOST_TIMEOUT = MMDVMHOST_TIMEOUT_DISABLED;\n"
assert anchor in s, "timeout constant not found"
helper = anchor + """
// The USB device handle tells us whether a host is actually enumerating us. Losing that
// is what "the computer went away" really means; host silence alone is not.
extern USBD_HandleTypeDef hUsbDeviceFS;
static bool usbHostIsAttached(void)
{
return (hUsbDeviceFS.dev_state == USBD_STATE_CONFIGURED);
}
"""
s = s.replace(anchor, helper, 1)
# make sure the USB device header is included
if '#include "usb_device.h"' not in s:
inc_anchor = '#include "functions/ticks.h"\n'
assert inc_anchor in s, "include anchor missing"
s = s.replace(inc_anchor, inc_anchor + '#include "usb_device.h"\n', 1)
# both inactivity checks now also fire when the host has disconnected
old = """ if ((MMDVMHOST_TIMEOUT != 0) && (nonVolatileSettings.hotspotType == HOTSPOT_TYPE_MMDVM) &&
((ticksGetMillis() - mmdvmHostLastActiveTime) > MMDVMHOST_TIMEOUT))
"""
new = """ if ((nonVolatileSettings.hotspotType == HOTSPOT_TYPE_MMDVM) &&
((usbHostIsAttached() == false) ||
((MMDVMHOST_TIMEOUT != 0) && ((ticksGetMillis() - mmdvmHostLastActiveTime) > MMDVMHOST_TIMEOUT))))
"""
count = s.count(old)
assert count == 2, "expected 2 inactivity checks, found %d" % count
s = s.replace(old, new)
open(p, "w").write(s)
print("hotspot.c: modem mode now ends when the USB host disconnects (%d checks)" % count)