
Сегодня мессенджеры стали одним из основных каналов общения. Но что, если нужно переписываться с корреспондентом, расположенным на расстоянии, измеряемом километрами, а интернет не работает?
Если речь идёт о текстовых сообщениях, помогут каналы связи, созданные с помощью технологии LoRa.
В этой статье мы разберёмся с темой обмена сообщениями через LoRa. Начнём с простого мессенджера, позволяющего обмениваться сообщениями между двумя компьютерами с разъёмами USB под управлением Windows или Linux. Затем перейдём к мессенджеру с микрокомпьютером Raspberry Pi.
В итоге мы создадим несложную mesh-сеть с ретрансляцией по принципу флуда и TTL-ограничением на микроконтроллерах ESP32-S3-N16R8 и модулях LoRa E22-900T22D. Каждый узел этой сети будет поднимать собственную точку доступа Wi-Fi с веб-интерфейсом чата. Для подключения к узлу и обмена сообщениями достаточно обычного смартфона с браузером. В качестве дополнения сделаем так, чтобы в этой сети можно было работать и с компьютера под Windows, к которому подключён LoRa USB-донгл E22-900T22U.
Оборудование для простого мессенджера LoRa
Чтобы сделать простейший мессенджер, позволяющий передавать текстовые строки между двумя компьютерами, не подключенными ни к какой сети, я использовал модули EBYTE LoRa E22-900T22U.
Это USB-донгл (рис. 1) с LoRa-чипом SX1262 и преобразователем USB-UART внутри.

Такой модуль очень легко подключить к микроконтроллеру, микрокомпьютеру, ноутбуку или к настольному компьютеру, где есть разъём USB. Драйвер в современных Linux и Windows 10/11 подтягивается автоматически, отдельно ставить обычно не нужно, хотя иногда его всё же приходится ставить вручную.
Правда, есть особенность режима конфигурирования модуля E22-900T22U. Чтобы перевести модуль в режим чтения или записи конфигурации, нужно нажать белую кнопку, расположенную на корпусе модуля, а потом секунды через две отпустить её. В режиме конфигурирования светодиод загорится красным цветом (рис. 1). Чтобы вернуться в рабочий режим, нажмите кнопку ещё раз.
При подключении к компьютеру с ОС Microsoft Windows в диспетчере устройств порт будет виден как COMx, а в Linux — как /dev/ttyUSB0.
Проверка конфигурации модулей
Программа e22_read_config.py, составленная на языке Python, позволяет узнать текущую конфигурацию модуля E22-900T22U. Перед запуском переведите модуль в режим конфигурирования — как описано выше.
Установите пакет pyserial:
pip install pyserial
При запуске укажите программе COM-порт, к которому подключен модуль. Ниже показан пример команды для Windows:
python e22_read_config.py --port COM5
Для ОС Raspbian:
sudo apt install python3-pip pip3 install pyserial python e22_read_config.py --port /dev/ttyUSB0
Чтобы определить, к какому COM-порту подключен модуль E22-900T22U, воспользуйтесь диспетчером устройств Windows (рис. 2).

