# Homebrew/MMDVM — что реально в эфире Сверено с исходниками MMDVMHost (`DMRDirectNetwork.cpp`), DMRGateway, HBlink3 (`hblink.py`) и спецификацией DL5DI/G4KLX/DG1HT 2015 года. **Где спецификация расходится с кодом — авторитетен код:** именно он крутится на каждом Pi-Star, а спецификацию никто до конца не реализовал. ## ⚠️ Тест-вектор из спецификации ОШИБОЧЕН — не «чините» по нему код Спецификация приводит: salt `0A7ED498`, пароль `DL5DI` → `cbf0e29abbd11c6573825d36a664e3064441f91fc815ac5dc5ca570d4c4a5f85`. Это значение получается из **ASCII-строки** `"0A7ED498"`, хотя тремя абзацами выше та же спецификация требует «трактовать 32-битное значение как битовый шаблон, а не как число». Проверено: ``` sha256(b'\x0a\x7e\xd4\x98' + b'DL5DI') = a763d5c7… ← так делают ВСЕ реализации sha256(b'0A7ED498' + b'DL5DI') = cbf0e29a… ← так написано в тест-векторе ``` Сырые байты используют MMDVMHost (`memcpy(in, m_salt, 4)`), HBlink3 (`bytes_4(SALT) + PASSPHRASE`) и наш `analyzer.auth_hash`. **Выбивается только бумажка.** Если «поправить» код под тест-вектор — вход перестанут проходить все хотспоты. ## Расхождения спецификации и реальности | | спецификация | на самом деле | |---|---|---| | Ответ мастера | `MSTACK` | **`RPTACK`** | | Keepalive | хотспот `MSTPING`, мастер `RPTPONG` | **хотспот `RPTPING`, мастер `MSTPONG`** | | RPTL | «семь байт» | **8 байт** | | Порядок в RPTC | callsign, затем id | **id@4, затем callsign@8** | | Описание/слоты | description 20 байт, поля слотов нет | **description 19@78 + slots 1@97** | | Длина DMRD | 53 | **53 ядро + BER/RSSI = 55 в эфире** | | Маски байта 15 | `0x01/0x02/0x0C/0xF0` | **`0x80/0x40/0x30/0x0F`** | | Период пинга | «раз в минуту» | **5–15 с** | ## Кадр DMRD | Смещение | Длина | Поле | |---:|---:|---| | 0 | 4 | `DMRD` | | 4 | 1 | последовательность | | 5 | 3 | src (uint24 BE) | | 8 | 3 | dst — talkgroup или абонент (uint24 BE) | | 11 | 4 | **peer_id (uint32 BE) — переписывается на получателя** | | 15 | 1 | биты | | 16 | 4 | stream_id — **непрозрачные байты, не число** | | 20 | 33 | бёрст DMR | | 53 | 1 | BER (расширение G4KLX) | | 54 | 1 | RSSI (расширение G4KLX) | Байт 15: `0x80` слот (0=TS1, 1=TS2) · `0x40` тип вызова (0=группа, 1=приват) · `0x30` тип кадра (`00` голос, `01` voice sync, `10` data sync) · `0x0F` тип данных либо номер голосового кадра. Начало передачи — data sync + `0x01`, конец — data sync + `0x02`. ## Три вещи, которые легко испортить 1. **Переписывать peer_id (11–14) на номер получателя.** Смысл поля — «ретранслятор, которого касается кадр на этом соединении». Делает `router.retarget()` через хук `encode_for`. MMDVMHost поле на приёме не проверяет, но DMRGateway на многих Pi-Star стоит между ним и сетью. 2. **Не возвращать кадр отправителю** — мгновенная петля в эфире на дуплексном ретрансляторе. Исключение — попугай, он отвечает намеренно и напрямую. 3. **Отправлять 55 байт, принимать 53/55/71.** DMRGateway молча выбрасывает 53-байтные кадры (`length == HOMEBREW_DATA_PACKET_LENGTH`). Терминаторы и заголовки регулярно теряются в эфире, поэтому начало передачи определяется ещё и сменой `stream_id`, а конец — таймаутом 360 мс (6 пропущенных голосовых кадров). ## Попугай (TG 9990) Записанные кадры проигрываются **дословно** — `payload[20:53]` как есть, меняется только `stream_id`. Это не оптимизация, а необходимость: в DMRhub пробовали пересобирать фрейминг, и получалось «секундное молчание» — рация видела вызов, но голос не декодировала. У кадров из рации уже корректные sync/EMB/slot-type/AMBE, трогать их нечего. Исключение — **приватный** эхотест: там нужен обратный адрес (`src=9990 → dst=вызывающий`), дословный повтор не годится, потому что в нём `dst=9990` и рация не примет вызов как «мне». Только для этого случая пересобираются LC-заголовок и терминатор (`dmr_voice` + `bptc`). Темп проигрывания — 60 мс на кадр (шаг слота TDMA), пауза перед началом — 1 с. Доставка — напрямую в хотспот вызывавшего, минуя маршрутизатор: так исключены петли и не портится last-heard. ## Источники - Спецификация: `DMRplus_IPSC_Protocol_for_HB_repeater.pdf` (DL5DI/G4KLX/DG1HT, 2015-07-26) - MMDVMHost `DMRDirectNetwork.cpp` @ `7008c62` (до удаления прямого доступа к мастерам) - DMRGateway `DMRNetwork.cpp`, HBlink3 `hblink.py`/`const.py`/`playback.py` - Проверенная реализация: `viktor/dmrhubv2`, откуда взяты `analyzer.py`, `server.py`, `parrot.py`, `dmr_voice.py`, `bptc.py`