В предыдущей статье мы собрали LoRa-передатчик на ESP32 + SX1278, который отправляет данные на 10 км. Но один приёмник на другом ESP32 — это ещё не система. Чтобы собирать показания с десятка датчиков, хранить историю и строить графики, нужен полноценный шлюз. Сегодня собираем его на Raspberry Pi: приём LoRa-пакетов, парсинг, запись в InfluxDB и визуализация в Grafana — всё за один вечер.
Зачем шлюз, если есть приёмник на ESP32
ESP32-приёмник из прошлой статьи работает — но у него нет ни базы данных, ни графиков, ни алертов. Данные улетают в Serial Monitor и теряются. Raspberry Pi решает сразу несколько задач:
- Хранение. InfluxDB на SD-карте или SSD хранит данные за месяцы и годы. Можно сравнить этот июнь с прошлым
- Визуализация. Grafana превращает строчки в графики, карты, спидометры — всё то, что мы настраивали в статье про Grafana
- Алерты. Температура в теплице выше 40°C? Raspberry Pi пришлёт SMS через Telegram-бота или дёрнет webhook
- Масштабируемость. ESP32 обрабатывает один пакет за раз. Python-скрипт на Pi может парсить пакеты от 50+ датчиков, фильтровать дубли, проверять CRC
- Сетевой мост. Pi подключён к WiFi/Ethernet, а в поле у датчиков только LoRa. Шлюз — это мост между двумя мирами
Что понадобится
Минимальный набор для шлюза:
- Raspberry Pi 3B+/4/5 — любая модель с GPIO. Б/у «тройка» на Avito — 2000–3000 ₽
- Модуль SX1278 (433 МГц) — тот же, что в передатчике, ~250 ₽
- Провода DuPont «мама-мама» — 8 штук, ~50 ₽
- Антенна 433 МГц — SMA-штырь (150 ₽) или пружинка (50 ₽)
- MicroSD 32 ГБ — для Raspberry Pi OS + InfluxDB, ~400 ₽
- Блок питания 5V 3A — USB-C для Pi 4/5 или microUSB для Pi 3, ~500 ₽
Итого: если Pi уже есть — около 450 ₽. Если покупать всё с нуля — 3500–4500 ₽. Сравните со стоимостью промышленного LoRaWAN-шлюза (15 000–60 000 ₽), и выбор очевиден.
Подключение SX1278 к Raspberry Pi по SPI
SX1278 общается по SPI — тому же протоколу, что и с ESP32, только пины другие. Raspberry Pi имеет два SPI-канала, нам нужен SPI0:
SX1278 Raspberry Pi (GPIO)
------ -------------------
VCC → 3.3V (Pin 1)
GND → GND (Pin 6)
SCK → GPIO 11 / SCLK (Pin 23)
MISO → GPIO 9 / MISO (Pin 21)
MOSI → GPIO 10 / MOSI (Pin 19)
NSS (CS) → GPIO 8 / CE0 (Pin 24)
RST → GPIO 25 (Pin 22)
DIO0 → GPIO 24 (Pin 18)
Критично: SX1278 питается от 3.3V — ни в коем случае не от 5V! Raspberry Pi GPIO тоже 3.3V, так что преобразователь уровней не нужен. А вот ESP32 из прошлой статьи тоже был 3.3V — совместимость идеальная.
Включаем SPI в Raspberry Pi OS
По умолчанию SPI выключен. Включаем:
sudo raspi-config
# → Interface Options → SPI → Enable
# Проверяем:
ls /dev/spidev*
# Должно показать: /dev/spidev0.0 /dev/spidev0.1
Если у вас Raspberry Pi OS Lite (без GUI) — этого достаточно. Перезагрузка не нужна, но если устройства не появились — перезагрузите:
sudo reboot
Python-скрипт: приём LoRa-пакетов
Для работы с SX1278 из Python используем библиотеку pyLoRa. Она маленькая, без зависимостей от C-библиотек, работает через spidev + RPi.GPIO:
# Установка
pip3 install pyLoRa RPi.GPIO spidev influxdb-client
Создаём файл /opt/lora-gateway/gateway.py:
#!/usr/bin/env python3
"""LoRa Gateway: приём пакетов → парсинг → InfluxDB."""
import time
import logging
from SX127x.LoRa import LoRa
from SX127x.board_config import BOARD
from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS
# === Настройки ===
INFLUX_URL = "http://localhost:8086"
INFLUX_TOKEN = "your-influx-token"
INFLUX_ORG = "farm"
INFLUX_BUCKET = "sensors"
LORA_FREQ = 433.0 # МГц — должна совпадать с передатчиком!
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler("/opt/lora-gateway/gateway.log"),
logging.StreamHandler()
]
)
log = logging.getLogger("gateway")
# InfluxDB клиент
influx = InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG)
write_api = influx.write_api(write_options=SYNCHRONOUS)
class LoRaGateway(LoRa):
def __init__(self):
super().__init__(verbose=False)
self.set_mode(0x01) # Standby
self.set_freq(LORA_FREQ)
# Настройки — должны совпадать с передатчиком!
self.set_spreading_factor(10)
self.set_bw(7) # 125 kHz
self.set_coding_rate(2) # 4/8
self.set_rx_crc(True)
self.set_pa_config(pa_select=1)
log.info("LoRa init: %.1f MHz, SF10, BW125, CR4/8", LORA_FREQ)
def on_rx_done(self):
"""Вызывается при получении пакета."""
payload = self.read_payload(nocheck=True)
raw = bytes(payload).decode("utf-8", errors="replace").strip()
rssi = self.get_pkt_rssi_value()
snr = self.get_pkt_snr_value()
log.info("RX [RSSI %d, SNR %.1f]: %s", rssi, snr, raw)
try:
self.parse_and_store(raw, rssi, snr)
except Exception as e:
log.error("Parse error: %s — raw: %s", e, raw)
# Снова в режим приёма
self.reset_ptr_rx()
self.set_mode(0x05) # RX continuous
def parse_and_store(self, raw, rssi, snr):
"""
Протокол из статьи #14:
DEVICE_ID:TEMP:HUMIDITY:SOIL:VOLTAGE
Пример: FIELD_01:25.3:61:540:3.85
"""
parts = raw.split(":")
if len(parts) != 5:
log.warning("Bad format (expected 5 fields): %s", raw)
return
device_id = parts[0]
temp = float(parts[1])
humidity = float(parts[2])
soil = int(parts[3])
voltage = float(parts[4])
# Пишем в InfluxDB
point = (
Point("sensor_data")
.tag("device", device_id)
.field("temperature", temp)
.field("humidity", humidity)
.field("soil_moisture", soil)
.field("battery_voltage", voltage)
.field("rssi", rssi)
.field("snr", snr)
)
write_api.write(bucket=INFLUX_BUCKET, record=point)
log.info("Stored: %s → temp=%.1f hum=%.0f soil=%d bat=%.2fV",
device_id, temp, humidity, soil, voltage)
def main():
BOARD.setup()
gw = LoRaGateway()
gw.set_mode(0x05) # RX continuous
log.info("Gateway started, listening...")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
log.info("Shutting down")
finally:
gw.set_mode(0x00) # Sleep
BOARD.teardown()
if __name__ == "__main__":
main()
Скрипт делает три вещи: слушает эфир, парсит пакеты по протоколу из статьи #14 (DEVICE_ID:TEMP:HUMIDITY:SOIL:VOLTAGE) и пишет в InfluxDB. RSSI и SNR сохраняются тоже — потом пригодятся для мониторинга качества связи.
Запуск InfluxDB и Grafana на Raspberry Pi
Если вы уже настроили InfluxDB и Grafana по нашей инструкции — пропускайте этот раздел. Если нет — краткая установка:
# InfluxDB 2.x
wget https://dl.influxdata.com/influxdb/releases/influxdb2_2.7.6-1_arm64.deb
sudo dpkg -i influxdb2_2.7.6-1_arm64.deb
sudo systemctl enable --now influxdb
# Первичная настройка (в браузере http://PI_IP:8086)
# Создайте org: farm, bucket: sensors, скопируйте token
# Grafana
sudo apt-get install -y adduser libfontconfig1 musl
wget https://dl.grafana.com/oss/release/grafana_11.1.0_arm64.deb
sudo dpkg -i grafana_11.1.0_arm64.deb
sudo systemctl enable --now grafana-server
# Вход: http://PI_IP:3000 (admin/admin)
После установки добавьте InfluxDB как Data Source в Grafana (тип: InfluxDB / Flux) и создайте дашборд. Подробные шаги — в статье про Grafana.
Systemd-сервис: шлюз работает 24/7
Шлюз должен запускаться автоматически при включении Pi и перезапускаться при падении. Systemd справится:
# /etc/systemd/system/lora-gateway.service
[Unit]
Description=LoRa Gateway
After=influxdb.service network-online.target
Wants=network-online.target
[Service]
Type=simple
User=pi
WorkingDirectory=/opt/lora-gateway
ExecStart=/usr/bin/python3 /opt/lora-gateway/gateway.py
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now lora-gateway
# Проверка:
sudo systemctl status lora-gateway
journalctl -u lora-gateway -f # Лог в реальном времени
Если InfluxDB ещё не стартовал — шлюз подождёт (директива After=influxdb.service). Если скрипт упал — systemd перезапустит его через 10 секунд (директива Restart=always).
Обработка нескольких датчиков: фильтрация и дедупликация
Когда в поле 10–20 передатчиков, возникают два вопроса: как отличить «свои» пакеты от чужого шума на 433 МГц и как не записать один пакет дважды (LoRa иногда доставляет дубли).
Белый список устройств
Добавьте в скрипт простой фильтр:
KNOWN_DEVICES = {
"FIELD_01": "Поле озимой пшеницы",
"FIELD_02": "Теплица №3",
"FIELD_03": "Сад (яблони)",
"GREENHOUSE_01": "Рассада",
}
def parse_and_store(self, raw, rssi, snr):
parts = raw.split(":")
if len(parts) != 5:
return
device_id = parts[0]
if device_id not in KNOWN_DEVICES:
log.warning("Unknown device: %s (ignored)", device_id)
return
# ... дальше как было
Неизвестные устройства — пульты гаражных ворот, метеостанции соседей — будут логироваться, но не попадут в базу.
Дедупликация по хешу
ESP32 из статьи #14 отправляет пакет один раз и засыпает. Но LoRa на физическом уровне иногда «переповторяет» пакет. Простой способ отфильтровать — хранить хеш последних N сообщений:
from hashlib import md5
from collections import deque
recent_hashes = deque(maxlen=100)
def is_duplicate(raw):
h = md5(raw.encode()).hexdigest()
if h in recent_hashes:
return True
recent_hashes.append(h)
return False
Вызываем is_duplicate(raw) перед парсингом. Хранилище на 100 записей хватит для 20 датчиков с интервалом 15 минут — это 5+ часов буфера.
Grafana-панели для LoRa-сети
К стандартным графикам температуры и влажности добавьте две панели для мониторинга самой радиосети:
Панель 1: RSSI по устройствам (Time Series)
from(bucket: "sensors")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "sensor_data" and r._field == "rssi")
|> aggregateWindow(every: 1h, fn: mean)
Если RSSI одного датчика вдруг упал с −80 до −115 — антенна повернулась, батарея села или выросла кукуруза между датчиком и шлюзом (серьёзно, кукуруза ослабляет 433 МГц на 6–10 дБ).
Панель 2: Пропущенные отчёты (Stat)
from(bucket: "sensors")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "sensor_data" and r._field == "temperature")
|> group(columns: ["device"])
|> count()
|> map(fn: (r) => ({r with _value: if r._value >= 3 then 1 else 0}))
Если датчик шлёт каждые 15 минут, за час должно быть 4 записи. Меньше 3 — жёлтый алерт. Ноль — красный: датчик замолчал.
Панель 3: Батарея по устройствам (Gauge)
from(bucket: "sensors")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "sensor_data" and r._field == "battery_voltage")
|> last()
|> group(columns: ["device"])
Пороги: зелёный >3.5V, жёлтый 3.2–3.5V, красный <3.2V. При 3.0V 18650 уже разряжена на 95% — пора менять.
Алерты: Telegram-уведомления
Grafana умеет отправлять алерты в Telegram без дополнительного кода. Настройка за 5 минут:
- Создайте бота через
@BotFatherв Telegram, получите токен - Узнайте свой chat_id: напишите боту, откройте
https://api.telegram.org/bot<TOKEN>/getUpdates - В Grafana: Alerting → Contact Points → New → Telegram, вставьте token и chat_id
- Создайте Alert Rule: если
battery_voltage < 3.2последние 30 минут — fire
Теперь если батарея датчика «FIELD_02» сядет — вы получите сообщение в Telegram: «FIELD_02: battery_voltage = 3.15V». Не нужно ходить по полю и проверять каждый датчик вручную.
Защита от сбоев: watchdog и логротация
Шлюз работает месяцами без присмотра. Но SD-карта Raspberry Pi не бесконечная, а лог файл растёт. Два защитных механизма:
Логротация
# /etc/logrotate.d/lora-gateway
/opt/lora-gateway/gateway.log {
daily
rotate 7
compress
missingok
notifempty
copytruncate
}
Хранит логи за 7 дней, сжимает старые. Файл не будет расти бесконечно.
Hardware watchdog
Raspberry Pi имеет аппаратный watchdog — если система зависнет, он перезагрузит плату:
# /etc/systemd/system.conf
RuntimeWatchdogSec=30
RebootWatchdogSec=10min
Если systemd не «покормит» watchdog 30 секунд — Pi перезагрузится. LoRa-шлюз перезапустится автоматически через systemd.
Безопасность: шлюз не должен торчать в интернет
Типичная ошибка: открыть порт 8086 (InfluxDB) или 3000 (Grafana) напрямую в интернет. Правильная схема:
- InfluxDB слушает только
127.0.0.1:8086— не принимает подключения извне - Grafana — через Cloudflare Tunnel или VPN. Мы описали настройку Tunnel в статье про Grafana
- SSH — только по ключу, пароль выключен (
PasswordAuthentication noв sshd_config) - Firewall:
sudo ufw default deny incoming
sudo ufw allow ssh
sudo ufw enable
LoRa-эфир не шифруется по умолчанию (мы добавили AES-128 в прошлой статье). Если вы пропустили этот шаг — вернитесь и добавьте шифрование. Без него любой с SX1278 в радиусе 10 км может читать ваши пакеты.
Масштабирование: несколько шлюзов
Одного шлюза хватит на площадь 5–10 км в радиусе (зависит от антенны и рельефа). Если хозяйство больше — ставьте второй шлюз. Оба пишут в один InfluxDB (по WiFi/Ethernet), Grafana показывает все данные на одном дашборде.
Схема для крупного хозяйства:
LoRa 433 МГц
[Датчик 1] ──────────┐
[Датчик 2] ──────────┤
[Датчик 3] ─────┐ │
▼ ▼
[Шлюз A (Pi)]──── WiFi ────┐
▼
[Датчик 4] ──────────┐ [Сервер InfluxDB]
[Датчик 5] ──────────┤ [Grafana]
[Датчик 6] ─────┐ │ ▲
▼ ▼ │
[Шлюз B (Pi)]──── WiFi ────┘
Каждый шлюз добавляет тег gateway к записям — так вы видите, через какой шлюз пришёл пакет. Добавьте в скрипт:
GATEWAY_ID = "GW_BARN" # или "GW_FIELD_NORTH"
point = (
Point("sensor_data")
.tag("device", device_id)
.tag("gateway", GATEWAY_ID) # ← новый тег
.field("temperature", temp)
# ...
)
Реальные грабли и решения
За два сезона эксплуатации LoRa-сети набрался список типичных проблем:
SD-карта умирает через 6–12 месяцев. InfluxDB активно пишет на диск. Решение: подключите USB SSD (500 ₽ за 120 ГБ б/у) и перенесите на него /var/lib/influxdb2. Или купите SD endurance-серии (Samsung, SanDisk Max Endurance).
Raspberry Pi перегревается летом в закрытом шкафу. Шлюз часто стоит в металлическом щитке на стене амбара. При +35°C снаружи внутри может быть +55°C. Pi троттлит на 80°C. Решения: радиатор (100 ₽), вентиляционные отверстия, или маленький 5V вентилятор (200 ₽).
Время на Pi сбивается без интернета. Если шлюз стоит в поле без WiFi — часы уплывают после перезагрузки (у Pi нет батарейки для часов). Решение: модуль RTC DS3231 за 150 ₽ — батарейка CR2032 держит время годами.
Кукуруза и подсолнечник глушат сигнал. В июле-августе высокие культуры ослабляют LoRa на 6–15 дБ. Датчик, который весной работал с RSSI −85, к августу показывает −105. Решение: поднимите антенну шлюза на мачту 3–5 метров. 2-метровая алюминиевая труба + крепёж — 500 ₽.
Молния. Антенна на крыше — громоотвод. Ставьте грозозащиту (Gas Discharge Tube) на антенный кабель за 300 ₽. Или хотя бы отключайте антенну во время грозы. Одна молния — и вы потеряете и Pi, и SX1278.
Стоимость всей системы
Посчитаем полную LoRa-сеть для среднего хозяйства (500 га):
- 5 передатчиков (ESP32 + SX1278 + батарея + корпус) — 5 × 1200 ₽ = 6000 ₽
- 1 шлюз (Raspberry Pi 4 + SX1278 + SD + БП + корпус) — 5000 ₽
- Антенна на мачту SMA 5dBi — 500 ₽
- Мачта (алюминиевая труба 3м) — 500 ₽
Итого: ~12 000 ₽ за полноценную сеть мониторинга на 5 точек с графиками, алертами и историей. Коммерческий аналог (RAK WisGate + датчики Dragino) обойдётся в 80 000–150 000 ₽. Разница — на порядок.
Для сравнения: ТерраКвант и подобные платформы спутникового мониторинга покрывают анализ с неба, а LoRa-сеть дополняет их наземными данными — вместе получается полная картина.
Что дальше
Шлюз работает, данные льются в Grafana. Дальше можно развивать систему:
- MQTT-брокер (Mosquitto на том же Pi) — для интеграции с Home Assistant, Node-RED и другими системами умного дома
- Резервное хранение — дублировать данные в облако (InfluxDB Cloud Free хранит 30 дней бесплатно)
- OTA-обновление прошивок датчиков — чтобы не ходить по полю с ноутбуком для каждой правки кода
- Двусторонняя связь — шлюз отправляет команды датчикам (включить реле, изменить интервал отправки)
Но даже без этого у вас уже есть рабочая сеть: датчики в поле шлют данные на километры, шлюз всё собирает, InfluxDB хранит историю, Grafana рисует графики, Telegram предупреждает о проблемах. Всё за 12 000 рублей и один выходной.




