LoRa-шлюз на Raspberry Pi: собираем данные с десятка датчиков в поле

Фермозавр·9 апреля 2026 г.·11 мин чтения

Один ESP32-приёмник — это точка-точка. Raspberry Pi со скриптом на Python превращается в полноценный шлюз: принимает LoRa-пакеты от 50 датчиков, пишет в InfluxDB, рисует Grafana, шлёт алерты в Telegram.

LoRa-шлюз на Raspberry Pi: собираем данные с десятка датчиков в поле

В предыдущей статье мы собрали 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 рублей и один выходной.

Источник: Фермозавр

💬 Комментарии

Чтобы оставить комментарий, войдите или зарегистрируйтесь

Загрузка комментариев...

Похожие статьи