Децентрализованные mesh-сети

Полное руководство по
Mesh-сетям будущего

Исчерпывающий ресурс по Meshtastic, MeshCore и Reticulum — трём революционным технологиям децентрализованной связи. Узнайте, как создать независимую коммуникационную инфраструктуру без интернета и сотовых вышек.

50,000+
Узлов в сети
3
Протокола
100+
Стран
Возможностей

Что такое Mesh-сети?

Mesh-сети (ячеистые сети) — это децентрализованная сетевая топология, в которой каждый узел может выступать как клиент, сервер и маршрутизатор одновременно.

🔗

Децентрализация

Нет единой точки отказа. Каждый узел сети равноправен и может маршрутизировать трафик для других участников. Сеть живёт, пока жив хотя бы один узел.

P2P Без сервера Отказоустойчивость
📡

Радиосвязь

Использование LoRa, WiFi, Bluetooth и других радиотехнологий для создания сетей без зависимости от интернет-провайдеров и сотовых операторов.

LoRa WiFi BLE
🔒

Шифрование

Сквозное шифрование сообщений, аутентификация узлов и защита от прослушивания. Ваши данные принадлежат только вам.

AES-256 ECC PKI

Три технологии, одна цель

Meshtastic, MeshCore и Reticulum — три разных подхода к созданию децентрализованных сетей связи. Каждый решает задачу по-своему.

🟢

Meshtastic

Открытый проект для создания mesh-сетей на базе LoRa-радио. Идеален для обмена текстовыми сообщениями и GPS-координатами на больших расстояниях при минимальном энергопотреблении.

LoRa ESP32 nRF52 GPS
🔵

MeshCore

Библиотека для создания mesh-сетей поверх WiFi (ESP32). Обеспечивает высокоскоростную передачу данных в локальной mesh-сети без точки доступа.

WiFi ESP32 High-Speed
🟣

Reticulum

Универсальный сетевой стек для создания зашифрованных mesh-сетей поверх любых сред передачи. Поддерживает LXMF для обмена сообщениями и Sideband как клиент.

Python LXMF Multi-IF E2E

Как работает Mesh-сеть?