Если вам нужно подключить несколько модулей E22-900T22U к одному компьютеру, то чтобы узнать их COM-порты, подключайте модули по очереди.
Подключая модуль к Raspberry Pi, выполните команду дважды — до и после подключения:
ls /dev/ttyUSB* /dev/ttyACM* 2>/dev/null /dev/ttyUSB0
Новое устройство в списке и будет вашим портом.
Также можете воспользоваться командой dmesg:
$ dmesg | grep tty [ 0.000000] Kernel command line: coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1 snd_bcm2835.enable_hdmi=0 video=HDMI-A-1:1920x1080M@60 vc_mem.mem_base=0x3ec00000 vc_mem.mem_size=0x40000000 console=tty1 root=PARTUUID=0efde4ec-02 rootfstype=ext4 fsck.repair=yes rootwait [ 0.000986] printk: console [tty1] enabled [ 3.363320] 3f201000.serial: ttyAMA0 at MMIO 0x3f201000 (irq = 114, base_baud = 0) is a PL011 rev2 [ 3.377666] 3f215040.serial: ttyS0 at MMIO 0x3f215040 (irq = 86, base_baud = 50000000) is a 16550 [ 5.906341] systemd[1]: Created slice system-getty.slice. [ 428.830531] usb 1-1.3: ch341-uart converter now attached to ttyUSB0
Здесь видно, что порт /dev/ttyUSB0 был подключен к устройству ch341-uart converter, а это как раз и есть наш модуль E22-900T22U.
Теперь о программе e22_read_config.py.
При запуске программа читает конфигурацию модуля функцией read_config:
def read_config(port: str, baud: int, timeout: float) -> bytes: with serial.Serial(port, baud, timeout=timeout) as ser: ser.reset_input_buffer() ser.write(bytes([0xC1, 0x00, 0x07])) ser.flush() resp = ser.read(10) return resp
Функция шлёт модулю команды чтения конфигурации из даташита E22-900T22U и возвращает ответ.
Далее расшифровка конфигурации выводится на консоль:
$ python e22_read_config.py --port COM13 Открываю COM13 @ 9600 baud... =================== КОНФИГУРАЦИЯ E22-900T22U =================== Адрес (ADDH:ADDL) 0x0001 NetID 0x00 Serial baud rate 9600 bps Serial parity 8N1 Air data rate 2.4k Sub-packet size 240B Ambient noise RSSI выкл TX power 10dBm Канал 19 (частота ≈ 869.125 МГц) RSSI байт в пакете вкл Режим передачи fixed Repeater выкл LBT вкл WOR роль receiver WOR cycle 2000 мс Сырой ответ (hex) c1 00 07 00 01 00 62 03 13 d3 ==================================================================
Здесь модуль настроен на фиксированный режим. Его адрес 0x0001, идентификатор сети 0x00. Установлена минимальная мощность передатчика 10 дБм и канал 19 для работы на частоте 869,125 МГц.
Используется режим предварительного прослушивания эфира перед передачей LBT, а также передача в пакете байта RSSI.
Вместо конфигурации в консоли может появиться ошибка:
Открываю COM14 @ 9600 baud... Нет ответа от модуля. Проверьте: модуль в режиме конфигурации (постоянный красный светодиод), порт не занят другой программой, baud rate указан верно.
Проверьте три вещи: верный ли COM-порт, включён ли режим конфигурирования и не занял ли порт другая программа.
Установка новой конфигурации модуля
Для установки новой конфигурации модуля вы можете воспользоваться программой e22_configure.py или фирменной утилитой RF_Setting(E22-E9X(SL)) V3.5(EN).exe, скачанной из раздела File Downloads страницы модуля.
В обоих случаях сначала переведите модуль в режим конфигурирования.
Программа e22_configure.py запускается так:
python e22_configure.py --port COM5 --address 0x0001
В --port передайте COM-порт, к которому подключен модуль, а в параметре --address задать адрес LoRa. Далее нам потребуется сконфигурировать два модуля, один из которых будет с адресом 0x0001, а другой — с адресом 0x0002.
Ниже я задаю для модулю, подключённому к порту COM13 адрес 0x0007:
$ python e22_configure.py --port COM13 --address 0x0007 Читаю текущую конфигурацию с COM13... Текущая конфигурация модуля: Адрес: 0x0001 (1) NetID: 0x00 Канал: 19 (~869.125 МГц) TX power: 10dBm Режим передачи: fixed LBT: вкл RSSI байт: вкл Сырой ответ: c1 00 07 00 01 00 62 03 13 d3 Записываю новую конфигурацию во Flash-память (0xC0)... ================================================== ПОДТВЕРЖДЕННАЯ И СОХРАНЕННАЯ КОНФИГУРАЦИЯ: ================================================== Адрес: 0x0007 (7) NetID: 0x00 Канал: 19 (~869.125 МГц) TX power: 10dBm Режим передачи: fixed LBT: вкл RSSI байт: вкл Сырой ответ: c1 00 07 00 07 00 62 03 13 d3 ==================================================
Успешно! Изменения сохранены во Flash-памяти модуля.
Параметры сохранятся при отключении питания и переподключении USB.
Программа собирает команды записи и передаёт их в write_config_eeprom:
# [0xC0 (Save Flash),0x00 (Start Reg),0x07 (Length), Payload (7 байт)] frame = bytes([0xC0, 0x00, 0x07, addh, addl, netid, reg0, reg1, channel, reg3]) print(f'\nЗаписываю новую конфигурацию во Flash-память (0xC0)...') resp = write_config_eeprom(ser, frame, wait_s=args.wait, debug=args.debug)
В скрипте мощность передачи задаётся константой MIN_TXPOWER_BITS:
reg1 = (cur[7] & 0b11111100) | MIN_TXPOWER_BITS # Устанавливаем минимальную мощность (10dBm)
Мощность можно поднять — в пределах, разрешённых законом:
# 10dBm MIN_TXPOWER_BITS = 0b11 # 22dBm (максимум): MIN_TXPOWER_BITS = 0b00 # 17dBm: MIN_TXPOWER_BITS = 0b01 # 13dBm: MIN_TXPOWER_BITS = 0b10
Для конфигурирования модуля с помощью утилиты RF_Setting(E22-E9X(SL)) V3.5(EN).exe выберите нужный COM-порт и щёлкните кнопку Get. Далее установите нужные параметры, как это показано на рис. 3 для модуля с адресом 0x0001 и щёлкните кнопку Set Param для записи новых значений в память модуля.

