Files
egorin/lora/gateway_lora_eth.ino
T

165 lines
4.9 KiB
Arduino

/*
* ================================================================
* ШЛЮЗ v2: LoRa ↔ Ethernet (W5500) ↔ MQTT
* Проекты: Егорин / ORBIT · протокол v2 (nodes/...)
* ================================================================
* Железо:
* - ESP32 DevKit V1
* - LoRa RA-01H (SX1276, 869 МГц)
* - Ethernet W5500 mini (SPI)
* Оба модуля на общей шине VSPI, разные CS:
* SCK=18 MISO=19 MOSI=23
* LoRa: NSS=5 RST=14 DIO0=26
* W5500: CS=4
*
* Ретранслирует LoRa-узлы в единое пространство топиков:
* пакет {"t":"info",...} → nodes/<id>/info (retained)
* пакет {"t":"d",...} → nodes/<id>/data (+rssi/snr/gw)
* nodes/<id>/cmd (MQTT) → LoRa вниз, адресно по id
*
* Онлайн/оффлайн LoRa-узлов решает сервер по last_seen
* (у радиоузлов нет LWT).
*
* Библиотеки: LoRa (Sandeep Mistry), Ethernet (Arduino),
* PubSubClient, ArduinoJson
* ================================================================
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
#include <LoRa.h>
#include <ArduinoJson.h>
// ================== НАСТРОЙКИ ==================
#define GW_ID "gw1"
#define MQTT_HOST "192.168.1.200"
#define MQTT_PORT 1883
#define MQTT_USER ""
#define MQTT_PASSWD ""
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x10, 0x01}; // уникальный на шлюз
// LoRa — параметры должны совпадать с узлами
#define LORA_FREQ 869000000E0
#define LORA_SF 9
#define LORA_BW 125E3
#define LORA_SYNC 0x2B
#define LORA_TX_DBM 20
#define LORA_NSS 5
#define LORA_RST 14
#define LORA_DIO0 26
#define W5500_CS 4
EthernetClient ethClient;
PubSubClient mqtt(ethClient);
// ================== СЕТЬ ==================
void netBegin() {
Ethernet.init(W5500_CS);
Serial.print("DHCP... ");
while (Ethernet.begin(mac) == 0) {
Serial.println("fail, retry 5s");
delay(5000);
}
Serial.println(Ethernet.localIP());
}
void mqttReconnect() {
while (!mqtt.connected()) {
if (mqtt.connect("lora-" GW_ID, MQTT_USER, MQTT_PASSWD,
"gateways/" GW_ID "/status", 1, true, "offline")) {
mqtt.publish("gateways/" GW_ID "/status", "online", true);
mqtt.subscribe("nodes/+/cmd");
Serial.println("MQTT OK");
} else {
Serial.printf("MQTT rc=%d, retry 5s\n", mqtt.state());
delay(5000);
}
}
}
// ================== MQTT → LoRa (вниз) ==================
void mqttCallback(char *topic, byte *payload, unsigned int len) {
String t(topic);
int a = t.indexOf('/') + 1;
int b = t.indexOf('/', a);
String nodeId = t.substring(a, b);
// LAN-узлы обрабатывают свои cmd сами; шлюз ретранслирует всё,
// радиоузлы фильтруют по своему id, чужое игнорируют.
StaticJsonDocument<192> doc;
if (deserializeJson(doc, payload, len)) return;
doc["id"] = nodeId;
char buf[192];
size_t n = serializeJson(doc, buf);
LoRa.beginPacket();
LoRa.write((const uint8_t*)buf, n);
LoRa.endPacket();
LoRa.receive();
Serial.printf("DOWN [%s]: %s\n", nodeId.c_str(), buf);
}
// ================== LoRa → MQTT (вверх) ==================
void handleLoRaPacket() {
String s;
while (LoRa.available()) s += (char)LoRa.read();
StaticJsonDocument<384> doc;
if (deserializeJson(doc, s)) { Serial.println("Bad pkt: " + s); return; }
const char *id = doc["id"];
if (!id) return;
const char *type = doc["t"] | "d";
bool isInfo = !strcmp(type, "info");
doc["gw"] = GW_ID;
if (!isInfo) {
doc["rssi"] = LoRa.packetRssi();
doc["snr"] = serialized(String(LoRa.packetSnr(), 1));
}
doc.remove("t");
char buf[384];
size_t n = serializeJson(doc, buf);
char topic[64];
snprintf(topic, sizeof(topic), "nodes/%s/%s", id, isInfo ? "info" : "data");
mqtt.publish(topic, (const uint8_t*)buf, n, isInfo); // info — retained
Serial.printf("UP %s: %s\n", topic, buf);
}
// ================== SETUP / LOOP ==================
void setup() {
Serial.begin(115200);
SPI.begin(18, 19, 23);
netBegin();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(mqttCallback);
mqtt.setBufferSize(512);
LoRa.setPins(LORA_NSS, LORA_RST, LORA_DIO0);
if (!LoRa.begin(LORA_FREQ)) {
Serial.println("LoRa init FAIL");
while (true) delay(1000);
}
LoRa.setSpreadingFactor(LORA_SF);
LoRa.setSignalBandwidth(LORA_BW);
LoRa.setSyncWord(LORA_SYNC);
LoRa.setTxPower(LORA_TX_DBM);
LoRa.enableCrc();
LoRa.receive();
Serial.println("Gateway " GW_ID " ready (ETH)");
}
void loop() {
Ethernet.maintain(); // продление DHCP-аренды
if (!mqtt.connected()) mqttReconnect();
mqtt.loop();
if (LoRa.parsePacket() > 0) handleLoRaPacket();
}