
LoRa придумывали для датчиков: раз в несколько минут отправить десяток байт на пару километров и прожить на батарейке год. Говорить через неё голосом считается невозможным — канал слишком узкий. Мы проверили, насколько это «невозможно», и получился MeshTRX: бесплатная прошивка для недорогих плат Heltec WiFi LoRa 32 (тех же, что берут под Meshtastic), приложение для телефона и рация, которая работает там, где нет ни интернета, ни сотовой связи. Нажал кнопку — говоришь, отпустил — слушаешь.
Меня зовут Станислав (R7KIQ), я автор проекта. Ниже — как это устроено внутри: сколько байт уходит на 20 миллисекунд речи, почему голос не ходит через mesh‑сеть так же, как текст, во что обходится шифрование в пакете на 39 байт, какие ошибки нашлись только в поле — и сколько километров получается на самом деле, а не на бумаге.
Исходники: github.com/StanislavButkovsky/meshtrx. Сайт с прошивкой из браузера и документацией: meshtrx.ru.
Что это и из чего состоит
Плата. Heltec WiFi LoRa 32 V3, V4 (ревизия 4.2) или V4.3: ESP32-S3 + радиочип Semtech SX1262, диапазон 863–870 МГц. Никакой доработки, нужна только антенна на 868 МГц.
Прошивка. Ставится за минуту прямо из Chrome через Web Serial: meshtrx.ru/flash. Не понравится — плата так же за минуту прошивается обратно в Meshtastic, железо то же самое.
Приложение. Android (с сайта; в RuStore — на модерации), iPhone и настольный клиент на Python. Телефон подключается к плате по Bluetooth LE и служит экраном, микрофоном и динамиком.
Связь. Платы говорят друг с другом напрямую или через ретранслятор. Ретранслятором становится любая плата одной настройкой.
Кроме голоса: текстовые сообщения с отметками доставки, фото и файлы, карта и радар с позициями участников, вызов «всех» или одного абонента, шифрование эфира по кодовому слову группы, 23 канала с поиском самого тихого.

Почему голос по LoRa считается невозможным
Параметры радио в MeshTRX: SF7, полоса 250 кГц, избыточность 4/7. На бумаге это около 7,8 кбит/с — без учёта преамбулы, заголовков и пауз, так что на деле заметно меньше.
Для сравнения: голос в GSM — 13 кбит/с, самый скромный интернет‑звонок — 8–16 кбит/с. Привычные способы передать речь в такой канал не «работают плохо» — они в него просто не помещаются.
Codec2: голос как описание, а не как звук
Выход нашёлся в Codec2 Дэвида Роу. Это не обычный кодек, а вокодер: он не хранит звуковую волну совсем. В эфир уходит описание того, как была устроена речь в этот момент — основной тон, энергия, форма спектра, — а на другом конце речь синтезируется заново.
Режим 3200 — 3200 бит в секунду. Кадр — 20 мс звука, 160 отсчётов при 8 кГц, и он укладывается в 8 байт. Кодирование идёт на телефоне: приложение пишет микрофон, сжимает речь и отдаёт рации уже готовые кадры по Bluetooth. Прошивка и все клиенты используют один и тот же режим.
Что лежит в пакете
Каждые 80 мс уходит один пакет: четыре кадра Codec2 подряд — 32 байта — и 7 байт заголовка.