Программа простого мессенджера
Программа e22_transceiver.py выполняет обмен данными через E22-900T22U и её нужно запустить на обоих компьютерах, переведя предварительно модули в рабочий режим (светодиоды должны быть зелёными).
Если на первом компьютере модуль E22-900T22U подключен к порту COM13, запустите в консоли такую команду:
python e22_transceiver.py --port COM13 --baud 9600 --peer-address 0x0002 --channel 19 --rssi
Открыт COM13 @ 9600 baud, режим: адресный, peer=0x0002, channel=19, RSSI: вкл
Интерактивный режим. Вводите строки и жмите Enter для отправки. Ctrl+C для выхода.
У этого модуля прошит адрес 0x0001. Команда передаёт данные во второй модуль с адресом 0x0002 на канале 19 и показывает RSSI.
На втором компьютере, где к порту COM14 подключен модуль с адресом LoRa, равным 0x0002, запустите следующую команду:
python e22_transceiver.py --port COM14 --baud 9600 --peer-address 0x0001 --channel 19 --rssi
Открыт COM14 @ 9600 baud, режим: адресный, peer=0x0001, channel=19, RSSI: вкл
Интерактивный режим. Вводите строки и жмите Enter для отправки. Ctrl+C для выхода.
Пример команды для запуска на Raspberry Pi:
python e22_transceiver.py --port /dev/ttyUSB0 --baud 9600 --peer-address 0x0001 --channel 19 --rssi
Наберите строку в консоли одного компьютера, нажмите Enter — и она появится в консоли второго (рис. 4 и рис. 5).


Параметр --list покажет занятые COM-порты без диспетчера устройств:
python e22_transceiver.py --list COM1 Последовательный порт (COM1) COM13 USB-SERIAL CH340 (COM13) VID:PID=1A86:7523) COM14 USB-SERIAL CH340 (COM14) VID:PID=1A86:7523)
Чат с веб-сервером
Теперь сделаем LoRa-мессенджер, который принимает и отправляет данные через веб-сервер на Raspberry Pi 3. Сообщения пойдут из консоли Windows на сайт в смартфоне — и обратно.
На рис. 6 показан скриншот чата в смартфоне.

Тот же чат в консоли Windows показан на рис. 7.

Подготовка Raspberry Pi
Подключите модуль E22-900T22U к свободному порту USB микрокомпьютера Raspberry Pi 3, если вы ещё этого не сделали (рис. 8).