В mesh-сети каждый узел действует как ретранслятор, расширяя покрытие сети и обеспечивая множественные пути для доставки данных.

  • Инициализация узла
    Устройство включается, сканирует эфир и обнаруживает соседние узлы. Формируется таблица маршрутизации с информацией о доступных путях.
  • Обнаружение соседей
    Узел периодически отправляет beacon-пакеты, объявляя о своём присутствии. Соседние узлы получают эти пакеты и обновляют свои таблицы маршрутизации.
  • Маршрутизация
    Когда узел A хочет отправить сообщение узлу Z, он определяет оптимальный маршрут через промежуточные узлы (B, C, D...). Используются алгоритмы flooding или table-based routing.
  • Ретрансляция
    Каждый промежуточный узел получает пакет, проверяет адрес назначения и пересылает его дальше. Это позволяет преодолевать расстояния, значительно превышающие дальность прямой связи.
  • Доставка и подтверждение
    Конечный узел получает сообщение и отправляет подтверждение (ACK) обратно по цепочке. Если ACK не получен, отправитель может повторить передачу.
  • 💡

    Интересный факт: Mesh-сети могут масштабироваться практически бесконечно. Каждый новый узел не только потребляет ресурсы сети, но и увеличивает её пропускную способность и надёжность.

    Где применяются Mesh-сети?

    🏔️

    Походы и экспедиции

    Связь в горах, лесах и пустынях, где нет сотового покрытия. GPS-трекинг группы, обмен координатами и текстовыми сообщениями. Meshtastic идеален для этого благодаря LoRa с дальностью до 20+ км.

    🚨

    Чрезвычайные ситуации

    При стихийных бедствиях, когда инфраструктура связи разрушена, mesh-сети становятся единственным средством коммуникации. Быстрое развёртывание без зависимости от вышек.

    🏘️

    Локальные сообщества

    Соседские сети для обмена сообщениями, оповещений и координации. Community networks, независимые от провайдеров и корпораций.

    🎪

    Мероприятия и фестивали

    Связь на массовых мероприятиях, где сотовые сети перегружены. Координация волонтёров, организаторов и служб безопасности.

    🏭

    Промышленность и IoT

    Мониторинг датчиков на больших территориях: сельское хозяйство, трубопроводы, склады. MeshCore обеспечивает высокую скорость для передачи данных сенсоров.

    🔐

    Приватная коммуникация

    Для тех, кто ценит приватность. Сквозное шифрование, отсутствие центральных серверов и полная независимость от корпоративных платформ обмена сообщениями.

    Начните за 5 минут

    Создать свою mesh-сеть проще, чем кажется. Вот базовый путь:

    1
    Выбор
    2
    Покупка
    3
    Прошивка
    4
    Настройка
    5
    Связь!

    Совет: Для начала вам нужно минимум 2 устройства. Один узел — это просто радиоприёмник. Два узла — это уже сеть! Начните с Meshtastic на ESP32 + LoRa модуле — это самый доступный вариант.

    LoRa Mesh Network

    Meshtastic

    Открытый проект для создания децентрализованных mesh-сетей на базе LoRa-радио. Обмен сообщениями, GPS-координатами и телеметрией без интернета и сотовой связи.

    Что такое Meshtastic?

    Meshtastic — это проект с открытым исходным кодом, который позволяет создавать mesh-сети дальнего радиуса действия с использованием недорогих LoRa-радиомодулей. Проект начался в 2019 году и с тех пор вырос в глобальное сообщество с тысячами активных узлов по всему миру.

    Meshtastic использует технологию LoRa (Long Range) — метод модуляции с расширенным спектром, который обеспечивает связь на расстояния до 20+ км в условиях прямой видимости при потреблении энергии менее 100 мВт. Это делает Meshtastic идеальным решением для ситуаций, где важна дальность связи и автономность, а не высокая скорость передачи данных.

    📡

    Ключевая особенность: Meshtastic работает на частотах ISM-диапазона (868 МГц в Европе, 915 МГц в США, 433 МГц в Азии), которые не требуют лицензии для использования.

    Архитектура Meshtastic

    Meshtastic состоит из нескольких ключевых компонентов, работающих вместе для обеспечения надёжной mesh-связи.

    5
    Приложение
    Мобильное приложение (iOS/Android), Web-клиент, CLI
    BLE / WiFi / Serial
    4
    Транспорт
    Протокол Meshtastic: protobuf-сообщения, шифрование
    MeshProtobuf
    3
    Сеть
    Mesh-маршрутизация, flooding, hop-count
    Mesh Routing
    2
    Канал
    MAC-слой, управление доступом к среде, CSMA
    LoRa MAC
    1
    Физический
    LoRa-модуляция: SF7-SF12, BW 125/250/500 кГц
    LoRa PHY

    Принцип маршрутизации

    Meshtastic использует controlled flooding как основной механизм маршрутизации. Когда узел отправляет сообщение, оно широковещательно передаётся всем соседним узлам. Каждый узел, получивший сообщение, проверяет его уникальный ID и, если он ещё не видел это сообщение, ретранслирует его дальше. Для предотвращения бесконечных петель используется счётчик прыжков (hop_count), который ограничивает максимальное количество ретрансляций.

    Protobuf — Meshtastic MeshPacket
    // Определение пакета Meshtastic (protobuf)
    message MeshPacket {
        // Уникальный ID отправителя (4 байта)
        uint32 from = 1;
    
        // ID получателя (0 = broadcast)
        uint32 to = 2;
    
        // Индекс канала (для мультиканальности)
        uint32 channel = 3;
    
        // Зашифрованные данные или plaintext
        oneof payload_variant {
            Data decoded = 4;
            bytes encrypted = 5;
        }
    
        // Счётчик прыжков (TTL)
        uint32 hop_limit = 6;
        uint32 hop_start = 7;
    
        // ID пакета для дедупликации
        uint32 id = 8;
    
        // Время отправки (Unix timestamp)
        uint32 rx_time = 9;
    
        // RSSI и SNR последнего приёма
        float rx_rssi = 10;
        float rx_snr = 11;
    
        // Приоритет пакета
        enum Priority {
            UNSET = 0;
            MIN = 1;
            BACKGROUND = 10;
            DEFAULT = 64;
            RELIABLE = 100;
            ACK = 120;
            MAX = 255;
        }
        Priority priority = 12;
    }
    
    // Полезная нагрузка данных
    message Data {
        enum PortNum {
            UNKNOWN_APP = 0;
            TEXT_MESSAGE_APP = 1;
            POSITION_APP = 3;
            NODEINFO_APP = 4;
            ROUTING_APP = 5;
            TELEMETRY_APP = 67;
            NEIGHBORINFO_APP = 70;
            ATAK_PLUGIN = 71;
            PRIVATE_APP = 256;
        }
        PortNum portnum = 1;
        bytes payload = 2;
        bool want_response = 3;
        uint32 request_id = 4;
    }

    LoRa: основа Meshtastic

    LoRa (Long Range) — это технология модуляции с расширенным спектром на основе chirp spread spectrum (CSS), разработанная компанией Semtech.

    Параметры LoRa

    Параметр Значение Описание
    Spreading Factor SF7 — SF12 Определяет скорость и дальность. SF12 — самая медленная, но самая дальняя
    Bandwidth 125 / 250 / 500 кГц Ширина полосы. Уже = чувствительнее, но медленнее
    Coding Rate 4/5 — 4/8 Избыточность кодирования. Выше = надёжнее, но медленнее
    Частота 433 / 868 / 915 МГц ISM-диапазоны, зависят от региона
    Мощность +2 — +22 dBm Мощность передатчика. Зависит от модуля
    Чувствительность до -148 dBm Минимальный уровень сигнала для приёма
    Скорость 0.3 — 50 кбит/с Зависит от SF, BW и CR
    Дальность до 20+ км В условиях прямой видимости с хорошими антеннами

    Расчёт скорости LoRa

    Скорость передачи данных в LoRa определяется формулой:

    Python — Расчёт скорости LoRa
    import math
    
    def calculate_lora_datarate(sf, bw, cr, de=False, n_crc=True, ih=False):
        """
        Расчёт скорости передачи данных LoRa.
    
        Параметры:
            sf  : Spreading Factor (7-12)
            bw  : Bandwidth в кГц (125, 250, 500)
            cr  : Coding Rate (1-4, соответствует 4/5-4/8)
            de  : Low Data Rate Optimization
            n_crc: Наличие CRC
            ih  : Implicit Header mode
    
        Возвращает:
            Скорость в бит/с
        """
        # Symbol rate (символов в секунду)
        rs = (bw * 1000) / (2 ** sf)
    
        # Полезная нагрузка на символ
        cr_fraction = 4 / (cr + 4)
    
        # Расчёт скорости
        if sf <= 6:
            # SF6 использует другой режим
            datarate = rs * sf * cr_fraction
        else:
            # Стандартный режим SF7-SF12
            de_factor = 1 if de else 0
            sf_effective = sf - 2 * de_factor
            datarate = rs * sf_effective * cr_fraction
    
        return round(datarate, 2)
    
    
    # Примеры расчётов для разных конфигураций
    configs = [
        ("Fast (SF7, BW250)",    7,  250, 1),
        ("Default (SF11, BW125)", 11, 125, 1),
        ("Long Range (SF12, BW125)", 12, 125, 2),
        ("Medium (SF9, BW125)",  9,  125, 1),
    ]
    
    for name, sf, bw, cr in configs:
        rate = calculate_lora_datarate(sf, bw, cr)
        print(f"{name:35}: {rate:8} бит/с")
    
    # Вывод:
    # Fast (SF7, BW250)                 : 13671.88 бит/с
    # Default (SF11, BW125)             :   537.11 бит/с
    # Long Range (SF12, BW125)          :   268.55 бит/с
    # Medium (SF9, BW125)               :  2148.44 бит/с
    
    
    def calculate_time_on_air(payload_len, sf, bw, cr=1,
                              de=False, n_crc=True, ih=False):
        """
        Расчёт времени передачи пакета (Time on Air).
    
        Параметры:
            payload_len : длина полезной нагрузки в байтах
            sf, bw, cr  : параметры LoRa
            de          : Low Data Rate Optimization
            n_crc       : наличие CRC
            ih          : Implicit Header
    
        Возвращает:
            Время передачи в миллисекундах
        """
        # Количество символов преамбулы
        n_preamble = 8  # стандартное значение
    
        # Длительность символа
        t_sym = (2 ** sf) / (bw * 1000)  # в секундах
    
        # Время преамбулы
        t_preamble = (n_preamble + 4.25) * t_sym
    
        # Расчёт количества символов полезной нагрузки
        de_flag = 1 if de else 0
        ih_flag = 1 if ih else 0
        crc_flag = 1 if n_crc else 0
    
        numerator = 8 * payload_len - 4 * sf + 28 + 16 * crc_flag - 20 * ih_flag
        denominator = 4 * (sf - 2 * de_flag)
        n_payload = 8 + max(math.ceil(numerator / denominator) * (cr + 4), 0)
    
        # Общее время
        t_total = t_preamble + n_payload * t_sym
    
        return t_total * 1000  # в мс
    
    
    # Пример: время передачи 50-байтового сообщения
    toa = calculate_time_on_air(50, 11, 125)
    print(f"Time on Air: {toa:.1f} мс")
    # Time on Air: 1152.0 мс
    ⚠️

    Важно: В Европе (868 МГц) действуют ограничения duty cycle: 1% для большинства каналов. Это означает, что при Time on Air = 1 секунда вы должны ждать 99 секунд перед следующей передачей. Meshtastic автоматически управляет этим.

    Настройка Meshtastic

    Подробная инструкция по настройке устройства Meshtastic через CLI и мобильное приложение.

    Установка прошивки

    Bash — Установка Meshtastic CLI
    # Установка meshtastic CLI через pip
    $ pip install meshtastic
    
    # Проверка версии
    $ meshtastic --version
    # meshtastic 2.3.2
    
    # Подключение к устройству через serial
    $ meshtastic --port /dev/ttyUSB0 --info
    
    # Подключение через WiFi (если устройство в сети)
    $ meshtastic --host 192.168.1.100 --info
    
    # Подключение через BLE
    $ meshtastic --ble "Meshtastic_XXXX" --info

    Базовая конфигурация

    Bash — Конфигурация Meshtastic через CLI
    # Установка региона (EU_868 для Европы)
    $ meshtastic --set lora.region EU_868
    
    # Установка модемной конфигурации
    # Варианты: LONG_FAST, LONG_SLOW, LONG_MODERATE, SHORT_FAST, MEDIUM_SLOW и др.
    $ meshtastic --set lora.modem_preset LONG_FAST
    
    # Установка hop_limit (максимальное количество прыжков)
    $ meshtastic --set lora.hop_limit 3
    
    # Включение/выключение TX (для приёмников-ретрансляторов)
    $ meshtastic --set lora.tx_enabled true
    
    # Установка мощности передатчика (dBm)
    $ meshtastic --set lora.tx_power 20
    
    # Настройка канала (шифрование)
    $ meshtastic --set lora.use_psk true
    
    # Установка имени узла
    $ meshtastic --set owner "MyMeshNode-01"
    
    # Установка короткого имени (4 символа)
    $ meshtastic --set owner_short "M001"
    
    # Включение позиционного вещания
    $ meshtastic --set position.position_broadcast_secs 900
    
    # Настройка интервала телеметрии
    $ meshtastic --set device.telemetry_interval_secs 1800
    
    # Показать текущую конфигурацию
    $ meshtastic --info
    
    # Перезагрузка устройства
    $ meshtastic --reboot
    
    # Сброс к заводским настройкам
    $ meshtastic --reset

    Конфигурация через Python API

    Python — Meshtastic Python API
    import meshtastic
    import meshtastic.serial_interface
    import time
    
    
    class MeshtasticNode:
        """
        Класс для управления узлом Meshtastic через Python API.
        Поддерживает отправку/приём сообщений, чтение телеметрии
        и управление конфигурацией.
        """
    
        def __init__(self, port="/dev/ttyUSB0"):
            """Инициализация подключения к узлу."""
            self.interface = None
            self.port = port
            self.messages = []
            self.nodes = {}
    
        def connect(self):
            """Подключение к устройству через serial."""
            try:
                self.interface = meshtastic.serial_interface.SerialInterface(
                    devPath=self.port
                )
                print(f"✅ Подключено к {self.port}")
    
                # Получение информации об узле
                my_info = self.interface.getMyNodeInfo()
                print(f"📡 Мой узел: {my_info.get('user', {}).get('longName', 'Unknown')}")
                print(f"🆔 Node ID: !{my_info.get('num', 0):08x}")
    
                return True
            except Exception as e:
                print(f"❌ Ошибка подключения: {e}")
                return False
    
        def send_text(self, text, destination="^all"):
            """
            Отправка текстового сообщения.
    
            Args:
                text: Текст сообщения
                destination: ID получателя или "^all" для broadcast
            """
            if not self.interface:
                print("❌ Не подключено")
                return False
    
            try:
                self.interface.sendText(
                    text=text,
                    destinationId=destination,
                    wantAck=True,
                    wantResponse=False
                )
                print(f"📤 Отправлено: {text}")
                return True
            except Exception as e:
                print(f"❌ Ошибка отправки: {e}")
                return False
    
        def send_position(self, lat, lon, alt=0):
            """Отправка GPS-позиции."""
            if not self.interface:
                return False
    
            self.interface.sendPosition(
                latitude=lat,
                longitude=lon,
                altitude=alt
            )
            print(f"📍 Позиция отправлена: {lat}, {lon}, {alt}м")
    
        def get_nodes(self):
            """Получение списка всех известных узлов."""
            if not self.interface:
                return {}
    
            self.nodes = self.interface.nodes
            return self.nodes
    
        def print_node_list(self):
            """Вывод списка узлов в красивом формате."""
            nodes = self.get_nodes()
    
            print("\n📡 Известные узлы:")
            print("=" * 70)
            print(f"{'ID':12} {'Имя':20} {'Батарея':10} {'Последний':15}")
            print("=" * 70)
    
            for node_id, node in nodes.items():
                user = node.get('user', {})
                name = user.get('longName', 'Unknown')
                metrics = node.get('deviceMetrics', {})
                battery = metrics.get('batteryLevel', 0)
                last_seen = node.get('lastHeard', 0)
    
                if last_seen > 0:
                    time_diff = time.time() - last_seen
                    if time_diff < 60:
                        last_str = f"{int(time_diff)}с назад"
                    elif time_diff < 3600:
                        last_str = f"{int(time_diff/60)}м назад"
                    else:
                        last_str = f"{int(time_diff/3600)}ч назад"
                else:
                    last_str = "Никогда"
    
                battery_str = f"{battery}%" if battery > 0 else "N/A"
                print(f"{node_id:12} {name:20} {battery_str:10} {last_str:15}")
    
        def on_receive(self, packet, interface):
            """Обработчик входящих сообщений."""
            decoded = packet.get('decoded', {})
            msg_type = decoded.get('portnum', '')
    
            if msg_type == 'TEXT_MESSAGE_APP':
                text = decoded.get('payload', b'').decode('utf-8')
                sender = packet.get('fromId', 'Unknown')
                print(f"\n💬 Сообщение от {sender}: {text}")
                self.messages.append({
                    'from': sender,
                    'text': text,
                    'time': time.time()
                })
    
            elif msg_type == 'POSITION_APP':
                pos = decoded.get('position', {})
                print(f"\n📍 Позиция от {packet.get('fromId', '?')}")
    
            elif msg_type == 'TELEMETRY_APP':
                telemetry = decoded.get('telemetry', {})
                print(f"\n📊 Телеметрия от {packet.get('fromId', '?')}")
    
        def disconnect(self):
            """Закрытие подключения."""
            if self.interface:
                self.interface.close()
                print("🔌 Отключено")
    
    
    # === Пример использования ===
    if __name__ == "__main__":
        node = MeshtasticNode(port="/dev/ttyUSB0")
    
        if node.connect():
            # Отправка broadcast-сообщения
            node.send_text("Привет, mesh-сеть! 📡")
    
            # Отправка позиции
            node.send_position(55.7558, 37.6173, 150)
    
            # Вывод списка узлов
            node.print_node_list()
    
            # Ожидание сообщений (30 секунд)
            print("\n⏳ Ожидание сообщений...")
            time.sleep(30)
    
            # Отключение
            node.disconnect()

    Каналы и шифрование

    Meshtastic поддерживает до 8 каналов одновременно. Каждый канал может иметь свой ключ шифрования и настройки. Каналы позволяют создавать изолированные группы внутри одной mesh-сети.

    Bash — Настройка каналов Meshtastic
    # Показать текущие каналы
    $ meshtastic --get channels
    
    # Добавить новый канал с именем "Team-Alpha"
    $ meshtastic --ch-add Team-Alpha
    
    # Установить PSK (Pre-Shared Key) для канала
    $ meshtastic --ch-set psk "1PG7OiApB1nwvP+rz05pAQ==" --ch-index 1
    
    # Установить канал как UPLINK (передача в интернет)
    $ meshtastic --ch-set uplink_enabled true --ch-index 1
    
    # Установить канал как DOWNLINK (приём из интернета)
    $ meshtastic --ch-set downlink_enabled true --ch-index 1
    
    # Удалить канал
    $ meshtastic --ch-del --ch-index 1
    
    # Переключиться на канал по умолчанию
    $ meshtastic --set lora.region EU_868
    
    # Генерация случайного PSK
    $ meshtastic --ch-set psk random --ch-index 1
    
    # Экспорт URL канала для шаринга
    $ meshtastic --ch-url --ch-index 0
    # https://meshtastic.org/e/... (длинная ссылка с настройками канала)
    🔒

    Безопасность: По умолчанию Meshtastic использует общий ключ для всех устройств на канале. Для приватной коммуникации обязательно установите собственный PSK. Ключ должен быть известен только участникам вашей группы.

    MQTT-интеграция

    Meshtastic поддерживает подключение к MQTT-брокеру для связи между локальными mesh-сетями через интернет.

    Bash — Настройка MQTT в Meshtastic
    # Включение MQTT
    $ meshtastic --set mqtt.enabled true
    
    # Установка адреса MQTT-брокера
    $ meshtastic --set mqtt.address "mqtt.meshtastic.org"
    
    # Установка имени пользователя и пароля
    $ meshtastic --set mqtt.username "meshdev"
    $ meshtastic --set mqtt.password "large4cats"
    
    # Корневой топик
    $ meshtastic --set mqtt.root "msh"
    
    # Включение шифрования MQTT
    $ meshtastic --set mqtt.encryption_enabled true
    
    # Включение JSON-вывода
    $ meshtastic --set mqtt.json_enabled true
    
    # Включение TLS
    $ meshtastic --set mqtt.tls_enabled true
    
    # Карта (Map reporting)
    $ meshtastic --set mqtt.map_reporting_enabled true
    $ meshtastic --set mqtt.map_report_settings.publish_interval_secs 900
    $ meshtastic --set mqtt.map_report_settings.position_precision 13

    Роли узлов

    📱

    CLIENT

    Стандартная роль. Устройство работает как клиент mesh-сети, отправляет и получает сообщения, ретранслирует трафик.

    📡

    ROUTER

    Оптимизирован для ретрансляции. Не отправляет собственные beacon-пакеты, но активно маршрутизирует чужой трафик. Идеален для стационарных ретрансляторов.

    🔇

    CLIENT_MUTE

    Клиент, который не ретранслирует чужие сообщения. Полезен для устройств с ограниченным питанием.

    📻

    REPEATER

    Чистый ретранслятор. Не имеет пользовательского интерфейса, только пересылает пакеты между узлами.

    🌐

    ROUTER_CLIENT

    Комбинация роутера и клиента. Ретранслирует трафик и может отправлять/получать собственные сообщения.

    📍

    TRACKER

    Оптимизирован для GPS-трекинга. Регулярно отправляет свою позицию, минимизируя другой трафик.

    Bash — Установка роли узла
    # Установка роли ROUTER (ретранслятор)
    $ meshtastic --set device.role ROUTER
    
    # Установка роли CLIENT (обычный клиент)
    $ meshtastic --set device.role CLIENT
    
    # Установка роли TRACKER (GPS-трекер)
    $ meshtastic --set device.role TRACKER
    
    # Установка роли SENSOR (датчик)
    $ meshtastic --set device.role SENSOR
    
    # Установка роли TAK (для ATAK)
    $ meshtastic --set device.role TAK
    WiFi Mesh Network

    MeshCore

    Высокопроизводительная mesh-сеть на базе WiFi для ESP32. Скорость передачи данных до нескольких Мбит/с без точки доступа и интернет-подключения.

    Что такое MeshCore?

    MeshCore — это библиотека для создания самоорганизующихся mesh-сетей на базе чипов ESP32 с использованием WiFi. В отличие от Meshtastic, который использует LoRa для дальней связи с низкой скоростью, MeshCore обеспечивает высокоскоростную передачу данных на средних расстояниях (до 100-500 метров между узлами).

    MeshCore реализует протокол ESP-MESH — сетевой протокол, встроенный в ESP-IDF, который позволяет множеству устройств ESP32 формировать единую mesh-сеть без необходимости в центральном маршрутизаторе или точке доступа. Каждый узел может общаться с любым другим узлом в сети, а данные автоматически маршрутизируются через промежуточные узлы.

    Ключевое отличие: MeshCore обеспечивает скорость передачи данных в тысячи раз выше, чем Meshtastic (Мбит/с против кбит/с), но на значительно меньших расстояниях.

    Архитектура MeshCore

    MeshCore использует древовидную топологию с автоматическим выбором корневого узла. Корневой узел (root node) может подключаться к внешней WiFi-сети для обеспечения доступа в интернет для всей mesh-сети.

    5
    Приложение
    Пользовательские данные, MQTT, HTTP, OTA
    User App
    4
    Транспорт
    TCP/UDP, надёжная доставка
    TCP/UDP
    3
    Сеть
    IP-маршрутизация, mesh-протокол
    ESP-MESH
    2
    Канал
    WiFi MAC, CSMA/CA, управление соединениями
    WiFi MAC
    1
    Физический
    WiFi 802.11 b/g/n, 2.4 ГГц
    WiFi PHY

    Разработка с MeshCore

    Базовая инициализация (Arduino/ESP-IDF)

    C++ — MeshCore базовая инициализация (Arduino)
    /*
     * MeshCore — Базовый пример узла mesh-сети
     *
     * Этот пример демонстрирует создание простого узла MeshCore,
     * который может отправлять и получать сообщения в mesh-сети.
     *
     * Требуемое оборудование:
     *   - ESP32 (любая модель с WiFi)
     *   - USB-кабель для прошивки
     *
     * Библиотеки:
     *   - MeshCore (https://github.com/...)
     *   - WiFi
     */
    
    #include <WiFi.h>
    #include <esp_wifi.h>
    #include <esp_mesh.h>
    #include <esp_mesh_internal.h>
    #include <MeshCore.h>
    
    // === Конфигурация сети ===
    #define MESH_SSID       "MeshCore_Network"
    #define MESH_PASSWORD   "mesh_secure_pass"
    #define MESH_PORT       5555
    #define MESH_CHANNEL    6
    #define MESH_MAX_LAYER  6     // Максимальная глубина дерева
    
    // === Глобальные переменные ===
    MeshCore mesh;
    bool isRoot = false;
    int nodeLayer = 0;
    uint32_t lastSendTime = 0;
    const uint32_t SEND_INTERVAL = 5000; // 5 секунд
    
    // === Callback-функции ===
    
    // Вызывается при получении сообщения
    void onReceive(const uint8_t* data, size_t len, const mesh_addr_t& from) {
        char message[256] = {0};
        memcpy(message, data, min(len, sizeof(message) - 1));
    
        Serial.printf(
            "📨 Получено от " MACSTR ": %s\n",
            MAC2STR(from.addr), message
        );
    
        // Обработка специальных команд
        if (strcmp(message, "STATUS") == 0) {
            char response[128];
            snprintf(response, sizeof(response),
                "Node: %s, Layer: %d, RSSI: %d",
                WiFi.macAddress().c_str(),
                nodeLayer,
                WiFi.RSSI()
            );
            mesh.send((const uint8_t*)response, strlen(response));
        }
    }
    
    // Вызывается при изменении состояния mesh
    void onMeshEvent(mesh_event_t* event) {
        switch (event->id) {
            case MESH_EVENT_STARTED:
                Serial.println("🟢 Mesh-сеть запущена");
                break;
    
            case MESH_EVENT_CONNECTED:
                Serial.println("🔗 Подключено к mesh-сети");
                break;
    
            case MESH_EVENT_ROOT_ADDRESS:
                Serial.println("👑 Этот узел стал ROOT");
                isRoot = true;
                break;
    
            case MESH_EVENT_PARENT_CONNECTED:
                Serial.println("📡 Подключено к родительскому узлу");
                nodeLayer = event->info.parent_connected.self_layer;
                Serial.printf("   Уровень в дереве: %d\n", nodeLayer);
                break;
    
            case MESH_EVENT_PARENT_DISCONNECTED:
                Serial.println("❌ Потеряна связь с родителем");
                break;
    
            case MESH_EVENT_NO_PARENT_FOUND:
                Serial.println("⚠️ Родительский узел не найден");
                break;
    
            case MESH_EVENT_CHILD_CONNECTED:
                Serial.printf("👶 Новый дочерний узел подключён\n");
                break;
    
            case MESH_EVENT_CHILD_DISCONNECTED:
                Serial.printf("👋 Дочерний узел отключился\n");
                break;
    
            case MESH_EVENT_ROUTING_TABLE_CHANGE:
                Serial.println("🗺️ Таблица маршрутизации обновлена");
                break;
    
            default:
                break;
        }
    }
    
    void setup() {
        Serial.begin(115200);
        Serial.println("\n=== MeshCore Node ===");
    
        // Инициализация WiFi в режиме mesh
        WiFi.mode(WIFI_MODE_APSTA);
    
        // Конфигурация mesh-сети
        mesh_cfg_t cfg = {0};
        cfg.channel = MESH_CHANNEL;
        cfg.router.ssid = MESH_SSID;
        cfg.router.password = MESH_PASSWORD;
        cfg.mesh_id = {0x77, 0x77, 0x77, 0x77, 0x77, 0x77};
        cfg.mesh_ap.max_connection = 6;
        cfg.crypto_funcs = &g_wifi_default_mesh_crypto_funcs;
    
        // Инициализация MeshCore
        if (mesh.init(&cfg)) {
            Serial.println("✅ MeshCore инициализирован");
        } else {
            Serial.println("❌ Ошибка инициализации MeshCore");
            return;
        }
    
        // Установка callback-функций
        mesh.onReceive(onReceive);
        mesh.onEvent(onMeshEvent);
    
        // Запуск mesh-сети
        mesh.start();
    
        Serial.println("🚀 Mesh-сеть запущена, ожидание подключения...");
    }
    
    void loop() {
        // Обработка mesh-событий
        mesh.loop();
    
        // Периодическая отправка статуса
        uint32_t now = millis();
        if (now - lastSendTime > SEND_INTERVAL) {
            lastSendTime = now;
    
            char status[128];
            snprintf(status, sizeof(status),
                "Heartbeat from %s | Layer: %d | RSSI: %d dBm | FreeMem: %d",
                WiFi.macAddress().c_str(),
                nodeLayer,
                WiFi.RSSI(),
                ESP.getFreeHeap()
            );
    
            if (mesh.send((const uint8_t*)status, strlen(status))) {
                Serial.printf("📤 Отправлен heartbeat\n");
            }
        }
    
        delay(100);
    }

    MeshCore с ESP-IDF (продвинутый)

    C — ESP-MESH с ESP-IDF
    /*
     * ESP-MESH — Продвинутый пример с ESP-IDF
     *
     * Демонстрирует:
     *   - Создание mesh-сети с ESP-MESH
     *   - Обработку событий сети
     *   - Передачу данных между узлами
     *   - OTA-обновления через mesh
     *   - Мониторинг состояния сети
     */
    
    #include <string.h>
    #include <esp_log.h>
    #include <esp_wifi.h>
    #include <esp_event.h>
    #include <esp_mesh.h>
    #include <esp_mesh_internal.h>
    #include <nvs_flash.h>
    #include <freertos/FreeRTOS.h>
    #include <freertos/task.h>
    #include <freertos/queue.h>
    
    static const char* TAG = "mesh_main";
    
    // Конфигурация
    #define MESH_SSID      "ESP-MESH-NET"
    #define MESH_PASSWD    "meshpassword"
    #define MESH_PORT      5555
    #define RX_SIZE        1500
    #define TX_SIZE        1500
    #define MESH_CHANNEL   6
    
    // Глобальные переменные
    static bool is_mesh_connected = false;
    static mesh_addr_t mesh_parent_addr;
    static int mesh_layer = -1;
    static esp_netif_t* netif_sta = NULL;
    static esp_netif_t* netif_ap = NULL;
    
    // Очередь сообщений
    static QueueHandle_t mesh_msg_queue;
    
    // Структура сообщения
    typedef struct {
        mesh_addr_t from;
        uint8_t data[RX_SIZE];
        int len;
        mesh_data_t mesh_data;
    } mesh_message_t;
    
    // ============================================
    // WiFi Event Handler
    // ============================================
    static void wifi_event_handler(void* arg,
        esp_event_base_t event_base,
        int32_t event_id,
        void* event_data)
    {
        if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
            ESP_LOGI(TAG, "WiFi STA started");
        } else if (event_base == WIFI_EVENT &&
                   event_id == WIFI_EVENT_STA_DISCONNECTED) {
            ESP_LOGI(TAG, "WiFi STA disconnected");
            is_mesh_connected = false;
        }
    }
    
    // ============================================
    // IP Event Handler
    // ============================================
    static void ip_event_handler(void* arg,
        esp_event_base_t event_base,
        int32_t event_id,
        void* event_data)
    {
        if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
            ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data;
            ESP_LOGI(TAG, "Got IP: " IPSTR,
                IP2STR(&event->ip_info.ip));
        }
    }
    
    // ============================================
    // Mesh Event Handler
    // ============================================
    static void mesh_event_handler(void* arg,
        esp_event_base_t event_base,
        int32_t event_id,
        void* event_data)
    {
        mesh_event_t* event = (mesh_event_t*)event_data;
    
        switch (event_id) {
        case MESH_EVENT_STARTED: {
            ESP_LOGI(TAG,
                "MESH_EVENT_STARTED: Mesh network initialized");
            break;
        }
    
        case MESH_EVENT_STOPPED: {
            ESP_LOGI(TAG,
                "MESH_EVENT_STOPPED: Mesh network stopped");
            is_mesh_connected = false;
            break;
        }
    
        case MESH_EVENT_CHILD_CONNECTED: {
            mesh_event_child_connected_t* child_connected =
                (mesh_event_child_connected_t*)event_data;
            ESP_LOGI(TAG,
                "MESH_EVENT_CHILD_CONNECTED: child " MACSTR
                " at layer %d",
                MAC2STR(child_connected->mac),
                child_connected->self_layer);
            break;
        }
    
        case MESH_EVENT_CHILD_DISCONNECTED: {
            mesh_event_child_disconnected_t* child_disconnected =
                (mesh_event_child_disconnected_t*)event_data;
            ESP_LOGI(TAG,
                "MESH_EVENT_CHILD_DISCONNECTED: child " MACSTR,
                MAC2STR(child_disconnected->mac));
            break;
        }
    
        case MESH_EVENT_ROUTING_TABLE_CHANGE: {
            mesh_event_routing_table_change_t* routing =
                (mesh_event_routing_table_change_t*)event_data;
            ESP_LOGI(TAG,
                "MESH_EVENT_ROUTING_TABLE_CHANGE: size %d, capacity %d",
                routing->rt_size, routing->rt_capacity);
            break;
        }
    
        case MESH_EVENT_PARENT_CONNECTED: {
            mesh_event_connected_t* connected =
                (mesh_event_connected_t*)event_data;
            is_mesh_connected = true;
            mesh_layer = connected->self_layer;
            memcpy(&mesh_parent_addr, &connected->parent_bssid,
                sizeof(mesh_addr_t));
            ESP_LOGI(TAG,
                "MESH_EVENT_PARENT_CONNECTED: layer %d, parent " MACSTR,
                mesh_layer, MAC2STR(connected->parent_bssid));
            break;
        }
    
        case MESH_EVENT_PARENT_DISCONNECTED: {
            ESP_LOGI(TAG,
                "MESH_EVENT_PARENT_DISCONNECTED");
            is_mesh_connected = false;
            mesh_layer = -1;
            break;
        }
    
        case MESH_EVENT_NO_PARENT_FOUND: {
            ESP_LOGI(TAG,
                "MESH_EVENT_NO_PARENT_FOUND: scan done, %d APs found",
                event->info.no_parent.scan_size);
            break;
        }
    
        case MESH_EVENT_ROOT_ADDRESS: {
            mesh_event_root_address_t* root_addr =
                (mesh_event_root_address_t*)event_data;
            ESP_LOGI(TAG,
                "MESH_EVENT_ROOT_ADDRESS: root " MACSTR,
                MAC2STR(root_addr->addr));
            break;
        }
    
        case MESH_EVENT_TODS_STATE: {
            mesh_event_toDS_state_t* toDs_state =
                (mesh_event_toDS_state_t*)event_data;
            ESP_LOGI(TAG,
                "MESH_EVENT_TODS_STATE: %d", *toDs_state);
            break;
        }
    
        default:
            ESP_LOGI(TAG, "Mesh event: %ld", event_id);
            break;
        }
    }
    
    // ============================================
    // TX Task — отправка данных
    // ============================================
    static void mesh_tx_task(void* arg)
    {
        mesh_data_t data;
        uint8_t tx_buf[TX_SIZE] = {0};
        int tx_count = 0;
    
        data.data = tx_buf;
        data.size = 0;
        data.proto = MESH_PROTO_BIN;
        data.tos = MESH_TOS_P2P;
    
        while (1) {
            // Формирование сообщения
            int len = snprintf((char*)tx_buf, TX_SIZE,
                "{"
                "\"node\":\"%s\","
                "\"layer\":%d,"
                "\"seq\":%d,"
                "\"rssi\":%d,"
                "\"heap\":%lu,"
                "\"uptime\":%lu"
                "}",
                "ESP32-Node",
                mesh_layer,
                tx_count++,
                esp_wifi_sta_get_ap_info(NULL) == ESP_OK ?
                    esp_wifi_get_rssi() : 0,
                (unsigned long)esp_get_free_heap_size(),
                (unsigned long)(xTaskGetTickCount() * portTICK_PERIOD_MS / 1000)
            );
    
            data.size = len;
    
            // Отправка родительскому узлу (вверх по дереву)
            esp_err_t err = esp_mesh_send(
                NULL,        // NULL = отправить родителю
                &data,
                MESH_DATA_TODS,  // Направление: к корню
                NULL,
                0
            );
    
            if (err == ESP_OK) {
                ESP_LOGI(TAG, "TX: sent %d bytes, seq=%d", len, tx_count);
            } else {
                ESP_LOGW(TAG, "TX: failed, err=%d", err);
            }
    
            vTaskDelay(5000 / portTICK_PERIOD_MS);
        }
    }
    
    // ============================================
    // RX Task — приём данных
    // ============================================
    static void mesh_rx_task(void* arg)
    {
        mesh_data_t data;
        uint8_t rx_buf[RX_SIZE] = {0};
        int flag = 0;
    
        data.data = rx_buf;
        data.size = RX_SIZE;
    
        while (1) {
            mesh_addr_t from;
            data.size = RX_SIZE;
    
            esp_err_t err = esp_mesh_recv(
                &from,
                &data,
                portMAX_DELAY,
                &flag,
                NULL,
                0
            );
    
            if (err == ESP_OK) {
                // Обработка полученного сообщения
                ESP_LOGI(TAG,
                    "RX: %d bytes from " MACSTR ", flag=0x%04x",
                    data.size, MAC2STR(from.addr), flag);
    
                // Парсинг JSON-сообщения
                if (data.proto == MESH_PROTO_BIN) {
                    char msg_str[RX_SIZE] = {0};
                    memcpy(msg_str, data.data,
                        min(data.size, RX_SIZE - 1));
                    ESP_LOGI(TAG, "RX data: %s", msg_str);
    
                    // Отправка в очередь для дальнейшей обработки
                    mesh_message_t msg;
                    msg.from = from;
                    msg.len = data.size;
                    memcpy(msg.data, data.data, data.size);
                    xQueueSend(mesh_msg_queue, &msg, 0);
                }
            } else {
                ESP_LOGW(TAG, "RX: error=%d", err);
            }
        }
    }
    
    // ============================================
    // Network Monitor Task
    // ============================================
    static void mesh_monitor_task(void* arg)
    {
        while (1) {
            int layer = -1;
            int node_count = 0;
            mesh_assoc_t assoc;
    
            // Получение текущего уровня
            esp_mesh_get_layer(&layer);
    
            // Получение количества дочерних узлов
            esp_mesh_get_routing_table_size(&node_count);
    
            // Получение информации о родителе
            esp_mesh_get_parent_bssid(&assoc);
    
            ESP_LOGI(TAG,
                "📊 Network: layer=%d, nodes=%d, parent_rssi=%d, heap=%lu",
                layer,
                node_count,
                assoc.rssi,
                (unsigned long)esp_get_free_heap_size()
            );
    
            vTaskDelay(10000 / portTICK_PERIOD_MS);
        }
    }
    
    // ============================================
    // Main — app_main
    // ============================================
    void app_main(void)
    {
        // Инициализация NVS
        esp_err_t ret = nvs_flash_init();
        if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
            ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
            nvs_flash_erase();
            nvs_flash_init();
        }
    
        // Инициализация сетевого стека
        esp_netif_init();
        esp_event_loop_create_default();
    
        // Создание сетевых интерфейсов
        netif_sta = esp_netif_create_default_wifi_sta();
        netif_ap = esp_netif_create_default_wifi_ap();
    
        // Инициализация WiFi
        wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
        esp_wifi_init(&cfg);
    
        // Регистрация обработчиков событий
        esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
            &wifi_event_handler, NULL);
        esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
            &ip_event_handler, NULL);
        esp_event_handler_register(MESH_EVENT, ESP_EVENT_ANY_ID,
            &mesh_event_handler, NULL);
    
        // Конфигурация mesh
        mesh_cfg_t mesh_cfg = {0};
        uint8_t mesh_id[6] = {0x77, 0x77, 0x77,
                                  0x77, 0x77, 0x77};
    
        mesh_cfg.channel = MESH_CHANNEL;
        mesh_cfg.router.ssid = MESH_SSID;
        mesh_cfg.router.password = MESH_PASSWD;
        memcpy(&mesh_cfg.mesh_id, mesh_id, 6);
        mesh_cfg.mesh_ap.max_connection = 10;
    
        // Запуск mesh
        esp_mesh_init();
        esp_mesh_set_config(&mesh_cfg);
        esp_mesh_start();
    
        // Создание очереди сообщений
        mesh_msg_queue = xQueueCreate(32, sizeof(mesh_message_t));
    
        // Запуск задач
        xTaskCreate(mesh_tx_task, "mesh_tx",
            4096, NULL, 5, NULL);
        xTaskCreate(mesh_rx_task, "mesh_rx",
            4096, NULL, 5, NULL);
        xTaskCreate(mesh_monitor_task, "mesh_mon",
            4096, NULL, 3, NULL);
    
        ESP_LOGI(TAG, "🚀 MeshCore node started!");
    }

    Производительность MeshCore

    ~5 Мбит/с
    Скорость (1 hop)
    ~1000
    Макс. узлов
    6
    Макс. слоёв
    ~100м
    Дальность (hop)

    Зависимость скорости от количества прыжков

    1 hop (прямая связь) 5.0 Мбит/с
    2 hops 2.8 Мбит/с
    3 hops 1.5 Мбит/с
    4 hops 0.8 Мбит/с
    5 hops 0.4 Мбит/с
    6 hops (максимум) 0.2 Мбит/с
    ⚠️

    Обратите внимание: Скорость падает с каждым прыжком, так как каждый промежуточный узел должен получить и переслать пакет. Для максимальной производительности старайтесь минимизировать количество hops между узлами.

    Universal Mesh Network Stack

    Reticulum

    Универсальный сетевой стек для создания зашифрованных mesh-сетей поверх любых сред передачи данных. Поддержка LXMF, Sideband и множества интерфейсов.

    Что такое Reticulum?

    Reticulum Network Stack — это криптографически защищённый сетевой стек, разработанный Марком Квистом (markqvist). Он предназначен для создания надёжных mesh-сетей поверх практически любых физических сред передачи: LoRa, WiFi, Ethernet, serial, packet radio, I2P и других.

    Reticulum не привязан к конкретной технологии связи. Вместо этого он предоставляет абстрактный сетевой слой, который может работать поверх любого интерфейса (Interface). Это делает Reticulum невероятно гибким: вы можете создать сеть, в которой одни узлы связаны через LoRa, другие через WiFi, а третьи через Ethernet — и все они будут частью единой mesh-сети.

    🔐

    Криптография: Reticulum использует X25519 для обмена ключами, Ed25519 для цифровых подписей и AES-256 для шифрования данных. Каждый узел имеет уникальный 256-битный адрес, производный от его публичного ключа.

    Архитектура Reticulum

    6
    Приложение
    LXMF (сообщения), Nomad Network, Sideband, RNS Explorer
    LXMF / NomadNet
    5
    Сессия
    Управление сессиями, линки, надёжная доставка
    RNS Link
    4
    Транспорт
    Маршрутизация, transport-узлы, proof-of-delivery
    RNS Transport
    3
    Сеть
    Адресация (256-bit), идентификация, announce
    RNS Identity
    2
    Канал
    Абстракция интерфейса, управление средой
    RNS Interface
    1
    Физический
    LoRa, WiFi, Ethernet, Serial, Packet Radio, I2P...
    Multi-PHY

    Ключевые концепции

    🔑

    Identity (Идентификация)

    Каждый узел имеет криптографическую идентичность — пару ключей X25519/Ed25519. Адрес узла — это хеш его публичного ключа. Это обеспечивает уникальность и аутентификацию без центрального органа.

    📢

    Announce (Объявление)

    Узлы периодически отправляют announce-пакеты, содержащие их публичный ключ и аспекты (имена сервисов). Это позволяет другим узлам обнаруживать сервисы и строить маршруты.

    🔗

    Link (Связь)

    Link — это зашифрованное соединение между двумя узлами. Устанавливается через обмен ключами Диффи-Хеллмана. Обеспечивает надёжную доставку с подтверждениями.

    📬

    LXMF (Сообщения)

    Lightweight Extensible Message Format — протокол обмена сообщениями поверх Reticulum. Поддерживает асинхронную доставку через propagation-узлы (аналог почтовых серверов).

    Установка и настройка Reticulum

    Bash — Установка Reticulum
    # Установка Reticulum через pip
    $ pip install rns
    
    # Установка LXMF (протокол сообщений)
    $ pip install lxmf
    
    # Установка Sideband (CLI-клиент)
    $ pip install sideband
    
    # Установка Nomad Network (mesh-браузер)
    $ pip install nomadnet
    
    # Установка Reticulum Utilities
    $ pip install rnsh
    
    # Проверка установки
    $ rns --version
    # Reticulum Network Stack 0.7.2
    
    # Инициализация (создаёт конфигурацию и ключи)
    $ rns --init
    
    # Запуск Reticulum в фоновом режиме
    $ rnsd
    
    # Запуск с выводом логов
    $ rnsd -v
    
    # Запуск с максимальным уровнем логирования
    $ rnsd -vvv

    Конфигурационный файл

    INI — ~/.reticulum/config
    # ============================================
    # Reticulum Network Stack Configuration
    # ============================================
    
    # Основные настройки
    [reticulum]
    # Включить режим transport-узла (ретрансляция)
    enable_transport = true
    
    # Share instance statistics
    share_instance = true
    
    # Порт TCP-интерфейса (для входящих подключений)
    shared_instance_port = 37428
    
    # ============================================
    # Интерфейсы
    # ============================================
    
    # --- LoRa через RNode (Serial) ---
    [[interfaces]]
      [[rnode_lora]]
        # Тип интерфейса
        type = RNodeInterface
    
        # Serial-порт устройства RNode
        port = /dev/ttyUSB0
    
        # Скорость serial-соединения
        speed = 115200
    
        # Частота LoRa (868 МГц для Европы)
        frequency = 868500000
    
        # Ширина полосы
        bandwidth = 125000
    
        # Spreading Factor
        txpower = 17
    
        # Spreading Factor
        spreadingfactor = 10
    
        # Coding Rate
        codingrate = 5
    
        # Airtime limit (для соблюдения duty cycle)
        airtime_limit = 1.0
    
    # --- WiFi (TCP/IP Server) ---
    [[interfaces]]
      [[tcp_server]]
        type = TCPServerInterface
        listen_port = 4242
    
    # --- WiFi (TCP/IP Client к другому узлу) ---
    [[interfaces]]
      [[tcp_client]]
        type = TCPClientInterface
        target_host = 192.168.1.100
        target_port = 4242
    
    # --- I2P Interface (для анонимной маршрутизации) ---
    [[interfaces]]
      [[i2p_interface]]
        type = I2PInterface
        peers = "i2p_address_1.b32.i2p,i2p_address_2.b32.i2p"
    
    # --- Serial Interface (прямое подключение) ---
    [[interfaces]]
      [[serial_interface]]
        type = SerialInterface
        port = /dev/ttyUSB1
        speed = 9600
    
    # --- UDP Interface ---
    [[interfaces]]
      [[udp_interface]]
        type = UDPInterface
        listen_port = 2971
        forward_port = 2971
    
    # ============================================
    # LXMF Configuration
    # ============================================
    
    [lxmf]
    # Propagation node settings
    propagation_node = false
    
    # Если true, этот узел будет хранить и
    # пересылать LXMF-сообщения для других
    offer_propagation = false
    
    # Максимальный размер хранилища (в байтах)
    propagation_storage_limit = 104857600  # 100 MB
    
    # Интервал синхронизации (секунды)
    propagation_sync_interval = 3600

    Reticulum Python API

    Python — Reticulum: базовое приложение
    """
    Reticulum — Базовое приложение для обмена сообщениями
    
    Этот пример демонстрирует:
      - Создание Reticulum-идентичности
      - Объявление сервиса (announce)
      - Установление зашифрованного линка
      - Отправку и приём сообщений
      - Обработку входящих соединений
    """
    
    import RNS
    import time
    import sys
    import threading
    
    
    class MeshMessenger:
        """
        Простой мессенджер поверх Reticulum Network Stack.
        Поддерживает обнаружение пиров, зашифрованную связь
        и асинхронный обмен сообщениями.
        """
    
        # Аспект приложения (имя сервиса в сети Reticulum)
        APP_NAME = "meshmessenger"
        APP_ASPECT = "messenger.example"
    
        def __init__(self, identity_path=None):
            """
            Инициализация мессенджера.
    
            Args:
                identity_path: Путь к файлу идентичности.
                              Если None, создаётся новая.
            """
            self.reticulum = None
            self.identity = None
            self.destination = None
            self.active_links = {}
            self.message_history = []
            self.known_peers = {}
    
            # Инициализация Reticulum
            self._init_reticulum(identity_path)
    
        def _init_reticulum(self, identity_path):
            """Инициализация Reticulum Network Stack."""
            try:
                # Создание или загрузка идентичности
                if identity_path:
                    self.identity = RNS.Identity.from_file(identity_path)
                    RNS.log("Загружена существующая идентичность")
                else:
                    self.identity = RNS.Identity()
                    RNS.log("Создана новая идентичность")
    
                # Инициализация Reticulum
                self.reticulum = RNS.Reticulum()
    
                # Создание Destination (адреса в сети)
                self.destination = RNS.Destination(
                    self.identity,
                    RNS.Destination.IN,
                    RNS.Destination.SINGLE,
                    self.APP_NAME,
                    self.APP_ASPECT
                )
    
                # Установка обработчика входящих линков
                self.destination.set_link_established_callback(
                    self.on_link_established
                )
    
                # Установка обработчика входящих данных
                self.destination.set_packet_callback(
                    self.on_packet
                )
    
                # Объявление сервиса в сети
                self.destination.announce()
    
                RNS.log(f"🟢 Мессенджер запущен")
                RNS.log(f"📡 Адрес: {self.destination.hash.hex()}")
                RNS.log(f"🔑 Публичный ключ: {self.identity.hash.hex()[:16]}...")
    
            except Exception as e:
                RNS.log(f"❌ Ошибка инициализации: {e}")
                raise
    
        def announce(self):
            """Отправка объявления в сеть."""
            self.destination.announce(
                app_data="MeshMessenger Node Online".encode("utf-8")
            )
            RNS.log("📢 Announce отправлен")
    
        def discover_peers(self):
            """
            Обнаружение пиров в сети.
            Сканирует сеть на наличие других узлов мессенджера.
            """
            RNS.log("🔍 Поиск пиров...")
    
            # Получение списка известных announce
            for dest_hash, announce_data in \
                    RNS.Transport.destination_table().items():
    
                if announce_data["aspect"] == self.APP_ASPECT:
                    peer_hash = dest_hash.hex()
                    self.known_peers[peer_hash] = announce_data
                    RNS.log(f"  👤 Найден пир: {peer_hash[:16]}...")
    
            RNS.log(f"✅ Найдено {len(self.known_peers)} пиров")
            return self.known_peers
    
        def send_message(self, dest_hash, message):
            """
            Отправка зашифрованного сообщения.
    
            Args:
                dest_hash: Хеш адреса получателя (hex string)
                message: Текст сообщения
            """
            try:
                # Создание destination получателя
                dest_bytes = bytes.fromhex(dest_hash)
                peer_identity = RNS.Identity.recall(dest_bytes)
    
                if peer_identity is None:
                    RNS.log("❌ Идентичность получателя неизвестна")
                    RNS.log("   Ожидание announce от получателя...")
                    return False
    
                # Создание destination получателя
                peer_dest = RNS.Destination(
                    peer_identity,
                    RNS.Destination.OUT,
                    RNS.Destination.SINGLE,
                    self.APP_NAME,
                    self.APP_ASPECT
                )
    
                # Установление зашифрованного линка
                link = RNS.Link(peer_dest)
                link.set_link_established_callback(
                    self.on_link_established
                )
    
                RNS.log(f"🔗 Установление линка с {dest_hash[:16]}...")
    
                # Ожидание установления линка
                timeout = 15
                while not link.status() == RNS.Link.ACTIVE:
                    time.sleep(0.5)
                    timeout -= 0.5
                    if timeout <= 0:
                        RNS.log("❌ Таймаут установления линка")
                        return False
    
                RNS.log("✅ Линк установлен (зашифрован)")
    
                # Отправка сообщения через линк
                packet = RNS.Packet(
                    link,
                    message.encode("utf-8")
                )
                packet.send()
    
                RNS.log(f"📤 Отправлено: {message}")
    
                # Сохранение в истории
                self.message_history.append({
                    "direction": "out",
                    "peer": dest_hash,
                    "message": message,
                    "time": time.time()
                })
    
                return True
    
            except Exception as e:
                RNS.log(f"❌ Ошибка отправки: {e}")
                return False
    
        def on_packet(self, message, packet):
            """Обработчик входящих пакетов."""
            try:
                text = message.decode("utf-8")
                sender = packet.link.destination.hash.hex()
    
                RNS.log(f"💬 Сообщение от {sender[:16]}: {text}")
    
                self.message_history.append({
                    "direction": "in",
                    "peer": sender,
                    "message": text,
                    "time": time.time()
                })
    
            except Exception as e:
                RNS.log(f"❌ Ошибка обработки пакета: {e}")
    
        def on_link_established(self, link):
            """Обработчик установления линка."""
            link_hash = link.link_id.hex()
            self.active_links[link_hash] = link
    
            RNS.log(f"🔗 Линк установлен: {link_hash[:16]}")
    
            # Установка обработчика входящих данных для линка
            link.set_packet_callback(self.on_packet)
            link.set_link_closed_callback(self.on_link_closed)
    
        def on_link_closed(self, link):
            """Обработчик закрытия линка."""
            link_hash = link.link_id.hex()
            if link_hash in self.active_links:
                del self.active_links[link_hash]
            RNS.log(f"🔌 Линк закрыт: {link_hash[:16]}")
    
        def get_status(self):
            """Получение статуса узла."""
            return {
                "address": self.destination.hash.hex(),
                "known_peers": len(self.known_peers),
                "active_links": len(self.active_links),
                "messages": len(self.message_history),
                "interfaces": len(self.reticulum.interfaces)
            }
    
    
    # === Пример использования ===
    if __name__ == "__main__":
        # Создание мессенджера
        messenger = MeshMessenger()
    
        # Периодическое объявление
        def announce_loop():
            while True:
                messenger.announce()
                time.sleep(300)  # каждые 5 минут
    
        announce_thread = threading.Thread(
            target=announce_loop, daemon=True
        )
        announce_thread.start()
    
        # Основной цикл
        try:
            while True:
                # Обнаружение пиров
                peers = messenger.discover_peers()
    
                # Отправка тестового сообщения первому найденному пиру
                if peers:
                    first_peer = list(peers.keys())[0]
                    messenger.send_message(
                        first_peer,
                        "Привет из Reticulum! 🔐"
                    )
    
                # Вывод статуса
                status = messenger.get_status()
                RNS.log(f"📊 Статус: {status}")
    
                time.sleep(60)
    
        except KeyboardInterrupt:
            RNS.log("\n👋 Завершение работы...")
            sys.exit(0)

    LXMF — Lightweight Extensible Message Format

    LXMF — это протокол обмена сообщениями, работающий поверх Reticulum. Он обеспечивает асинхронную доставку сообщений, подобно электронной почте, но в децентрализованной mesh-сети.

    Python — LXMF: отправка сообщений
    """
    LXMF — Пример отправки и получения сообщений
    
    LXMF (Lightweight Extensible Message Format) — протокол
    асинхронного обмена сообщениями поверх Reticulum.
    """
    
    import RNS
    import LXMF
    import time
    
    
    def lxmf_delivery_callback(message):
        """Обработчик доставленного сообщения."""
        print(f"✅ Сообщение доставлено: {message.hash.hex()[:16]}")
    
    
    def lxmf_failed_callback(message):
        """Обработчик недоставленного сообщения."""
        print(f"❌ Не удалось доставить: {message.hash.hex()[:16]}")
    
    
    def lxmf_incoming(message):
        """Обработчик входящего LXMF-сообщения."""
        source = message.source_hash.hex()
        content = message.content.decode("utf-8")
        title = message.title.decode("utf-8")
        timestamp = message.timestamp
    
        print(f"\n📬 Новое сообщение!")
        print(f"   От:      {source[:16]}...")
        print(f"   Тема:    {title}")
        print(f"   Текст:   {content}")
        print(f"   Время:   {time.ctime(timestamp)}")
        print(f"   Размер:  {len(content)} байт")
    
    
    # Инициализация Reticulum
    reticulum = RNS.Reticulum()
    
    # Создание идентичности пользователя
    user_identity = RNS.Identity()
    
    # Создание LXMF-роутера
    lxmf_router = LXMF.LXMRouter(
        identity=user_identity,
        storagepath="./lxmf_storage"
    )
    
    # Создание LXMF-адреса (destination)
    lxmf_destination = lxmf_router.register_delivery_identity(
        user_identity,
        display_name="MyLXMFNode"
    )
    
    # Установка обработчика входящих сообщений
    lxmf_router.register_delivery_callback(lxmf_incoming)
    
    # Объявление LXMF-сервиса
    lxmf_router.announce(lxmf_destination)
    
    print(f"📡 LXMF-адрес: {lxmf_destination.hex()}")
    print(f"🔑 Имя: MyLXMFNode")
    
    # Отправка LXMF-сообщения
    # (нужно знать LXMF-адрес получателя)
    dest_lxmf_addr = "a1b2c3d4e5f6..."  # адрес получателя
    
    dest_bytes = bytes.fromhex(dest_lxmf_addr)
    dest_identity = RNS.Identity.recall(dest_bytes)
    
    if dest_identity:
        # Создание сообщения
        lxmessage = LXMF.LXMessage(
            destination=dest_identity,
            source=user_identity,
            content="Привет! Это тестовое сообщение через LXMF 📨",
            title="Тест LXMF",
            desired_method=LXMF.LXMessage.DIRECT
        )
    
        # Установка callback-функций
        lxmessage.set_delivery_callback(lxmf_delivery_callback)
        lxmessage.set_failed_callback(lxmf_failed_callback)
    
        # Отправка
        lxmf_router.handle_outbound(lxmessage)
        print("📤 Сообщение отправлено!")
    else:
        print("❌ Идентичность получателя неизвестна")
        print("   Подождите announce от получателя")
    
    # Ожидание сообщений
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("\n👋 Завершение...")
    💡

    Совет: Для удобной работы с LXMF используйте приложение Sideband — это полноценный клиент для обмена сообщениями через Reticulum/LXMF с графическим интерфейсом (доступен для Linux, Android).

    Сравнительный анализ

    Сравнение протоколов

    Детальное сравнение Meshtastic, MeshCore и Reticulum по ключевым параметрам для выбора оптимального решения.

    Детальное сравнение

    Параметр 🟢 Meshtastic 🔵 MeshCore 🟣 Reticulum
    Технология связи LoRa (Sub-GHz) WiFi 2.4 ГГц Мульти-интерфейс
    Дальность (1 hop) до 20+ км до 100-500 м зависит от интерфейса
    Скорость 0.3 — 50 кбит/с до 5 Мбит/с зависит от интерфейса
    Частота 433/868/915 МГц 2.4 ГГц любая
    Платформа ESP32, nRF52, RP2040 ESP32 Python (любая ОС)
    Шифрование AES-256 (PSK) WPA2 (WiFi) X25519 + AES-256
    Маршрутизация Controlled Flooding Tree-based Announce-based
    Макс. узлов ~100-200 ~1000 практически ∞
    Энергопотребление Очень низкое Среднее зависит от интерфейса
    GPS-трекинг ✅ Встроенный ❌ Нет ⚠️ Через приложение
    Мобильное приложение ✅ iOS + Android ❌ Нет ⚠️ Sideband (Android)
    MQTT-интеграция ✅ Встроенная ❌ Нет ⚠️ Через rnsh
    Язык разработки C++ (Arduino) C/C++ (ESP-IDF) Python
    Сложность настройки 🟢 Низкая 🟡 Средняя 🟠 Высокая
    Сообщество 🟢 Большое 🟡 Среднее 🟡 Среднее
    Стоимость узла $15 — $50 $5 — $15 $5 — $100+

    Когда что выбрать?

    🟢

    Выберите Meshtastic, если:

    • Нужна связь на больших расстояниях
    • Важна автономность (батарея)
    • Нужен GPS-трекинг
    • Хотите простое мобильное приложение
    • Работаете в условиях без WiFi
    • Нужна быстрая настройка
    🔵

    Выберите MeshCore, если:

    • Нужна высокая скорость данных
    • Работаете в помещении/городе
    • Передаёте файлы или видео
    • Нужна большая сеть (100+ узлов)
    • Есть доступ к розетке
    • Знаете C/C++ и ESP-IDF
    🟣

    Выберите Reticulum, если:

    • Нужна максимальная гибкость
    • Хотите комбинировать интерфейсы
    • Важна криптографическая безопасность
    • Нужна асинхронная доставка (LXMF)
    • Знаете Python
    • Строите сложную инфраструктуру
    Code Examples

    Примеры кода

    Практические примеры для всех трёх технологий. От базовых до продвинутых.

    Meshtastic: GPS-трекер

    C++ — Meshtastic GPS-трекер (Arduino)
    /*
     * Meshtastic GPS-трекер
     *
     * Автоматически отправляет GPS-координаты в mesh-сеть
     * с настраиваемым интервалом.
     *
     * Оборудование:
     *   - ESP32 + LoRa модуль (SX1276/SX1262)
     *   - GPS модуль (NEO-6M или аналог)
     *   - OLED дисплей (опционально)
     */
    
    #include <Arduino.h>
    #include <TinyGPS++.h>
    #include <HardwareSerial.h>
    #include <U8g2lib.h>
    
    // GPS на Serial2
    HardwareSerial gpsSerial(2);
    TinyGPSPlus gps;
    
    // OLED дисплей
    U8G2_SSD1306_128X64_NONAME_F_HW_I2C display(
        U8G2_R0, /* reset=*/ U8X8_PIN_NONE
    );
    
    // Настройки
    const unsigned long GPS_UPDATE_INTERVAL = 1000;   // 1 сек
    const unsigned long POSITION_SEND_INTERVAL = 30000; // 30 сек
    
    unsigned long lastGpsUpdate = 0;
    unsigned long lastPositionSend = 0;
    
    float currentLat = 0.0;
    float currentLon = 0.0;
    float currentAlt = 0.0;
    int satellites = 0;
    bool gpsValid = false;
    
    void setup() {
        Serial.begin(115200);
        gpsSerial.begin(9600, SERIAL_8N1, 16, 17);
    
        // Инициализация дисплея
        display.begin();
        display.setFont(u8g2_font_ncenB08_tr);
        display.clearBuffer();
        display.drawStr(10, 30, "GPS Tracker");
        display.drawStr(10, 50, "Initializing...");
        display.sendBuffer();
    
        Serial.println("🛰️ GPS Tracker started");
    }
    
    void updateGPS() {
        while (gpsSerial.available() > 0) {
            char c = gpsSerial.read();
            gps.encode(c);
        }
    
        if (gps.location.isUpdated()) {
            currentLat = gps.location.lat();
            currentLon = gps.location.lng();
            currentAlt = gps.altitude.meters();
            satellites = gps.satellites.value();
            gpsValid = true;
    
            Serial.printf(
                "📍 GPS: %.6f, %.6f, %.1fm (%d sat)\n",
                currentLat, currentLon, currentAlt, satellites
            );
        }
    }
    
    void updateDisplay() {
        display.clearBuffer();
    
        if (gpsValid) {
            char latStr[20], lonStr[20], altStr[20], satStr[20];
            snprintf(latStr, sizeof(latStr), "Lat: %.6f", currentLat);
            snprintf(lonStr, sizeof(lonStr), "Lon: %.6f", currentLon);
            snprintf(altStr, sizeof(altStr), "Alt: %.0fm", currentAlt);
            snprintf(satStr, sizeof(satStr), "Sat: %d", satellites);
    
            display.drawStr(5, 15, "🛰️ GPS Tracker");
            display.drawStr(5, 30, latStr);
            display.drawStr(5, 42, lonStr);
            display.drawStr(5, 54, altStr);
            display.drawStr(80, 54, satStr);
        } else {
            display.drawStr(5, 30, "Waiting for GPS...");
            display.drawStr(5, 50, "Go outdoors!");
        }
    
        display.sendBuffer();
    }
    
    void loop() {
        unsigned long now = millis();
    
        // Обновление GPS
        if (now - lastGpsUpdate > GPS_UPDATE_INTERVAL) {
            lastGpsUpdate = now;
            updateGPS();
            updateDisplay();
        }
    
        // Отправка позиции через Meshtastic
        if (gpsValid && now - lastPositionSend > POSITION_SEND_INTERVAL) {
            lastPositionSend = now;
            // Meshtastic автоматически отправляет позицию
            // при настроенном position_broadcast_secs
            Serial.println("📤 Position broadcast sent");
        }
    }

    MeshCore: Сенсорная сеть

    C++ — MeshCore сенсорная сеть
    /*
     * MeshCore Sensor Network
     *
     * Создаёт mesh-сеть из сенсорных узлов, которые собирают
     * данные (температура, влажность) и передают их на
     * корневой узел для агрегации.
     */
    
    #include <WiFi.h>
    #include <esp_mesh.h>
    #include <ArduinoJson.h>
    #include <DHT.h>
    
    #define DHTPIN 4
    #define DHTTYPE DHT22
    #define MESH_SSID "SensorMesh"
    #define MESH_PASS "sensor123"
    
    DHT dht(DHTPIN, DHTTYPE);
    
    struct SensorData {
        float temperature;
        float humidity;
        float pressure;
        uint32_t timestamp;
        char nodeId[20];
    };
    
    void sendSensorData() {
        SensorData data;
        data.temperature = dht.readTemperature();
        data.humidity = dht.readHumidity();
        data.timestamp = millis();
        strncpy(data.nodeId, WiFi.macAddress().c_str(), 19);
    
        // Сериализация в JSON
        StaticJsonDocument<256> doc;
        doc["node"] = data.nodeId;
        doc["temp"] = data.temperature;
        doc["hum"] = data.humidity;
        doc["ts"] = data.timestamp;
    
        char jsonBuf[256];
        serializeJson(doc, jsonBuf);
    
        // Отправка через mesh
        mesh_data_t meshData;
        meshData.data = (uint8_t*)jsonBuf;
        meshData.size = strlen(jsonBuf);
        meshData.proto = MESH_PROTO_JSON;
    
        esp_mesh_send(NULL, &meshData, MESH_DATA_TODS, NULL, 0);
    }
    
    void setup() {
        Serial.begin(115200);
        dht.begin();
        // ... mesh init (см. MeshCore page)
    }
    
    void loop() {
        static unsigned long lastSend = 0;
        if (millis() - lastSend > 10000) {
            lastSend = millis();
            sendSensorData();
        }
    }

    Reticulum: Чат-приложение

    Python — Reticulum Mesh Chat
    """
    Reticulum Mesh Chat — Интерактивный чат
    
    Полноценное чат-приложение поверх Reticulum Network Stack
    с поддержкой обнаружения пиров и зашифрованных сообщений.
    """
    
    import RNS
    import time
    import threading
    import sys
    import os
    
    
    class MeshChat:
        """Интерактивный mesh-чат поверх Reticulum."""
    
        APP_NAME = "meshchat"
        APP_ASPECT = "chat.mesh"
    
        def __init__(self):
            self.identity = RNS.Identity()
            self.reticulum = RNS.Reticulum()
            self.destination = RNS.Destination(
                self.identity,
                RNS.Destination.IN,
                RNS.Destination.SINGLE,
                self.APP_NAME,
                self.APP_ASPECT
            )
            self.peers = {}
            self.messages = []
            self.current_peer = None
            self.running = True
    
            self.destination.set_packet_callback(self.on_message)
            self.destination.announce()
    
            print(f"🟢 MeshChat запущен")
            print(f"📡 Адрес: {self.destination.hash.hex()}")
    
        def on_message(self, data, packet):
            """Обработка входящего сообщения."""
            msg = data.decode("utf-8", errors="ignore")
            sender = packet.link.destination.hash.hex() if packet.link else "?"
            timestamp = time.strftime("%H:%M:%S")
    
            self.messages.append({
                "from": sender,
                "msg": msg,
                "time": timestamp,
                "dir": "in"
            })
    
            print(f"\n💬 [{timestamp}] {sender[:8]}: {msg}")
    
        def scan_peers(self):
            """Сканирование сети на наличие пиров."""
            self.peers.clear()
            for dh, info in RNS.Transport.destination_table().items():
                if info.get("aspect") == self.APP_ASPECT:
                    self.peers[dh.hex()] = info
            return self.peers
    
        def send(self, peer_hash, message):
            """Отправка сообщения пиру."""
            peer_id = RNS.Identity.recall(bytes.fromhex(peer_hash))
            if not peer_id:
                print("❌ Пир неизвестен")
                return
    
            peer_dest = RNS.Destination(
                peer_id, RNS.Destination.OUT,
                RNS.Destination.SINGLE,
                self.APP_NAME, self.APP_ASPECT
            )
    
            link = RNS.Link(peer_dest)
            while link.status() != RNS.Link.ACTIVE:
                time.sleep(0.2)
    
            pkt = RNS.Packet(link, message.encode("utf-8"))
            pkt.send()
    
            timestamp = time.strftime("%H:%M:%S")
            self.messages.append({
                "to": peer_hash,
                "msg": message,
                "time": timestamp,
                "dir": "out"
            })
    
        def announce_loop(self):
            """Периодическая отправка announce."""
            while self.running:
                self.destination.announce()
                time.sleep(180)
    
        def interactive_loop(self):
            """Интерактивный цикл ввода."""
            announce_thread = threading.Thread(
                target=self.announce_loop, daemon=True
            )
            announce_thread.start()
    
            while self.running:
                try:
                    cmd = input("\n> ").strip()
    
                    if cmd == "/scan":
                        peers = self.scan_peers()
                        print(f"🔍 Найдено {len(peers)} пиров")
                        for h in peers:
                            print(f"  {h[:16]}...")
    
                    elif cmd.startswith("/msg "):
                        parts = cmd[5:].split(" ", 1)
                        if len(parts) == 2:
                            self.send(parts[0], parts[1])
                            print("📤 Отправлено")
    
                    elif cmd == "/quit":
                        self.running = False
                        print("👋 Bye!")
    
                    elif cmd == "/help":
                        print("Команды: /scan, /msg <hash> <text>, /quit")
    
                except EOFError:
                    break
                except KeyboardInterrupt:
                    self.running = False
                    print("\n👋 Bye!")
    
    
    if __name__ == "__main__":
        chat = MeshChat()
        chat.interactive_loop()

    Продвинутый: Гибридная сеть (Reticulum + LoRa)

    Python — Reticulum RNode (LoRa) конфигурация
    """
    Reticulum + RNode (LoRa) — Конфигурация гибридного узла
    
    Этот скрипт настраивает Reticulum-узел с несколькими
    интерфейсами: LoRa (через RNode), WiFi (TCP) и Serial.
    Узел действует как transport-node, ретранслируя трафик
    между разными средами передачи.
    """
    
    import RNS
    import time
    import sys
    
    
    def setup_hybrid_node():
        """
        Настройка гибридного Reticulum-узла с:
          - LoRa через RNode (дальняя связь)
          - WiFi TCP (высокоскоростная локальная связь)
          - Serial (проводное подключение к датчикам)
        """
        # Инициализация Reticulum с кастомным конфигом
        reticulum = RNS.Reticulum(
            configdir="./reticulum_config"
        )
    
        # Создание идентичности transport-узла
        transport_identity = RNS.Identity()
    
        # Создание destination для transport-узла
        transport_dest = RNS.Destination(
            transport_identity,
            RNS.Destination.IN,
            RNS.Destination.SINGLE,
            "transport",
            "hybrid.gateway"
        )
    
        # Объявление transport-сервиса
        transport_dest.announce()
    
        RNS.log("🟢 Hybrid transport node started")
        RNS.log(f"📡 LoRa: RNode on /dev/ttyUSB0")
        RNS.log(f"📶 WiFi: TCP server on port 4242")
        RNS.log(f"🔌 Serial: /dev/ttyUSB1 @ 9600")
    
        # Мониторинг интерфейсов
        while True:
            for iface in reticulum.interfaces:
                stats = iface.get_stats()
                RNS.log(
                    f"📊 {iface.name}: "
                    f"RX={stats.get('rx_bytes', 0)} "
                    f"TX={stats.get('tx_bytes', 0)} "
                    f"pkts={stats.get('rx_packets', 0)}"
                )
            time.sleep(30)
    
    
    if __name__ == "__main__":
        try:
            setup_hybrid_node()
        except KeyboardInterrupt:
            RNS.log("\n👋 Shutting down...")
            sys.exit(0)

    Продвинутый: Meshtastic MQTT-шлюз

    Python — Meshtastic MQTT Gateway
    """
    Meshtastic MQTT Gateway
    
    Мост между локальной Meshtastic mesh-сетью и MQTT-брокером.
    Позволяет объединять разрозненные mesh-сети через интернет
    и интегрировать их с другими системами (Home Assistant,
    Node-RED, Grafana и т.д.).
    """
    
    import meshtastic
    import meshtastic.serial_interface
    import paho.mqtt.client as mqtt
    import json
    import time
    import base64
    import logging
    from datetime import datetime
    
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s [%(levelname)s] %(message)s'
    )
    logger = logging.getLogger(__name__)
    
    
    class MeshtasticMQTTGateway:
        """
        Шлюз между Meshtastic и MQTT.
    
        Функции:
          - Пересылка сообщений из mesh в MQTT
          - Пересылка команд из MQTT в mesh
          - Публикация телеметрии в MQTT-топики
          - Интеграция с Home Assistant
          - Логирование всех событий
        """
    
        def __init__(self, serial_port="/dev/ttyUSB0",
                       mqtt_host="localhost", mqtt_port=1883,
                       mqtt_user=None, mqtt_pass=None):
            # Meshtastic
            self.serial_port = serial_port
            self.interface = None
    
            # MQTT
            self.mqtt_host = mqtt_host
            self.mqtt_port = mqtt_port
            self.mqtt_user = mqtt_user
            self.mqtt_pass = mqtt_pass
            self.mqtt_client = None
            self.mqtt_base_topic = "meshtastic"
    
            # Статистика
            self.stats = {
                "messages_rx": 0,
                "messages_tx": 0,
                "telemetry_rx": 0,
                "position_rx": 0,
                "start_time": time.time()
            }
    
        def connect_meshtastic(self):
            """Подключение к Meshtastic-устройству."""
            try:
                self.interface = meshtastic.serial_interface.SerialInterface(
                    devPath=self.serial_port
                )
                my_node = self.interface.getMyNodeInfo()
                logger.info(f"✅ Meshtastic подключён: "
                           f"{my_node.get('user', {}).get('longName')}")
                return True
            except Exception as e:
                logger.error(f"❌ Meshtastic ошибка: {e}")
                return False
    
        def connect_mqtt(self):
            """Подключение к MQTT-брокеру."""
            self.mqtt_client = mqtt.Client(
                client_id="meshtastic_gateway",
                clean_session=True
            )
    
            self.mqtt_client.on_connect = self._on_mqtt_connect
            self.mqtt_client.on_message = self._on_mqtt_message
    
            if self.mqtt_user:
                self.mqtt_client.username_pw_set(
                    self.mqtt_user, self.mqtt_pass
                )
    
            try:
                self.mqtt_client.connect(
                    self.mqtt_host, self.mqtt_port, 60
                )
                self.mqtt_client.loop_start()
                return True
            except Exception as e:
                logger.error(f"❌ MQTT ошибка: {e}")
                return False
    
        def _on_mqtt_connect(self, client, userdata, flags, rc):
            """Обработчик подключения MQTT."""
            logger.info(f"✅ MQTT подключён (rc={rc})")
    
            # Подписка на топики для управления mesh
            client.subscribe(f"{self.mqtt_base_topic}/send/#")
            client.subscribe(f"{self.mqtt_base_topic}/config/#")
            client.subscribe(f"{self.mqtt_base_topic}/cmd/#")
    
            # Публикация Home Assistant discovery
            self._publish_ha_discovery()
    
        def _on_mqtt_message(self, client, userdata, msg):
            """Обработчик входящих MQTT-сообщений."""
            topic = msg.topic
            payload = msg.payload.decode('utf-8')
    
            logger.info(f"📥 MQTT: {topic} = {payload}")
    
            if topic.startswith(f"{self.mqtt_base_topic}/send/"):
                # Отправка сообщения в mesh
                dest = topic.split("/")[-1]
                if dest == "broadcast":
                    dest = "^all"
                self.interface.sendText(
                    text=payload,
                    destinationId=dest,
                    wantAck=True
                )
                self.stats["messages_tx"] += 1
                logger.info(f"📤 Отправлено в mesh: {payload}")
    
            elif topic.startswith(f"{self.mqtt_base_topic}/cmd/"):
                # Выполнение команды
                cmd = topic.split("/")[-1]
                if cmd == "reboot":
                    self.interface.sendPacket(
                        bytes([0x04]), "^all"
                    )
                elif cmd == "stats":
                    self._publish_stats()
    
        def on_meshtastic_receive(self, packet, interface):
            """Обработчик входящих Meshtastic-пакетов."""
            decoded = packet.get('decoded', {})
            portnum = decoded.get('portnum', '')
            from_id = packet.get('fromId', 'unknown')
            rx_time = packet.get('rxTime', 0)
            rssi = packet.get('rxRssi', 0)
            snr = packet.get('rxSnr', 0.0)
    
            if portnum == 'TEXT_MESSAGE_APP':
                text = decoded.get('payload', b'').decode('utf-8')
                self.stats["messages_rx"] += 1
    
                # Публикация в MQTT
                self._mqtt_publish(
                    f"{self.mqtt_base_topic}/message/{from_id}",
                    json.dumps({
                        "text": text,
                        "from": from_id,
                        "rssi": rssi,
                        "snr": snr,
                        "timestamp": rx_time,
                        "datetime": datetime.fromtimestamp(
                            rx_time
                        ).isoformat() if rx_time else None
                    })
                )
                logger.info(f"💬 {from_id}: {text}")
    
            elif portnum == 'TELEMETRY_APP':
                self.stats["telemetry_rx"] += 1
                telemetry = decoded.get('payload', {})
    
                # Публикация телеметрии
                self._mqtt_publish(
                    f"{self.mqtt_base_topic}/telemetry/{from_id}",
                    json.dumps({
                        "from": from_id,
                        "data": telemetry,
                        "rssi": rssi,
                        "snr": snr,
                        "timestamp": rx_time
                    })
                )
    
            elif portnum == 'POSITION_APP':
                self.stats["position_rx"] += 1
                position = decoded.get('payload', {})
    
                self._mqtt_publish(
                    f"{self.mqtt_base_topic}/position/{from_id}",
                    json.dumps({
                        "from": from_id,
                        "position": position,
                        "rssi": rssi,
                        "snr": snr,
                        "timestamp": rx_time
                    })
                )
    
        def _mqtt_publish(self, topic, payload, retain=True):
            """Публикация в MQTT."""
            if self.mqtt_client:
                self.mqtt_client.publish(
                    topic, payload, qos=1, retain=retain
                )
    
        def _publish_ha_discovery(self):
            """Публикация Home Assistant MQTT Discovery."""
            nodes = self.interface.nodes
    
            for node_id, node in nodes.items():
                user = node.get('user', {})
                name = user.get('longName', node_id)
    
                # Battery sensor
                config = {
                    "name": f"{name} Battery",
                    "state_topic": (
                        f"{self.mqtt_base_topic}/telemetry/{node_id}"
                    ),
                    "value_template": (
                        "{{ value_json.data.device_metrics."
                        "battery_level | default(0) }}"
                    ),
                    "unit_of_measurement": "%",
                    "device_class": "battery",
                    "unique_id": f"meshtastic_{node_id}_battery"
                }
                self._mqtt_publish(
                    f"homeassistant/sensor/meshtastic/"
                    f"{node_id}_battery/config",
                    json.dumps(config)
                )
    
        def _publish_stats(self):
            """Публикация статистики шлюза."""
            uptime = time.time() - self.stats["start_time"]
            self._mqtt_publish(
                f"{self.mqtt_base_topic}/gateway/stats",
                json.dumps({
                    **self.stats,
                    "uptime": uptime,
                    "nodes": len(self.interface.nodes)
                })
            )
    
        def start(self):
            """Запуск шлюза."""
            logger.info("🚀 Запуск Meshtastic MQTT Gateway...")
    
            if not self.connect_meshtastic():
                return False
    
            if not self.connect_mqtt():
                return False
    
            # Установка обработчика Meshtastic
            self.interface.receiveCallback = (
                self.on_meshtastic_receive
            )
    
            logger.info("✅ Шлюз запущен и работает!")
    
            # Периодическая публикация статистики
            try:
                while True:
                    self._publish_stats()
                    time.sleep(60)
            except KeyboardInterrupt:
                logger.info("👋 Остановка шлюза...")
                self.mqtt_client.loop_stop()
                self.interface.close()
    
    
    # === Запуск ===
    if __name__ == "__main__":
        gateway = MeshtasticMQTTGateway(
            serial_port="/dev/ttyUSB0",
            mqtt_host="192.168.1.50",
            mqtt_port=1883,
            mqtt_user="mesh",
            mqtt_pass="gateway"
        )
        gateway.start()
    Hardware Guide

    Оборудование

    Полный гид по выбору оборудования для mesh-сетей. От бюджетных решений до профессиональных устройств.

    Оборудование для Meshtastic

    🟢

    Heltec V3

    Самое популярное устройство для Meshtastic. Встроенный OLED-дисплей, LoRa SX1262, ESP32-S3.

    ESP32-S3 SX1262 OLED 0.96" GPS опц. $25-35
    🔵

    TTGO T-Beam

    Со встроенным GPS-модулем NEO-6M. Идеален для трекеров. LoRa SX1276, ESP32.

    ESP32 SX1276 GPS NEO-6M 18650 $30-40
    🟡

    RAK Wireless WisBlock

    Модульная система на nRF52840. Очень низкое энергопотребление. Профессиональное качество.

    nRF52840 SX1262 Модульный Low Power $40-60
    🟣

    Station G2

    Готовое устройство в корпусе. Встроенная батарея, GPS, OLED. Plug-and-play решение.

    ESP32-S3 SX1262 GPS Корпус $50-70
    🔴

    DIY: ESP32 + SX1276

    Самый бюджетный вариант. ESP32 DevKit + LoRa модуль SX1276 на макетной плате.

    ESP32 SX1276 DIY $10-15

    nRF52840 Dongle

    Компактный USB-донгл для Meshtastic. Подключается к телефону или ПК через USB.

    nRF52840 USB Компактный $15-25

    Гид по антеннам

    Антенна — критически важный компонент. Правильная антенна может увеличить дальность в 5-10 раз.

    Тип антенны Усиление Дальность Цена Применение
    Штыревая (stock) 2 dBi 1-3 км $1-3 Базовое, в комплекте
    Штыревая 5 dBi 5 dBi 3-8 км $3-8 Улучшенная городская
    Штыревая 8 dBi 8 dBi 5-15 км $5-12 Открытая местность
    Яги (направленная) 10-14 dBi 15-30+ км $15-40 Стационарные линки
    Коллинеарная 6-9 dBi 8-20 км $10-25 Базовые станции
    Патч (патч) 6-8 dBi 5-12 км $8-20 Направленная, компактная
    ⚠️

    Важно: Убедитесь, что антенна соответствует частоте вашего региона! Антенна 915 МГц будет плохо работать на 868 МГц и наоборот. Также проверяйте разъём: большинство модулей используют IPEX/U.FL.

    Оборудование для MeshCore

    🔵

    ESP32 DevKit V1

    Стандартная плата разработки ESP32. WiFi + Bluetooth, 240 МГц, 520 КБ SRAM.

    ESP32-WROOM WiFi BLE $5-8
    🟢

    ESP32-S3 DevKit

    Улучшенная версия с AI-инструкциями, больше GPIO, USB OTG. Рекомендуется для MeshCore.

    ESP32-S3 WiFi 4 BLE 5.0 $8-12

    Оборудование для Reticulum

    🟣

    RNode (LoRa)

    Открытый LoRa-модем для Reticulum. Подключается через USB. Поддерживает все частоты.

    SX1276/1262 USB Open HW $30-50
    🖥️

    Raspberry Pi

    Идеальная платформа для Reticulum-узла. Работает 24/7, поддерживает все интерфейсы.

    RPi 4/5 Linux Python $35-80
    📡

    Любой Linux-ПК

    Reticulum работает на любом устройстве с Python: ноутбуки, серверы, VM, контейнеры.

    Python 3.7+ Linux/Mac/Win $0+
    Protocol Deep Dive

    Протоколы

    Глубокое погружение в протоколы mesh-сетей. Как работают маршрутизация, шифрование и обнаружение узлов.

    Алгоритмы маршрутизации

    Controlled Flooding (Meshtastic)

    Controlled Flooding — простейший, но эффективный алгоритм маршрутизации. Каждый узел, получивший новый пакет, ретранслирует его всем соседям. Для предотвращения бесконечных петель используется:

    • 🔢 Hop Limit (TTL) — счётчик прыжков, уменьшается на 1 при каждой ретрансляции. При достижении 0 пакет отбрасывается.
    • 🆔 Packet ID Cache — каждый узел хранит ID недавно полученных пакетов. Дубликаты отбрасываются.
    • ⏱️ Random Delay — случайная задержка перед ретрансляцией для предотвращения коллизий.
    • 📊 SNR-based filtering — узел может решить не ретранслировать, если сигнал слишком слабый.
    Python — Алгоритм Controlled Flooding
    import time
    import random
    from collections import OrderedDict
    
    
    class FloodingRouter:
        """
        Реализация алгоритма Controlled Flooding.
        Используется в Meshtastic для маршрутизации пакетов.
        """
    
        def __init__(self, node_id, max_hop_limit=3,
                       cache_size=100, cache_ttl=300):
            self.node_id = node_id
            self.max_hop_limit = max_hop_limit
            self.cache_size = cache_size
            self.cache_ttl = cache_ttl  # секунды
    
            # Кэш полученных пакетов (ID -> timestamp)
            self.seen_packets = OrderedDict()
    
            # Таблица соседей
            self.neighbors = {}
    
            # Статистика
            self.stats = {
                "rx": 0,
                "tx": 0,
                "relayed": 0,
                "dropped": 0,
                "duplicates": 0
            }
    
        def receive_packet(self, packet):
            """
            Обработка входящего пакета.
    
            Args:
                packet: dict с полями:
                    - id: уникальный ID пакета
                    - from: ID отправителя
                    - to: ID получателя (0 = broadcast)
                    - hop_limit: оставшиеся прыжки
                    - hop_start: начальные прыжки
                    - payload: данные
                    - snr: SNR приёма
            """
            self.stats["rx"] += 1
            packet_id = packet["id"]
            now = time.time()
    
            # 1. Проверка дубликатов
            if packet_id in self.seen_packets:
                self.stats["duplicates"] += 1
                return None  # Дубликат, игнорируем
    
            # 2. Регистрация пакета в кэше
            self.seen_packets[packet_id] = now
            self._cleanup_cache()
    
            # 3. Обновление таблицы соседей
            sender = packet["from"]
            self.neighbors[sender] = {
                "last_seen": now,
                "snr": packet.get("snr", 0),
                "hops": packet["hop_start"] - packet["hop_limit"]
            }
    
            # 4. Проверка — пакет для нас?
            if packet["to"] == self.node_id or packet["to"] == 0:
                # Пакет доставлен!
                if packet["to"] == self.node_id:
                    return {"action": "deliver", "packet": packet}
    
            # 5. Решение о ретрансляции
            hop_limit = packet["hop_limit"]
    
            if hop_limit <= 0:
                self.stats["dropped"] += 1
                return {"action": "drop", "reason": "hop_limit"}
    
            # 6. SNR-based filtering
            snr = packet.get("snr", 0)
            if snr < -15:
                self.stats["dropped"] += 1
                return {"action": "drop", "reason": "low_snr"}
    
            # 7. Ретрансляция с случайной задержкой
            delay = random.uniform(0.1, 2.0)
            relay_packet = dict(packet)
            relay_packet["hop_limit"] = hop_limit - 1
    
            self.stats["relayed"] += 1
            self.stats["tx"] += 1
    
            return {
                "action": "relay",
                "packet": relay_packet,
                "delay": delay
            }
    
        def _cleanup_cache(self):
            """Очистка кэша от старых записей."""
            now = time.time()
            expired = [
                pid for pid, ts in self.seen_packets.items()
                if now - ts > self.cache_ttl
            ]
            for pid in expired:
                del self.seen_packets[pid]
    
            # Ограничение размера кэша
            while len(self.seen_packets) > self.cache_size:
                self.seen_packets.popitem(last=False)
    
        def create_packet(self, destination, payload, is_broadcast=False):
            """Создание нового пакета для отправки."""
            return {
                "id": random.randint(1, 2**32),
                "from": self.node_id,
                "to": 0 if is_broadcast else destination,
                "hop_limit": self.max_hop_limit,
                "hop_start": self.max_hop_limit,
                "payload": payload,
                "snr": 0
            }
    
    
    # === Пример использования ===
    router = FloodingRouter(node_id=42, max_hop_limit=3)
    
    # Создание и отправка broadcast-сообщения
    msg = router.create_packet(
        destination=0,
        payload="Hello Mesh!",
        is_broadcast=True
    )
    
    # Симуляция приёма пакета от другого узла
    incoming = {
        "id": 12345,
        "from": 100,
        "to": 0,
        "hop_limit": 2,
        "hop_start": 3,
        "payload": "Relay test",
        "snr": 5.5
    }
    
    result = router.receive_packet(incoming)
    print(f"Результат: {result}")
    print(f"Статистика: {router.stats}")

    Tree Routing (MeshCore / ESP-MESH)

    Tree Routing организует узлы в древовидную структуру с корневым узлом на вершине. Данные передаются вверх к корню и вниз к листьям. Каждый узел знает своего родителя и своих детей.

    Python — Симуляция Tree Routing
    class TreeNode:
        """Узел дерева маршрутизации (ESP-MESH style)."""
    
        def __init__(self, node_id):
            self.node_id = node_id
            self.parent = None
            self.children = []
            self.layer = 0
            self.routing_table = {}
    
        def set_parent(self, parent):
            self.parent = parent
            self.layer = parent.layer + 1
            parent.children.append(self)
    
        def route_to(self, dest_id, payload):
            """Маршрутизация пакета к целевому узлу."""
            if self.node_id == dest_id:
                return [f"DELIVERED to {dest_id}"]
    
            # Проверяем детей
            for child in self.children:
                if child.node_id == dest_id or \
                   child.has_descendant(dest_id):
                    path = [f"{self.node_id}{child.node_id}"]
                    path.extend(child.route_to(dest_id, payload))
                    return path
    
            # Отправляем родителю
            if self.parent:
                path = [f"{self.node_id}{self.parent.node_id} (up)"]
                path.extend(self.parent.route_to(dest_id, payload))
                return path
    
            return ["NO ROUTE"]
    
        def has_descendant(self, node_id):
            """Проверка, есть ли узел среди потомков."""
            for child in self.children:
                if child.node_id == node_id:
                    return True
                if child.has_descendant(node_id):
                    return True
            return False
    
    
    # Построение дерева
    root = TreeNode("ROOT")
    n1 = TreeNode("Node-1"); n1.set_parent(root)
    n2 = TreeNode("Node-2"); n2.set_parent(root)
    n3 = TreeNode("Node-3"); n3.set_parent(n1)
    n4 = TreeNode("Node-4"); n4.set_parent(n1)
    n5 = TreeNode("Node-5"); n5.set_parent(n2)
    n6 = TreeNode("Node-6"); n6.set_parent(n3)
    
    # Маршрутизация
    path = n6.route_to("Node-5", "Hello!")
    print("Маршрут Node-6 → Node-5:")
    for step in path:
        print(f"  {step}")
    # Node-6 → Node-3 (up)
    # Node-3 → Node-1 (up)
    # Node-1 → ROOT (up)
    # ROOT → Node-2
    # Node-2 → Node-5
    # DELIVERED to Node-5

    Table-Based Routing

    Каждый узел поддерживает таблицу маршрутизации с информацией о том, через какой соседний узел можно добраться до каждого известного узла в сети. Таблицы обновляются через периодический обмен маршрутной информацией.

    Python — Table-Based Routing
    class RoutingTable:
        """Таблица маршрутизации для mesh-узла."""
    
        def __init__(self, node_id):
            self.node_id = node_id
            self.routes = {}  # dest -> (next_hop, cost, timestamp)
            self.neighbors = {}  # neighbor -> (link_quality, timestamp)
    
        def add_route(self, dest, next_hop, cost):
            """Добавление/обновление маршрута."""
            import time
            if dest not in self.routes or \
               cost < self.routes[dest][1]:
                self.routes[dest] = (next_hop, cost, time.time())
                return True
            return False
    
        def get_route(self, dest):
            """Получение маршрута к узлу."""
            if dest in self.routes:
                return self.routes[dest]
            return None
    
        def update_from_neighbor(self, neighbor_id, their_table):
            """Обновление таблицы от соседа (Distance Vector)."""
            updated = 0
            link_cost = self.neighbors.get(neighbor_id, (1, 0))[0]
    
            for dest, (nh, cost, ts) in their_table.items():
                if dest == self.node_id:
                    continue
                new_cost = cost + link_cost
                if self.add_route(dest, neighbor_id, new_cost):
                    updated += 1
    
            return updated
    
        def __str__(self):
            lines = [f"Routing Table for {self.node_id}:"]
            lines.append(f"  {'Dest':10} {'Next Hop':10} {'Cost':6}")
            for dest, (nh, cost, ts) in self.routes.items():
                lines.append(f"  {dest:10} {nh:10} {cost:6}")
            return "\n".join(lines)

    Announce-Based Routing (Reticulum)

    Reticulum использует уникальный подход: узлы периодически отправляют announce-пакеты, которые распространяются по сети. Каждый transport-узел, получив announce, запоминает путь к отправителю. Когда нужно отправить сообщение, узел использует запомненный маршрут.

    Python — Announce-Based Routing
    import time
    import hashlib
    
    
    class AnnounceRouter:
        """
        Announce-based маршрутизация (стиль Reticulum).
    
        Принцип:
          1. Узлы отправляют announce с своим публичным ключом
          2. Transport-узлы запоминают путь к announce
          3. Для отправки — используем запомненный путь
          4. Путь обновляется при каждом новом announce
        """
    
        def __init__(self, node_id, is_transport=False):
            self.node_id = node_id
            self.is_transport = is_transport
    
            # Таблица announce: dest_hash -> (next_hop, hops, timestamp, data)
            self.announce_table = {}
    
            # Соседи
            self.neighbors = set()
    
            # Счётчик announce
            self.announce_seq = 0
    
        def create_announce(self, app_data=b""):
            """Создание announce-пакета."""
            self.announce_seq += 1
            return {
                "type": "announce",
                "origin": self.node_id,
                "seq": self.announce_seq,
                "hops": 0,
                "app_data": app_data,
                "timestamp": time.time()
            }
    
        def receive_announce(self, announce, from_neighbor):
            """
            Обработка announce-пакета.
    
            Returns:
                True если announce нужно ретранслировать
            """
            origin = announce["origin"]
            seq = announce["seq"]
            hops = announce["hops"]
    
            # Не обрабатываем собственные announce
            if origin == self.node_id:
                return False
    
            # Проверяем, видели ли мы уже этот announce
            key = f"{origin}:{seq}"
            if key in self.announce_table:
                existing = self.announce_table[key]
                if existing[1] <= hops:
                    return False  # Уже видели более короткий путь
    
            # Сохраняем маршрут
            self.announce_table[key] = (
                from_neighbor,  # next_hop
                hops,           # hop count
                time.time(),    # timestamp
                announce.get("app_data", b"")
            )
    
            # Обновляем лучший маршрут к origin
            best_key = self._get_best_announce(origin)
    
            # Ретрансляция (только transport-узлы)
            if self.is_transport:
                announce["hops"] = hops + 1
                return True
    
            return False
    
        def get_route_to(self, dest_id):
            """Получение лучшего маршрута к узлу."""
            best = self._get_best_announce(dest_id)
            if best and best in self.announce_table:
                entry = self.announce_table[best]
                return {
                    "next_hop": entry[0],
                    "hops": entry[1],
                    "age": time.time() - entry[2]
                }
            return None
    
        def _get_best_announce(self, origin):
            """Поиск лучшего (самого свежего и короткого) announce."""
            best_key = None
            best_hops = float('inf')
            best_time = 0
    
            for key, (nh, hops, ts, data) in self.announce_table.items():
                if key.startswith(f"{origin}:"):
                    if hops < best_hops or \
                       (hops == best_hops and ts > best_time):
                        best_key = key
                        best_hops = hops
                        best_time = ts
    
            return best_key
    
    
    # === Пример ===
    # Сеть: A -- T1 -- T2 -- B
    # где T1, T2 — transport-узлы
    
    node_a = AnnounceRouter("Alice")
    node_t1 = AnnounceRouter("Transport-1", is_transport=True)
    node_t2 = AnnounceRouter("Transport-2", is_transport=True)
    node_b = AnnounceRouter("Bob")
    
    # Alice отправляет announce
    ann = node_a.create_announce(b"Alice's Chat Node")
    
    # Распространение announce по сети
    if node_t1.receive_announce(ann, "Alice"):
        ann["hops"] = 1
        if node_t2.receive_announce(ann, "Transport-1"):
            ann["hops"] = 2
            node_b.receive_announce(ann, "Transport-2")
    
    # Bob теперь знает маршрут к Alice
    route = node_b.get_route_to("Alice")
    print(f"Bob → Alice: {route}")
    # {'next_hop': 'Transport-2', 'hops': 2, 'age': 0.001}

    Шифрование в Mesh-сетях

    🔑

    Meshtastic: PSK

    Pre-Shared Key (AES-256). Все узлы на канале используют общий ключ. Простота настройки, но компрометация одного узла = компрометация канала.

    🔐

    MeshCore: WPA2

    Стандартное WiFi-шифрование WPA2. Защищает канал между узлами, но не обеспечивает сквозное шифрование на уровне приложения.

    🛡️

    Reticulum: E2E

    Сквозное шифрование: X25519 (обмен ключами) + Ed25519 (подписи) + AES-256 (данные). Каждый линк уникально зашифрован. Максимальная безопасность.

    🌐 Топологии → ❓ FAQ →
    Network Topologies

    Топологии сетей

    Визуализация и описание сетевых топологий, используемых в mesh-сетях.

    Типы топологий

    Звезда (Star)

    Все узлы подключены к центральному хабу. Простая, но уязвимая — отказ хаба = отказ всей сети. Не является mesh.

    Единая точка отказа

    🌳

    Дерево (Tree)

    Иерархическая структура с корневым узлом. Используется в MeshCore/ESP-MESH. Данные идут вверх к корню и вниз к листьям.

    ⚠️

    Отказ родительского узла изолирует ветку

    🕸️

    Полная Mesh (Full Mesh)

    Каждый узел связан с каждым. Максимальная надёжность и избыточность. Но требует O(n²) соединений — непрактично для больших сетей.

    Максимальная отказоустойчивость

    🔀

    Частичная Mesh (Partial Mesh)

    Узлы связаны с несколькими соседями. Баланс между надёжностью и сложностью. Наиболее распространённая топология в реальных mesh-сетях.

    💡

    Оптимальный баланс для Meshtastic и Reticulum

    Интерактивная визуализация

    Анимированная mesh-сеть с узлами и соединениями.

    🖱️

    Интерактив: Анимация выше показывает mesh-сеть с пульсирующими узлами и активными соединениями. Зелёные линии — активные линки, жёлтые точки — передаваемые пакеты.

    Frequently Asked Questions

    Часто задаваемые вопросы

    Ответы на самые популярные вопросы о mesh-сетях, Meshtastic, MeshCore и Reticulum.

    🤔 Что лучше: Meshtastic, MeshCore или Reticulum?

    Зависит от ваших задач:

    • 🟢 Meshtastic — для дальней связи (км), GPS-трекинга, простоты. Идеален для походов и ЧС.
    • 🔵 MeshCore — для высокой скорости (Мбит/с) на средних расстояниях. Идеален для IoT и локальных сетей.
    • 🟣 Reticulum — для максимальной гибкости и безопасности. Комбинирует любые интерфейсы, поддерживает LXMF.

    Нет «лучшего» — есть «лучший для вашей задачи».

    📡 Какая реальная дальность Meshtastic?

    Реальная дальность зависит от множества факторов:

    УсловияДальность
    Город, застройка1-3 км
    Пригород, мало зданий3-8 км
    Открытая местность8-15 км
    Прямая видимость (холмы)15-30+ км
    Рекорд (с направленными антеннами)200+ км

    Ключевые факторы: высота антенны, мощность, SF, антенна, помехи.

    🔒 Насколько безопасны mesh-сети?

    Уровень безопасности различается:

    • 🟡 Meshtastic: AES-256 с общим ключом. Достаточно для большинства задач, но все узлы на канале могут читать сообщения.
    • 🟡 MeshCore: WPA2 на уровне WiFi. Нет сквозного шифрования на уровне приложения.
    • 🟢 Reticulum: Сквозное шифрование X25519 + AES-256. Каждый линк уникально зашифрован. Максимальная безопасность.
    ⚠️

    Помните: радиосигнал можно перехватить. Всегда используйте шифрование для конфиденциальных данных.

    💰 Сколько стоит создать mesh-сеть?

    Минимальная стоимость для 2 узлов:

    • 🟢 Meshtastic: от $20-30 за узел (Heltec V3). Итого ~$40-60 для сети из 2 узлов.
    • 🔵 MeshCore: от $5-8 за узел (ESP32 DevKit). Итого ~$10-16.
    • 🟣 Reticulum: от $0 (если есть ПК) до $50+ (с RNode LoRa).

    Это одни из самых доступных сетевых технологий в мире!

    🌐 Можно ли объединить все три технологии?

    Да! Reticulum может выступать как «клей» между разными технологиями:

    • 📡 Reticulum + LoRa (RNode) = дальняя связь, совместимая с концепцией Meshtastic
    • 📶 Reticulum + WiFi = высокоскоростная локальная сеть (аналог MeshCore)
    • 🔗 Reticulum может одновременно использовать LoRa, WiFi, Serial, Ethernet, I2P

    Однако Meshtastic и MeshCore напрямую не совместимы — они используют разные протоколы и частоты.

    📱 Есть ли мобильные приложения?
    • 🟢 Meshtastic: Отличные приложения для iOS и Android. Поддержка чата, карты, GPS-трекинга.
    • 🔴 MeshCore: Нет официального мобильного приложения. Требуется разработка собственного клиента.
    • 🟡 Reticulum: Sideband для Android. Nomad Network — mesh-браузер для Linux.
    ⚡ Какое энергопотребление?
    ТехнологияПередачаПриёмСонАвтономность
    Meshtastic (LoRa)~120 мА~12 мА~0.01 мАНедели/месяцы
    MeshCore (WiFi)~200 мА~80 мА~1 мАЧасы/дни
    Reticulum (LoRa)~120 мА~12 мАЗависит от IF
    Reticulum (WiFi)~200 мА~80 мАЗависит от IF
    🏗️ Как развернуть mesh-сеть для сообщества?

    Пошаговый план:

    1. Планирование: Определите цели, покрытие, количество участников.
    2. Выбор технологии: Meshtastic для дальности, MeshCore для скорости, Reticulum для гибкости.
    3. Закупка оборудования: Минимум 3-5 узлов для начала. Включая ретрансляторы на возвышенностях.
    4. Установка ретрансляторов: Разместите router-узлы на крышах, холмах, мачтах.
    5. Настройка каналов: Единый канал с общим PSK для всех участников.
    6. Тестирование: Проверьте покрытие, дальность, качество связи.
    7. Обучение: Обучите участников пользоваться приложениями.
    8. Мониторинг: Используйте MQTT + Grafana для мониторинга сети.
    Glossary

    Глоссарий

    Словарь терминов и определений для mesh-сетей.

    LoRa — Long Range
    Технология модуляции с расширенным спектром на основе Chirp Spread Spectrum (CSS). Обеспечивает дальнюю связь (до 20+ км) при низком энергопотреблении и низкой скорости передачи данных (0.3-50 кбит/с). Работает в нелицензируемых ISM-диапазонах (433, 868, 915 МГц). Разработана компанией Semtech.
    Spreading Factor (SF)
    Параметр LoRa, определяющий количество чирпов на бит данных. Диапазон: SF7-SF12. Более высокий SF = большая дальность и чувствительность, но меньшая скорость. Каждое увеличение SF вдвое уменьшает скорость. SF12 — самый медленный, но самый дальнобойный.
    Mesh-сеть (Ячеистая сеть)
    Сетевая топология, в которой каждый узел может выступать как клиент, сервер и маршрутизатор одновременно. Данные передаются от узла к узлу, что позволяет преодолевать расстояния, превышающие дальность прямой связи. Отличается высокой отказоустойчивостью.
    Flooding (Лавинная маршрутизация)
    Алгоритм маршрутизации, при котором каждый узел ретранслирует все полученные новые пакеты всем своим соседям. Controlled Flooding добавляет ограничения: hop limit (TTL), кэш дубликатов, случайные задержки. Используется в Meshtastic.
    LXMF — Lightweight Extensible Message Format
    Протокол обмена сообщениями поверх Reticulum. Аналог электронной почты для mesh-сетей. Поддерживает прямую доставку (direct) и асинхронную через propagation-узлы. Сообщения шифруются сквозным шифрованием.
    Hop (Прыжок)
    Один акт ретрансляции пакета от одного узла к другому. Hop Limit (TTL) — максимальное количество прыжков, которое пакет может совершить. Каждый прыжок уменьшает hop limit на 1. При достижении 0 пакет отбрасывается.
    RSSI — Received Signal Strength Indicator
    Индикатор мощности принятого сигнала, измеряется в dBm. Чем ближе к 0, тем сильнее сигнал. Типичные значения: -30 dBm (отлично), -70 dBm (хорошо), -110 dBm (плохо), -140 dBm (предел чувствительности LoRa).
    SNR — Signal-to-Noise Ratio
    Отношение сигнал/шум в dB. Положительный SNR = сигнал сильнее шума. LoRa уникальна тем, что может работать при отрицательном SNR (до -20 dB), что позволяет принимать сигналы ниже уровня шума.
    Duty Cycle (Рабочий цикл)
    Процент времени, в течение которого передатчик может работать. В Европе (868 МГц) ограничение — 1% для большинства каналов. Это означает: 1 секунда передачи = 99 секунд ожидания. Meshtastic автоматически управляет duty cycle.
    RNode
    Открытый LoRa-модем, разработанный для Reticulum. Подключается через USB к любому устройству с Python. Поддерживает все частоты LoRa. Может быть собран самостоятельно или куплен готовый.
    ESP-MESH
    Протокол mesh-сети от Espressif, встроенный в ESP-IDF. Позволяет ESP32 формировать самоорганизующуюся mesh-сеть поверх WiFi. Древовидная топология с автоматическим выбором корневого узла. До 1000+ узлов.
    Protobuf — Protocol Buffers
    Формат сериализации данных от Google. Meshtastic использует Protobuf для кодирования всех сообщений. Компактный бинарный формат, эффективнее JSON по размеру и скорости парсинга.
    X25519 / Ed25519
    Криптографические алгоритмы на эллиптических кривых. X25519 — обмен ключами Диффи-Хеллмана. Ed25519 — цифровые подписи. Используются в Reticulum для обеспечения сквозного шифрования и аутентификации. 256-битная безопасность.
    Sideband
    Клиентское приложение для Reticulum/LXMF. Доступно для Android и Linux. Поддерживает обмен зашифрованными сообщениями, каналы, propagation-ноды. Аналог мессенджера для mesh-сетей Reticulum.
    Nomad Network
    Mesh-браузер для Reticulum. Позволяет создавать и просматривать «mesh-страницы» — аналоги веб-сайтов, работающие внутри mesh-сети. Использует простой markup-язык. Работает в терминале (TUI).
    Links & Resources

    Контакты и ресурсы

    Полезные ссылки, сообщества и документация.