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

Если речь идёт о текстовых сообщениях, помогут каналы связи, созданные с помощью технологии 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 внутри.

Рис. 1. Модули E22-900T22U
Рис. 1. Модули E22-900T22U

Такой модуль очень легко подключить к микроконтроллеру, микрокомпьютеру, ноутбуку или к настольному компьютеру, где есть разъём 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).

Рис. 2. Просмотр задействованных COM-портов
Рис. 2. Просмотр задействованных COM-портов

Если вам нужно подключить несколько модулей 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 для записи новых значений в память модуля.

Рис. 3. Утилита конфигурирования RF_Setting(E22-E9X(SL)) V3.5(EN).exe
Рис. 3. Утилита конфигурирования RF_Setting(E22-E9X(SL)) V3.5(EN).exe

Программа простого мессенджера

Программа 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).

Рис. 4. Сообщения на первом компьютере
Рис. 4. Сообщения на первом компьютере
Рис. 5. Сообщения на втором компьютере
Рис. 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 показан скриншот чата в смартфоне.

Рис. 6. Скриншот чата в смартфоне
Рис. 6. Скриншот чата в смартфоне

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

Рис. 7. Чат в консоли Windows
Рис. 7. Чат в консоли Windows

Подготовка Raspberry Pi

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

Рис. 8. Подключение модуля E22-900T22U
Рис. 8. Подключение модуля E22-900T22U

Установите необходимые пакеты и модули в ОС 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).

Рис. 9. Страница сайта LoRa-мессенджера
Рис. 9. Страница сайта LoRa-мессенджера

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

Рис. 10. Сообщения чата в консольном окне Windows
Рис. 10. Сообщения чата в консольном окне Windows

Веб-чат на микроконтроллере ESP32-S3-N16R8

Дальше — скетч полностью автономного узла чата, которому интернет не нужен. Плата микроконтроллера ESP32-S3-N16R8 сама создаёт точку доступа Wi-Fi и поднимает веб-сайт, который устанавливает WebSocket-соединения с клиентами. При этом сообщения передаются по радиоканалу LoRa таким же узлам через простую mesh-сеть.

В этой реализации mesh-сети нет маршрутизации. Вся сеть работает на одном канале и одном широковещательном адресе. Это скорее "общий эфир", чем настоящая mesh-сеть с адресуемыми линками узел-узел. В сети неизбежен дублирующий трафик, а пропускная способность ограничена применением LBT.

Подобную сеть и узлы можно использовать для изучения технологии LoRa и экспериментов, например, в полевых условиях и там, где нет ни сотовой сети, ни интернета. В зависимости от рельефа и мощности передатчика связь добивает от сотен метров до нескольких километров.

Сборка макетов узлов

Макеты двух узлов веб-чата на микроконтроллере ESP32-S3-N16R8 и LoRa-модуле E22-900T22D показаны на рис. 11.

Рис. 11. Макеты узлов веб-чата
Рис. 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).

Рис. 12. Структура каталогов проекта
Рис. 12. Структура каталогов проекта

Настройка Arduino IDE

Считаем, что вы уже установили Arduino IDE 2.x. Откройте раздел FilePreferences и в поле Additional boards manager URLs добавьте строку:

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

В разделе меню ToolsBoardBoards 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.

Рис. 13. Диалог в сети на смартфонах iPhone и Android
Рис. 13. Диалог в сети на смартфонах iPhone и Android

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

Рис. 14. Диалог в сети через скрипт Python
Рис. 14. Диалог в сети через скрипт Python

Этот скрипт будет описан ниже в разделе «Узел сети на базе скрипта 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.

Положение об акции