Установите необходимые пакеты и модули в ОС Raspbian:
sudo apt update && sudo apt install -y python3-serial python3-flask python3 -m pip install --upgrade pip pip3 install flask-sock
Убедитесь, что пользователь, от имени которого будет запускаться веб-сервер LoRa-мессенджера, добавлен в группу dialout:
sudo usermod -aG dialout $USER
Перелогиньтесь или перезагрузите систему.
Установка сервера
Создайте в каталоге пользователя подкаталог lora-web-chat и загрузите в него программу e22_web_chat.py.
Это Flask-сервер, с помощью которого можно обмениваться текстовыми сообщениями с удалённым узлом через браузер и модуль LoRa E22-900T22U.
Сообщения, принятые модулем LoRa от удалённого узла, доставляются через WebSocket на веб-страницу, открытую в браузере.
На странице есть поле ввода и кнопка Отправить. Сообщение, введённое в этом поле, передаётся в эфир и попадает на удалённый узел связи.
Для запуска сервера вы можете воспользоваться Unit-файлом lora-web-chat.service, отредактировав его нужным образом и скопировав в каталог /etc/systemd/system/.
Для запуска и просмотра журнала используйте такие команды:
sudo systemctl start lora-web-chat sudo systemctl enable lora-web-chat sudo journalctl -u lora-web-chat -f
По умолчанию в файле lora-web-chat.service команда запуска сервера выглядит так:
ExecStart=/usr/bin/python3 /home/frolov/lora-web-chat/e22_web_chat.py \ --port /dev/ttyUSB0 \ --baud 9600 \ --peer-address 0x0001 \ --channel 19 \ --rssi \ --web-host 0.0.0.0 \ --web-port 5000
Кроме параметров, знакомых по e22_transceiver.py, здесь заданы IP-адрес и порт веб-сервера. Модуль уже сконфигурирован на адрес 0x0002, и будет обмениваться данными с модулем, имеющим адрес 0x0001.
На удалённом узле запустите программу e22_transceiver.py:
python e22_transceiver.py --port COM13 --baud 9600 --peer-address 0x0002 --channel 19 --rssi
Здесь 0x0002 — адрес узла, с которым будем переписываться.
Чтобы быстро найти нужный COM-порт, запустите программу с параметром --list:
$ python3 e22_web_chat.py --list /dev/ttyUSB0 USB Serial (VID:PID=1A86:7523) /dev/ttyAMA0 ttyAMA0
Описание скрипта веб-cервера
Скрипт e22_web_chat.py, построенный на Flask, реализует веб-чат для обмена сообщениями через USB-донгл EBYTE E22-900T22U.
Скрипт открывает выбранный последовательный порт и поднимает локальный веб-сервер. В отдельном потоке идёт постоянное чтение данных из LoRa-эфира, а через простую HTML-страницу пользователь может отправлять текст.
Кадры формируются так же, как в e22_transceiver.py. Здесь поддерживаются как прозрачный режим передачи, так и фиксированный, где перед данными добавляется 3-байтный заголовок [ADDH][ADDL][CHANNEL]. Также можно разбирать последний байт пакета, чтобы вытащить RSSI.
Все принятые и отправленные сообщения хранятся в классе ChatState. Он не только накапливает историю, но и рассылает каждое новое сообщение подписчикам. Так сообщения долетают до браузера мгновенно.
Для отладки есть REST-эндпоинты /api/messages, /api/status и /api/send, но основной канал взаимодействия — WebSocket.
За WebSocket отвечает flask-sock. На маршруте /ws при подключении клиенту сразу отдаётся вся накопленная история сообщений. Затем соединение переходит в цикл обработки.
В этом цикле с таймаутом 0.2 с поочерёдно проверяются два источника событий. Во-первых, входящий текст от браузера — он сразу же уходит в порт через send_over_lora. Во-вторых, очередь исходящих обновлений ChatState, на которые клиент подписан через chat.subscribe. У каждого соединения своя очередь queue.Queue.
Поскольку flask-sock синхронный, именно такое чередование неблокирующего приёма и опроса очереди позволяет обходиться без отдельного цикла событий. На стороне фронтенда используется простой WebSocket API с автоматическим переподключением при обрыве связи.
Запускаем чат на парковке без интернета
Узел для LoRa-чата уже готов. Осталось сделать так, чтобы он работал без интернета вообще.
Идея простая: поднять на Raspberry Pi 3 точку доступа Wi-Fi и уже знакомый Flask-сервер. Если мобильный телефон подключится к этому хот-споту, то можно будет общаться через браузер и сервер e22_web_chat.py с удалённым узлом, даже если наш узел не подключен к интернету.
Настройка хот-спота
Дальше — инструкция для Raspbian GNU/Linux 11 (bullseye).
Проверьте, установлены ли hostapd и dnsmasq:
$ which hostapd dnsmasq /usr/sbin/hostapd /usr/sbin/dnsmasq
Если нет — ставим:
sudo apt update sudo apt-get install hostapd sudo apt-get install dnsmasq
Остановите сервис и снимите с него маску — в Raspbian hostapd по умолчанию замаскирован:
sudo systemctl stop hostapd sudo systemctl unmask hostapd
Сделайте резервную копию конфигурационных файлов:
sudo cp /etc/dhcpcd.conf /etc/dhcpcd.conf.bak sudo cp /etc/dnsmasq.conf /etc/dnsmasq.conf.bak 2>/dev/null sudo cp /etc/default/hostapd /etc/default/hostapd.bak sudo tee /etc/default/hostapd << 'EOF' DAEMON_CONF="/etc/hostapd/hostapd.conf" EOF
Настройте статический IP на wlan0:
sudo tee -a /etc/dhcpcd.conf << 'EOF' # --- LoRa hotspot start --- interface wlan0 static ip_address=192.168.4.1/24 nohook wpa_supplicant # --- LoRa hotspot end --- EOF
Настройте dnsmasq:
sudo mv /etc/dnsmasq.conf /etc/dnsmasq.conf.orig 2>/dev/null sudo tee /etc/dnsmasq.conf << 'EOF' interface=wlan0 dhcp-range=192.168.4.2,192.168.4.20,255.255.255.0,24h EOF
Настройте hostapd:
sudo tee /etc/hostapd/hostapd.conf << 'EOF' interface=wlan0 driver=nl80211 ssid=LoRa-2026 hw_mode=g channel=7 wmm_enabled=0 macaddr_acl=0 auth_algs=1 ignore_broadcast_ssid=0 wpa=2 wpa_passphrase=lora12345678 wpa_key_mgmt=WPA-PSK wpa_pairwise=TKIP rsn_pairwise=CCMP EOF
Обратите внимание, что здесь задан SSID сети «LoRa-2026» и пароль WPA «lora12345678». Измените их при необходимости.
Включите сервисы в автозагрузку:
sudo systemctl enable hostapd sudo systemctl enable dnsmasq
Запустите точку доступа:
sudo systemctl restart dhcpcd sudo systemctl start hostapd sudo systemctl start dnsmasq
Проверьте результат:
sudo systemctl status hostapd --no-pager hostapd.service - Access point and authentication server for Wi-Fi and Ethernet Loaded: loaded (/lib/systemd/system/hostapd.service; enabled; vendor preset: enabled) Active: active (running) since Thu 2026-08-13 13:16:20 MSK; 1h 28min ago Docs: man:hostapd(8) Process: 464 ExecStart=/usr/sbin/hostapd -B -P /run/hostapd.pid -B $DAEMON_OPTS ${DAEMON_CONF} (code=exited, status=0/SUCCESS) Main PID: 493 (hostapd) Tasks: 1 (limit: 1595) CPU: 177ms CGroup: /system.slice/hostapd.service └─493 /usr/sbin/hostapd -B -P /run/hostapd.pid -B /etc/hostapd/hostapd.conf Aug 13 13:16:19 raspberrypi systemd[1]: Starting Access point and authentication server for Wi-Fi and Ethernet... Aug 13 13:16:19 raspberrypi hostapd[464]: Configuration file: /etc/hostapd/hostapd.conf Aug 13 13:16:19 raspberrypi hostapd[464]: Using interface wlan0 with hwaddr b8:27:eb:30:79:07 and ssid "LoRa-2026" Aug 13 13:16:20 raspberrypi hostapd[464]: wlan0: interface state UNINITIALIZED->ENABLED Aug 13 13:16:20 raspberrypi hostapd[464]: wlan0: AP-ENABLED Aug 13 13:16:20 raspberrypi systemd[1]: Started Access point and authentication server for Wi-Fi and Ethernet. Aug 13 13:20:07 raspberrypi hostapd[493]: wlan0: STA 86:06:85:1e:7a:82 IEEE 802.11: associated Aug 13 13:20:07 raspberrypi hostapd[493]: wlan0: STA 86:06:85:1e:7a:82 RADIUS: starting accounting session EF593972BF5F091D Aug 13 13:20:07 raspberrypi hostapd[493]: wlan0: STA 86:06:85:1e:7a:82 WPA: pairwise key handshake completed (RSN) Aug 13 13:32:36 raspberrypi hostapd[493]: wlan0: STA 86:06:85:1e:7a:82 IEEE 802.11: disassociated
Должен появиться адрес 192.168.4.1/24:
ip addr show wlan0 3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether b8:27:eb:30:79:07 brd ff:ff:ff:ff:ff:ff inet 192.168.4.1/24 brd 192.168.4.255 scope global noprefixroute wlan0 valid_lft forever preferred_lft forever inet6 fe80::fb29:b416:30be:d540/64 scope link valid_lft forever preferred_lft forever inet6 fe80::ba27:ebff:fe30:7907/64 scope link valid_lft forever preferred_lft forever
Теперь возьмите смартфон и проверьте, что в списке Wi-Fi на телефоне появилась сеть LoRa-2026. Подключитесь к ней.
Запуск веб-сервера
Теперь запустите веб-сервер и проверьте, что все работает:
sudo systemctl start lora-web-chat sudo journalctl -u lora-web-chat -f
Откройте на смартфоне браузер и введите в адресной строке http://192.168.4.1:5000. Если вы все сделали правильно, откроется окно сайта нашего LoRa-мессенджера (рис. 9).

