9.9 KiB
0DMRMaster (форк)
Частный DMR-мастер-сервер на Python: реализация протокола Homebrew DMR (того же, что используют BrandMeister и HBlink), к которому подключаются хотспоты и репитеры, плюс декодирующий UDP-прокси для анализа трафика и веб-дашборд. Позволяет поднять собственную закрытую DMR-сеть, не завязанную на публичные мастера.
Это форк проекта 0DMRMaster Александра Мокрова (UR6LKW), оригинал — https://github.com/olympy/0DMRMaster, лицензия GPL-3.0 (файл LICENSE). Копия взята для эксплуатации у себя — как мастер-сервер и как инструмент разбора DMR-пакетов рядом с XLX-рефлектором. Документация оригинального проекта (описание протокола, roadmap, список источников ETSI) сохранена в README.upstream.md.
Отличия от оригинала
2025-06-19— единственный собственный коммит:localmaster_EXAMPLE.pyпереименован вlocalmaster.py. Содержимое файла не изменено (similarity index 100%).
Смысл правки: в оригинале localmaster.py — локальный конфиг-файл запуска, он перечислен в .gitignore (/localmaster.py) и в репозитории лежит только пример. В этой копии рабочий файл запуска взят под контроль версий, чтобы разворачивать сервер клонированием без ручного копирования примера. Побочный эффект: любые локальные настройки (в том числе список разрешённых peer ID и паролей в ListPeerAuth) при последующих правках попадут в историю git.
Кода самого проекта (dmrtools/, api/, dashboard/) владелец не менял — он полностью соответствует апстриму по состоянию на 2025-06-01.
Стек
- Python 3.11+, asyncio (
asyncio.DatagramProtocolдля UDP). - FastAPI + uvicorn — HTTP API и раздача дашборда;
bitarray/bitstring— разбор битовых полей DMR;dmr-utils3(BPTC-декодирование Embedded LC); pydantic. Полный список —requirements.txt. - Дашборд — статический HTML + JS без фреймворков.
- Работает на Linux и Windows. Специального железа (AMBE-чипов) не требуется: голос не транскодируется, кодек AMBE пока не реализован.
Структура
localmaster.py— точка входа: наследникDMRMaster, в методеconfig()задаётся авторизация пиров и регистрируются приложения. Здесь же включается «попугай» (ParrotApp(9990)).udpproxylogger.py— второй исполняемый скрипт: UDP-прокси между хотспотом и чужим мастером с hex-логом и декодированием пакетов (L1 MMDVM + L2 ETSI).relog.py— повторный разбор ранее сохранённого лога (строкиHD: <hex>) тем же декодером.dmrtools/dmrmaster.py— каркас сервера: разбор аргументов командной строки, логирование, запуск UDP-сервера и API.dmrtools/dispatcher.py— маршрутизация пакетов между пирами и приложениями.dmrtools/peer.py,peer_controller.py— состояние подключённых репитеров/хотспотов и конечный автомат логина (RPTL/RPTK/RPTC).dmrtools/auth.py— политики доступа:AllowAllPeerAuth(пускать всех),DenyAllPeerAuth,ListPeerAuth({id: 'пароль'})со сверкой SHA-256 хэша с солью.dmrtools/call.py— сборка голосового вызова из потока пакетов.dmrtools/parrot_app.py— приложение-эхо: записывает вызов на свой ID и проигрывает обратно с задержкой.dmrtools/app.py— базовые интерфейсы приложений (App,IAppCallInterceptor,IAppDispatcher).dmrtools/udpproxy.py— многоклиентский UDP-прокси (сессия на каждый адрес клиента).dmrtools/network.py,asyncnetwork.py— интерфейсы отправки/приёма датаграмм и их asyncio-реализация.dmrtools/dmrproto/mmdvm_l1.py— пакеты Homebrew/MMDVM (DMRD, RPTL, RPTPING, DMRA и т.д.), расчёт хэша пароля.dmrtools/dmrproto/etsi_l2.py,lc_util.py,enums.py,base_fields.py,factory.py— разбор внутренней структуры DMR по ETSI TS 102 361: Link Control, тип голосового кадра, Talker Alias.dmrtools/pphex.py— hexdump для логов.api/dmrapi.py— FastAPI-приложение:/api/dashboard,/api/peers,/api/calls, монтирование статики.dashboard/index.html,dashboard/app.js— веб-дашборд, опрашивающий API.doc/dashboard-screenshot.png— скриншот дашборда для README.
Как запустить/установить
python -m venv venv
. venv/bin/activate
python -m pip install --upgrade pip
pip install -r requirements.txt
python localmaster.py
Остановка — Ctrl+C.
По умолчанию сервер слушает 62031/udp (DMR) и 8000/tcp (API и дашборд):
- API:
http://<сервер>:8000/api/dashboard - Дашборд:
http://<сервер>:8000/dashboard/index.html
UDP-прокси с логом:
python udpproxylogger.py <listenip> <listenport> <serverip> <serverport> [-l log.txt]
Повторный разбор лога:
python relog.py <входной-лог> [-l log.txt]
Конфигурация
Аргументы localmaster.py (обрабатываются в dmrtools/dmrmaster.py):
-i, --interface— интерфейс для DMR (по умолчанию0.0.0.0);-p, --port— UDP-порт DMR (по умолчанию62031);--web-interface— интерфейс API (по умолчанию0.0.0.0);--web-port— TCP-порт API (по умолчанию8000);-l, --log-file— файл лога;-d, --ll-debug— уровень логирования DEBUG вместо INFO.
Настройки в коде localmaster.py → DMRMasterLocal.config():
- политика доступа: сейчас активна
AllowAllPeerAuth()— подключиться может любой peer ID без пароля; закомментирована строка сListPeerAuth({1: 'pass1', 2: ''})— вариант со списком разрешённых ID и паролей; self.register_app(ParrotApp(9990))— ID «попугая»; уParrotAppтакже есть параметрыrepeat_delay(5 с) и прочие, задаваемые при создании.
Паролей и токенов в репозитории нет: pass1 в закомментированной строке — заглушка из примера. Реальные пароли пиров при вводе в эксплуатацию следует держать вне репозитория (см. .env/локальный незакоммиченный конфиг), тем более что localmaster.py здесь отслеживается git.
Состояние
Рабочий прототип (сам автор оригинала определяет статус как public alpha, версия 0.5, «не использовать в проде, интерфейсы могут меняться»). Последний коммит 2025-06-19 (собственный, переименование файла), последний коммит апстрима — 2025-06-01. Всего 17 коммитов.
Что не доделано
- Явный TODO в коде один:
dmrtools/dmrproto/lc_util.py:141—return None # Not supported; !!TODO: decode 7-bit(не декодируется 7-битный Talker Alias). - По roadmap апстрима (см.
README.upstream.md) не реализованы: файл конфигурации и конфигурация пользователей, полноценная маршрутизация (по слотам, подписки на группы), кодирование внутренних структур DMR, датапакеты/сообщения, кодек AMBE, запись голосовых вызовов в файлы, TA внутри DMRD, регистрация абонентов и резолв ID в позывные. - Собственных доработок форка нет — расхождение с оригиналом сводится к одному переименованию, поэтому обновление апстрима должно проходить без конфликтов, кроме файла
localmaster.py. localmaster.pyчислится в.gitignore, но при этом отслеживается — при правке локальных настроек их легко закоммитить случайно.