Байт | Поле | Зачем |
|---|---|---|
0 | type | аудио, текст, файл, служебное |
1 | channel | номер канала 0–22; старший бит — «нагрузка зашифрована» |
2 | seq | порядковый номер |
3 | flags | начало и конец передачи, голосовая активация |
4 | ttl | сколько ещё пересылок разрешено |
5–6 | sender | последние два байта MAC — кто говорит |
На SF7/250 кГц/4/7 такой пакет висит в эфире 53,4 мс. Пакеты идут каждые 80 мс, значит, пока человек говорит, канал занят на две трети. Это ключевое число: оно объясняет почти все решения дальше.
Почему голос не ходит по mesh‑сети
Текстовое сообщение — это один пакет. Ему нужно одно удачное окно в эфире: не дошло — приложение повторит через несколько секунд, и человек этого даже не заметит.
Голос так не умеет. Двенадцать пакетов в секунду, и каждый должен успеть вовремя: повтор, пришедший через полсекунды, уже бесполезен. А при занятости канала 67% ретранслятору просто некогда повторять: пока он передаёт копию, исходная рация уже шлёт следующий пакет.
Поэтому в MeshTRX голос идёт напрямую или через ретранслятор, а не через произвольную сеть из многих узлов, как текст в Meshtastic. TTL у пакета — 2. От зацикливания защищает память ретранслятора: он помнит, что уже переслал (кольцевой кеш на 128 записей; отправитель + номер + тип), и дубль отбрасывает.
На этом мы и споткнулись. Номер голосового пакета — один байт, и сначала он сбрасывался в ноль на каждом нажатии кнопки: «приёмник видит ноль — значит, новая фраза». А ретранслятор помнил пересланное тридцать секунд. Вторая фраза через пять секунд приходила с теми же номерами от того же отправителя — и отбрасывалась целиком как дубль. На стенде это было однозначно: первая фраза — 10 пакетов из 10, вторая — 0 из 10, четвёртая через 40 секунд — снова 10 из 10. В поле это звучало как «через ретранслятор голос доходит через раз».
Лечение: номер стал сквозным, начало фразы приёмник узнаёт по отдельному флагу, а память о голосе сократилась до трёх секунд — пакет живёт в сети доли секунды, этого хватает, чтобы отсечь эхо, и мало, чтобы забыть живую речь.

Шифрование в 39 байтах
Первое время эфир MeshTRX был открыт, и мы прямо писали об этом. Когда дошли руки, оказалось, что каждый байт криптографии стоит эфирного времени:
Пакет | Время в эфире | Занято канала |
|---|---|---|
без шифрования, 39 байт | 53,4 мс | 67% |
+ подпись 4 байта | 57,0 мс | 71% |
+ подпись 8 байт | 60,5 мс | 76% |
+ полная подпись 16 байт | 71,3 мс | 89% |
+ публичный ключ 32 байта | 85,6 мс | 107% |
Последняя строка — не опечатка: пакет с публичным ключом X25519 в канал не помещается, передача не успевает закончиться до начала следующей. Поэтому обмена ключами в каждом пакете нет и не будет.
Что получилось:
Общий ключ группы из кодового слова. На всех рациях вводится одна и та же фраза, из неё выводится 256-битный ключ (PBKDF2-SHA256, 50 000 итераций — около трёх секунд на ESP32-S3, это защита от перебора). Фраза в телефоне не хранится и в эфир не уходит.
Шифр — AES-256 в режиме CTR, длина шифротекста равна длине открытого текста. Заголовок остаётся открытым: по нему ретранслятор делает дедупликацию и TTL, не зная ключа.
Подпись — AES‑CMAC, укороченная до 6 байт, плюс 2 байта эпохи. Итого +8 байт к пакету. Округление символов LoRa таково, что шесть байт подписи стоят не дороже четырёх.
Отпечаток ключа — четыре символа, по которым группа сверяет ключ вслух, не диктуя само слово. Если рядом говорят чужим ключом, приложение это показывает.
Честно об ограничениях: это общий ключ на группу, а не сквозное шифрование между двумя людьми. Потерянная рация с ключом — повод сменить кодовое слово у всех.
23 канала и соседи по диапазону
На 868 МГц живёт много кто: домофоны, метеостанции, счётчики, автосигнализации. В городе это заметный и неравномерный фон. Рация умеет пройти по всем 23 каналам (863,15–869,75 МГц с шагом 300 кГц) и померить шум. Уровень берётся как максимум серии, а не среднее: помеха бьёт вспышками, и по среднему канал выглядит тихим, хотя раз в секунду в нём проезжает чужой пакет — ровно то, что убивает голос. Первый же замер на столе показал 15 дБ между самым тихим и самым шумным каналом.
Найти тихий канал мало — на него надо перевести всю группу, включая ретранслятор на крыше. Поэтому есть команда «сменить канал у всех»: рация трижды подряд рассылает «через десять секунд все на канал N», сама уходит последней, а станция, которая на новом канале никого не услышала, сама возвращается на старый.