Сообщения этого чата на другом узле (консоль Windows) показаны на рис. 10.

Веб-чат на микроконтроллере ESP32-S3-N16R8
Дальше — скетч полностью автономного узла чата, которому интернет не нужен. Плата микроконтроллера ESP32-S3-N16R8 сама создаёт точку доступа Wi-Fi и поднимает веб-сайт, который устанавливает WebSocket-соединения с клиентами. При этом сообщения передаются по радиоканалу LoRa таким же узлам через простую mesh-сеть.
В этой реализации mesh-сети нет маршрутизации. Вся сеть работает на одном канале и одном широковещательном адресе. Это скорее "общий эфир", чем настоящая mesh-сеть с адресуемыми линками узел-узел. В сети неизбежен дублирующий трафик, а пропускная способность ограничена применением LBT.
Подобную сеть и узлы можно использовать для изучения технологии LoRa и экспериментов, например, в полевых условиях и там, где нет ни сотовой сети, ни интернета. В зависимости от рельефа и мощности передатчика связь добивает от сотен метров до нескольких километров.
Сборка макетов узлов
Макеты двух узлов веб-чата на микроконтроллере ESP32-S3-N16R8 и LoRa-модуле E22-900T22D показаны на рис. 11.

Подключите E22-900T22D к микроконтроллеру ESP32-S3-N16R8 следующим образом:
GND — контакт GND микроконтроллера ESP32-S3-N16R8;
TXD — GPIO17 (RX микроконтроллера);
RXD — GPIO18 (TX микроконтроллера);
AUX — GPIO16, статус готовности модуля E22-900T22D;
M0 — GPIO5, выбор режима;
M1 — GPIO6, выбор режима.
На контакт VCC модуля подайте напряжение 3,3 В от внешнего источника питания. Не стоит подключать его к контакту 3V3 микроконтроллера ESP32-S3-N16R8 из-за недостаточной нагрузочной способности этого контакта.
На плате ESP32-S3-N16R8 есть два разъёма, обозначенные как COM и USB. Для подключения к компьютеру я использовал разъём COM.
Чтобы проверить макет, я использовал программу чтения конфигурации модуля E22_ReadConfig.ino, описанную в статье «Ретранслятор LoRa своими руками: увеличиваем дальность связи без лишней мощности».
Подготовка скетча для узла веб-чата
Все файлы, необходимые для создания узла веб-чата, вы найдете в репозитории.
Создайте каталог проекта lora_chat_esp32 и разместите в нем файлы lora_chat_esp32.ino, partitions.csv, а также каталог data. В этом каталоге должны быть файлы index.html, style.css и app.js (рис. 12).

