Мы создадим Mesh-сеть на модулях LoRa (SX1278), которая будет работать без интернета. Это идеально для удаленных районов или катастроф.
Протокол ARH-NET (ARDUINOHACKS Network):
cpp
#include <SPI.h>
#include <LoRa.h>
#define NODE_ID 1
#define BROADCAST_ADDR 255
// Структура пакета
struct Packet {
uint8_t src;
uint8_t dst;
uint8_t ttl;
uint8_t type; // 0=данные, 1=маршрутизация, 2=приветствие
uint8_t payload[32];
uint8_t checksum;
};
// Таблица маршрутизации (до 8 узлов)
struct RouteEntry {
uint8_t dest;
uint8_t next_hop;
uint8_t hop_count;
uint8_t seq;
uint32_t last_seen;
};
RouteEntry routingTable[8];
uint8_t routeCount = 0;
// Отправка пакета
void sendPacket(Packet* pkt) {
// Вычисляем контрольную сумму
uint8_t checksum = 0;
uint8_t* data = (uint8_t*)pkt;
for (uint8_t i = 0; i < sizeof(Packet) - 1; i++) {
checksum ^= data[i];
}
pkt->checksum = checksum;
// Отправляем через LoRa
LoRa.beginPacket();
LoRa.write((uint8_t*)pkt, sizeof(Packet));
LoRa.endPacket();
}
// Обработка полученного пакета
void handlePacket(Packet* pkt) {
// Проверяем контрольную сумму
uint8_t checksum = 0;
uint8_t* data = (uint8_t*)pkt;
for (uint8_t i = 0; i < sizeof(Packet) - 1; i++) {
checksum ^= data[i];
}
if (checksum != pkt->checksum) {
return; // Пакет поврежден
}
// Если пакет для нас
if (pkt->dst == NODE_ID || pkt->dst == BROADCAST_ADDR) {
// Обрабатываем
if (pkt->type == 0) { // Данные
processPayload(pkt->payload);
} else if (pkt->type == 1) { // Маршрутизация
updateRoutingTable(pkt);
} else if (pkt->type == 2) { // Приветствие
sendHelloResponse();
}
}
// Пересылаем, если TTL > 0
if (pkt->ttl > 0 && pkt->dst != NODE_ID) {
pkt->ttl--;
sendPacket(pkt);
}
}
// Обновление таблицы маршрутизации
void updateRoutingTable(Packet* pkt) {
uint8_t src = pkt->src;
uint8_t hop = pkt->payload[0]; // Количество хопов
// Ищем запись в таблице
uint8_t idx = 255;
for (uint8_t i = 0; i < routeCount; i++) {
if (routingTable[i].dest == src) {
idx = i;
break;
}
}
// Добавляем или обновляем
if (idx == 255) {
if (routeCount < 8) {
idx = routeCount++;
} else {
// Заменяем самую старую запись
uint32_t oldest = routingTable[0].last_seen;
idx = 0;
for (uint8_t i = 1; i < 8; i++) {
if (routingTable[i].last_seen < oldest) {
oldest = routingTable[i].last_seen;
idx = i;
}
}
}
}
routingTable[idx].dest = src;
routingTable[idx].hop_count = hop + 1;
routingTable[idx].last_seen = millis();
routingTable[idx].seq = pkt->payload[1];
}
// Поиск маршрута к узлу
uint8_t findRoute(uint8_t dest) {
for (uint8_t i = 0; i < routeCount; i++) {
if (routingTable[i].dest == dest) {
return routingTable[i].next_hop;
}
}
return 255; // Маршрут не найден
}
// Отправка сообщения с маршрутизацией
void sendMessage(uint8_t dest, uint8_t* data, uint8_t len) {
Packet pkt;
pkt.src = NODE_ID;
pkt.dst = dest;
pkt.ttl = 10;
pkt.type = 0;
memcpy(pkt.payload, data, len);
// Находим маршрут
uint8_t next_hop = findRoute(dest);
if (next_hop != 255) {
// Отправляем напрямую следующему узлу
sendPacket(&pkt);
} else {
// Широковещательный поиск маршрута
pkt.dst = BROADCAST_ADDR;
pkt.type = 1; // Запрос маршрута
pkt.payload[0] = dest; // Ищем маршрут к этому узлу
sendPacket(&pkt);
}
}
void setup() {
Serial.begin(115200);
// Инициализация LoRa
if (!LoRa.begin(868E6)) { // 868 МГц (Европа)
Serial.println("Ошибка LoRa!");
while (1);
}
LoRa.setSpreadingFactor(12);
LoRa.setSignalBandwidth(125E3);
LoRa.setCodingRate4(5);
LoRa.setTxPower(20);
// Отправляем приветствие в сеть
sendHello();
}
void loop() {
// Проверяем входящие пакеты
if (LoRa.parsePacket()) {
Packet pkt;
LoRa.readBytes((uint8_t*)&pkt, sizeof(Packet));
handlePacket(&pkt);
}
// Отправляем heartbeat каждые 10 секунд
static uint32_t lastHeartbeat = 0;
if (millis() - lastHeartbeat > 10000) {
Packet pkt;
pkt.src = NODE_ID;
pkt.dst = BROADCAST_ADDR;
pkt.ttl = 1;
pkt.type = 2; // Heartbeat
pkt.payload[0] = NODE_ID;
sendPacket(&pkt);
lastHeartbeat = millis();
}
}