Сообщение дойдёт, даже если телефон выключен
У телефона садится батарея, человек закрыл приложение — а рация на поясе продолжает работать. Если адресат без телефона, его рация принимает сообщение и складывает в память: кольцевой буфер на 8 КБ, а на платах с PSRAM — на 128 КБ. Когда места не хватает, выбрасываются самые старые. Отправитель видит отметку «принято нодой», а когда адресат подключит телефон, сообщение придёт с пометкой, сколько оно пролежало.
Попутно пришлось перестать врать про доставку. Раньше подтверждение терялось на обратном пути, и у отправителя стоял крестик «не доставлено» на сообщении, которое давно прочитали. Теперь у сообщения честные состояния: отправлено, принято нодой, доставлено.

Сколько километров на самом деле
В описании любого LoRa‑проекта стоит красивая цифра дальности, и почти всегда она для прямой видимости. Первые развёрнутые замеры от людей, которые ходят с рациями по городу: устойчивый голос до двух километров, дальше проходят текстовые сообщения. В плотной застройке голос сдаётся раньше — около километра, при том что текст по тому же маршруту ходит почти до двух. На прямой видимости дальше, а ретранслятор на возвышенности заметно растягивает связь.
Соседние узлы Meshtastic на медленных режимах добивают дальше — это правда, и это физика, а не качество прошивки: чем медленнее передача, тем дальше её слышно. Медленный режим голос не унесёт, поэтому MeshTRX сознательно работает на быстром.
Ошибки, которые нашлись только в поле
Самая дорогая находка — внешний усилитель на одной из ревизий платы. Приём работал, передача работала, связь была, только дальность — хуже ожидаемой. Оказалось, нога, которая переключает усилитель между передачей и приёмом, стояла не в том состоянии, и сигнал шёл мимо него. Уровень у соседней рации: −78 дБм до правки и −20 дБм после, разница 58 дБ. Ничего не падало и не ругалось — просто «работает» и «работает как задумано» оказались разными вещами. Отсюда же в мастере прошивки обязательный выбор ревизии платы: у V4 rev 4.2 и V4.3 разные усилители и разные выводы управления.
Похожая история с Wi‑Fi на ретрансляторе: лишний включённый передатчик рядом с приёмником давал на стенде ноль пересланных голосовых пакетов из двадцати. Подозрение пришло от участника сообщества, подтвердили его уже цифрами.
Как это проверяется
Две реальные платы на столе подключены к компьютеру, а вместо телефона — эмулятор протокола Bluetooth на Python. Он гоняет сценарии: голос туда и обратно, сообщения при «выключенном» телефоне, смену канала, ретранслятор. В прошивке для стенда есть тестовая консоль, через которую видно, что рация приняла, переслала и отбросила. Многие таблицы из этой статьи сняты именно так.
Стек: прошивка — PlatformIO + Arduino для ESP32-S3, RadioLib для SX1262, NimBLE‑Arduino для Bluetooth, Codec2, mbedTLS. Android — Kotlin, iOS — SwiftUI, настольный клиент — Python.
Попробовать и поучаствовать
Понадобятся две платы Heltec WiFi LoRa 32 (V3, V4 или V4.3) с антеннами на 868 МГц и два телефона. Прошивка — meshtrx.ru/flash, приложение — meshtrx.ru/download. Помните, что 863–870 МГц — диапазон для маломощных устройств, и правила его использования в разных странах свои.
Исходники и вопросы: github.com/StanislavButkovsky/meshtrx, обсуждения — Discussions
Сообщество, замеры из поля и самоделки: t.me/MeshTRX
Короткие ролики о возможностях: youtube.com/@meshtrx
Документация и статьи с подробностями: meshtrx.ru/docs, meshtrx.ru/articles
Проект некоммерческий, исходники открыты под лицензией CC BY‑NC 4.0: свободно для некоммерческого использования с указанием авторства. Больше всего проекту помогают замеры дальности, отчёты об ошибках и фото ваших сборок — антенн, корпусов, ретрансляторов на крышах.
73! Станислав, R7KIQ
Meshtastic® — зарегистрированный товарный знак его владельцев; MeshTRX с проектом Meshtastic не связан.