Настройка Arduino IDE
Считаем, что вы уже установили Arduino IDE 2.x. Откройте раздел File → Preferences и в поле Additional boards manager URLs добавьте строку:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
В разделе меню Tools → Board → Boards Manager выберите плату esp32 by Espressif Systems.
Далее установите библиотеки:
EByte LoRa E22 версии 1.0.9 от Renzo Mischianti;
Adafruit NeoPixel версии 1.1.15 от Adafruit;
ArduinoJson версии 7.4.3 от Benoit Blanchon;
AsyncTCP версии 3.5.0 от ESP32Async;
ESPAsyncWebServer версии 3.12.0 от ESP32Async.
Еще потребуется плагин для заливки данных в LittleFS. Зайдите на эту страницу и скачайте файл arduino-littlefs-upload-1.6.3.vsix. Скопируйте его в каталог C:\Users\имя_пользователя\.arduinoIDE\plugins, а затем перезапустите Arduino IDE.
Проверьте, что в палитре команд, доступной через комбинацию клавиш Ctrl+Shift+P или клавишу F1, появилась строка "Upload LittleFS to Pico/ESP8266/ESP32".
Далее нужно выполнить ряд настроек в меню Tools:
проверьте, что параметр USB CDC On Boot установлен в Disabled. Это необходимо для получения данных в отладочной консоли Arduino IDE при подключении платы ESP32-S3-N16R8 к компьютеру через разъём COM;
выберите для Flash Size значение 16MB;
для параметра PSRAM выберите значение OPI PSRAM;
в разделе Partition Scheme выберите Custom
Прошивка скетча
Откройте файл lora_chat_esp32.ino в Arduino IDE. Убедитесь, что выбрана правильная плата и COM-порт, к которому она подключена.
Далее залейте файловую систему, выбрав в меню команд строку "Upload LittleFS to Pico/ESP8266/ESP32".
Прошейте скетч обычным образом с помощью кнопки Upload.
Проверьте, что в отладочной консоли появились сообщения:
=== LoRa-2026 Chat (ESP32-S3-N16R8 + E22-900T22D) === LittleFS OK AP запущена: LoRa-2026 IP: 192.168.4.1 Веб-сервер запущен Конфигурация E22: OK Адрес узла: 00:07, NETID: 00, канал: 19 Готово. Подключайтесь к точке доступа и открывайте http://192.168.4.1/
Когда вы будете прошивать скетч на разные узлы, то для каждого узла задайте его уникальный адрес в сети и ник:
//#define NODE_ADDL 0x07 #define NODE_ADDL 0x08 //#define NODE_NICK "node1" #define NODE_NICK "node8"
Задайте уникальный SSID хот-спота Wi-Fi и IP-адрес веб-сайта:
//#define AP_SSID "LoRa-2026" #define AP_SSID "LoRa-2026-8" //IPAddress AP_IP(192, 168, 4, 1); IPAddress AP_IP(192, 168, 2, 2);
При необходимости поменяйте пароль Wi-Fi:
#define AP_PASSWORD "lora2026pass"
Если вы изменяете IP-адрес веб-сайта, отредактируйте его в сообщении, которое выводится в консоль отладки:
//Serial.println(F("Готово. Подключайтесь к точке доступа и открывайте http://192.168.4.1/")); Serial.println(F("Готово. Подключайтесь к точке доступа и открывайте http://192.168.2.2/"));
Подключение к Wi-Fi узлу ESP32-S3-N16R8
Подключитесь на смартфоне к узлу Wi-Fi «LoRa-2026» с паролем «lora2026pass». После этого откройте браузер и зайдите на страницу http://192.168.4.1/. Здесь вы можете вводить свои сообщения и просматривать сообщения от других узлов вашей LoRa-сети.
На рис. 13 показан диалог в сети на смартфонах iPhone и Android.

Помимо смартфонов в этой сети могут работать и компьютеры, на которых запущен скрипт Python, совместимый по протоколу со скетчем lora_chat_esp32.ino (рис. 14).

Этот скрипт будет описан ниже в разделе «Узел сети на базе скрипта Python».
Описание скетча lora_chat_esp32.ino
Прошивка скетча lora_chat_esp32.ino запускает две задачи в рамках FreeRTOS.
Первая из них работает на ядре 0. Она принимает пакеты с модуля E22-900T22D, проверяет их целостность, разбирает и собирает сообщения, ретранслирует чужие пакеты и отправляет исходящие сообщения из очереди.
В рамках второй задачи работает цикл loop. Он обслуживает точку доступа Wi-Fi, DNS-сервер, AsyncWebServer и WebSocket, то есть всё необходимое для общения с браузером пользователя. Обычно это браузер смартфона, подключенного к точке доступа Wi-Fi.
Каждый узел создаёт свою точку доступа и свой сайт. Смартфон подключается к одному узлу за раз, поэтому, чтобы видеть обе стороны диалога одновременно, понадобятся два устройства.
Когда пользователь пишет сообщение через веб-сайт, открытый на смартфоне, оно попадает в очередь outQueue. Дальше broadcastChatJson рассылает его всем подключённым клиентам.
С помощью механизма Captive Portal устройство, подключившееся к Wi-Fi, автоматически перенаправляется на страницу /index.html.
Через эфир данные передаются в бинарной структуре размером 54 байта (с учётом байта RSSI):
#pragma pack(push, 1) struct ChatPacket { uint8_t msgId; // счётчик сообщений отправителя (0..255, с переполнением) uint8_t fromAddr; // ADDL отправителя uint8_t ttl; // сколько раз ещё можно переретранслировать uint8_t chunkIndex; // номер фрагмента (с 0) uint8_t chunkTotal; // всего фрагментов в сообщении char nick[NICK_LEN]; char text[TEXT_CHUNK_LEN]; uint8_t crc8; // CRC8 по всем предыдущим полям (msgId..text) }; #pragma pack(pop)
Для передачи используется широковещательный адрес 0xFFFF, поэтому данные получают все узлы, работающие на заданном канале и в сети с заданным идентификатором.
Передаваемое сообщение разбивается на чанки по 38 байт. Размер буфера сборки ограничивает количество фрагментов до восьми.
Скетч ретранслирует пакеты лавинно, с TTL. Любой узел, приняв чужой пакет с ttl > 0, уменьшает TTL на 1, пересчитывает CRC и пересылает пакет дальше со случайной задержкой (20–120 мс). Случайная задержка разводит по времени соседние узлы, чтобы они не заговорили разом.
Чтобы одно и то же ретранслированное сообщение на показывалось и не пересылалось повторно, применяется дедупликация по паре (адрес источника fromAddr, идентификатор сообщения msgId). Старые записи со временем «протухают» и не блокируют навсегда переиспользованные msgId. Это актуально после перезапуска клиента.
Сообщения, разрезанные на части, собираются обратно, а контрольная сумма CRC проверяется на каждом пакете. При несовпадении пакет отбрасывается и не попадает в буфер сборки.
Перечислю самые важные функции скетча lora_chat_esp32.ino:
configureE22 — программирует модуль E22 при старте. Функция задаёт адрес узла, NETID, канал 19 для частоты 869,125 МГц, скорость в эфире 2.4 кбит/с, фиксированный режим передачи, включает LBT и RSSI-байт;
sendChatOverLora — разделяет исходящий текст на чанки по 38 байт, формирует ChatPacket для каждого, вычисляет CRC8 и отправляет данные функцией sendFixedMessage. Из-за включённого режима WOR приходится делать паузу свыше 2 секунд;
repeatPacket — реализует логику ретрансляции. Здесь уменьшается TTL, пересчитывается CRC8, добавляется случайная задержка, после которой пакет данных переотправляется;
handleIncomingPacket — эта функция приёма разводит сообщения, состоящие из одного или нескольких пакетов, обновляет кэш дедупликации, сохраняет данные в слот пересборки. При полной сборке она публикует сообщение в историю и рассылает его по WebSocket, а также инициирует ретрансляцию дальше по сети;
crc8, fillCrc, crcValid — функции расчёта и проверки контрольной суммы. Вызываются при отправке, ретрансляции и приёме: если сумма не сошлась, пакет отбрасывается;
loraTask — главный цикл на ядре 0. Здесь выполняется приём и разбор пакетов, проверка CRC, обработка исходящей очереди, обновление светодиодной индикации;
onWsEvent — это обработчик WebSocket-событий. При подключении клиента он отправляет ему накопленную историю сообщений history, а при получении сообщения от клиента записывает его в outQueue на отправку по радио и сразу показывает его во всех локально подключённых вкладках.
Также в программе реализована индикация передачи данных RGB-светодиодом WS2812, встроенным в микроконтроллер ESP32-S3-N16R8. Светодиод сигнализирует синей вспышкой при отправке собственных данных, а зелёной — при ретрансляции чужого пакета.
Узел сети на базе скрипта Python
Роль ещё одной точки доступа может играть скрипт e22_chat_console.py. Я запустил его на компьютере с ОС Microsoft Windows, к которому подключен USB-донгл EBYTE LoRa E22-900T22U:
python e22_chat_console.py --port COM13 --nick PC Открыт COM13 @ 9600 baud Узел: addr=0x01, channel=19, nick='PC', TTL=3, RSSI байт: вкл Размер пакета на линии: 54 байт (payload 53 + 1 RSSI) Вводите текст и жмите Enter для отправки в чат LoRa-2026. Ctrl+C — выход.
Для совместимости с только что описанным скетчем LoRa-чата в этом скрипте реализован бинарный протокол ChatPacket. Также используется тот же самый алгоритм CRC8, такая же нарезка текста на чанки по 38 «сырых» байт.
Особенности режима WOR для модуля E22-900T22U
У донгла E22-900T22U нельзя отключить WOR. WOR — это режим энергосбережения радиомодуля, при котором приёмник большую часть времени спит и лишь периодически кратковременно включается для проверки эфира на наличие сигнала. Это резко снижает энергопотребление ценой задержки и необходимости удлинённой преамбулы у передатчика.
При конфигурировании модуля E22-900T22U можно задать два режима WOR, определяющих роль модуля:
Receiver — модуль периодически "просыпается" на короткое время, слушает эфир и снова засыпает. Период задаётся отдельным полем WOR_PERIOD с шагом 500 мс, в диапазоне 500–4000 мс;
Transmitter — перед каждым пакетом модуль сам добавляет удлинённую преамбулу, чтобы "разбудить" удалённый узел, находящийся в режиме WOR Receiver
Если приёмник спит в цикле WOR с периодом до 2000 мс, то передатчик должен излучать пробуждающую преамбулу дольше, чем этот цикл. В противном случае момент передачи может не попасть в короткое окно прослушивания приёмника, и пакет будет потерян.
Чтобы гарантированно перекрыть весь возможный цикл сна приёмника, в функцию sendFixedMessage скетча lora_chat_esp32.ino была добавлена задержка:
delay(2520);
Если в вашей сети нет узлов с донглом E22-900T22U, а для остальных узлов режим WOR выключен, то вы можете попробовать уменьшить эту задержку до 50 мс:
delay(50);
Итоги
В этой статье я рассказал, как собственными силами реализовать чат на модулях LoRa. Конечно, уже существуют готовые решения для подобных чатов, например проекты Meshtastic и MeshCore. Однако настраивая всё самостоятельно, вы сможете создать именно такой чат, который вам нужен. Также на собственном опыте вы познакомитесь с особенностями реализации подобных систем.
Надеюсь, вам было интересно. Ожидайте следующих статей про технологию LoRa и пишите, о каких областях применения LoRa вам ещё хотелось бы узнать.
Автор @AlexandreFrolov
НЛО прилетело и оставило здесь промокод для читателей нашего блога:
-15% на заказ нового VDS — HABRFIRSTVDS.

