Skip to content

Latest commit

 

History

History
752 lines (586 loc) · 23 KB

File metadata and controls

752 lines (586 loc) · 23 KB

Zone Mesh - Децентрализирана Mesh Мрежа за ESP32

Описание

Zone Mesh е custom mesh network архитектура оптимизирана за ESP32 устройства. Системата е проектирана за scaling до 400+ devices с минимална консумация на RAM (~1-2KB per zone vs 8KB за painlessMesh DHT).

Основни възможности

1. Zone-based Architecture

Устройствата се групират в zones (например "kitchen", "livingroom", "garden"):

  • Max 30 devices/zone - Баланс между locality и management
  • Automatic coordinator election - Базиран на RAM, uptime, load
  • Local subscription registry - Без нужда от global DHT
  • Zone discovery - Automatic route discovery between zones

2. Coordinator Election

Всяка зона избира coordinator автоматично на база:

Критерий Тегло Описание
Free RAM 40% Повече RAM = по-добър coordinator
Uptime 20% Стабилни устройства (max 30 days)
CPU Load 15% По-малко натоварени devices
Device Count 10% Колко devices вече управлява
External Power 10% AC power vs battery
RSSI Average 5% Signal quality към members

Election Formula:

score = (freeRAM/1KB × 40) +
        (uptime/day × 20) +
        ((100-load) × 0.15) +
        ((30-devCount)/30 × 10) +
        (externalPower ? 1000 : 0) +
        ((100+rssi) × 0.05)

3. Subscription System

Local subscriptions (same zone):

Zone "kitchen"
├── Coordinator manages subscriptions
├── "kitchen.temp.value.real" → ["dashboard", "automation"]
├── "kitchen.light.1.state" → ["dashboard"]
└── Fast delivery (no routing)

Inter-zone subscriptions (different zones):

Zone "kitchen"              Zone "livingroom"
[temp sensor] ──────────> [Coordinator] ─────> [Coordinator] ─────> [dashboard]
                              │                     │
                        Subscription          Route via
                        registered           coordinators

4. Routing Protocol

  • Beacon-based discovery: Coordinators broadcast presence
  • Hop-count routing: Shortest path selection
  • Route timeout: 5 minutes (auto-refresh)
  • TTL protection: Max 10 hops
  • Binary protocol: Efficient packet header

5. Memory Optimization

Per Zone Memory:
├── ZoneManager: ~1.5KB
│   ├── Device list (30): 50 bytes × 30 = 1,500 bytes
│   ├── Subscriptions: 40 bytes × 20 = 800 bytes
│   └── Zone info: ~200 bytes
├── ZoneRouter: ~500 bytes
│   ├── Route table: 50 bytes × 5 = 250 bytes
│   └── Statistics: ~100 bytes
└── TOTAL: ~2-3KB per zone

vs painlessMesh: 8KB DHT table → 73% memory reduction

Архитектура

Network Topology

Zone "kitchen" (30 devices)          Zone "livingroom" (20 devices)
┌─────────────────────────────────┐  ┌──────────────────────────────┐
│                                 │  │                              │
│  [Device A] ──┐                 │  │                [Device X]    │
│  [Device B] ──┼─> [Coordinator] ├──┼─> [Coordinator] ──┬─> [Device Y] │
│  [Device C] ──┘      (elected)  │  │      (elected)      └─> [Device Z] │
│                                 │  │                              │
└─────────────────────────────────┘  └──────────────────────────────┘
         Zone registry:                      Zone registry:
         - kitchen.temp → [dashboard]        - livingroom.light → [app]
         - kitchen.light → [app, mqtt]       - livingroom.temp → [mqtt]

Packet Types

Packet Type Код Описание
COORDINATOR_BEACON 0x01 Coordinator announces presence
DEVICE_BEACON 0x02 Device announces presence
ELECTION_VOTE 0x03 Vote during coordinator election
ELECTION_RESULT 0x04 New coordinator announcement
SUBSCRIBE_REQUEST 0x10 Subscribe to endpoint
SUBSCRIBE_ACK 0x11 Subscription confirmed
UNSUBSCRIBE_REQUEST 0x12 Unsubscribe from endpoint
DATA_PUBLISH 0x20 Publish data to subscribers
DATA_UNICAST 0x21 Unicast data to device
ZONE_ROUTE 0x30 Route to another zone
ZONE_QUERY 0x31 Query another zone coordinator
ZONE_RESPONSE 0x32 Response from coordinator

Binary Protocol Header

struct ZoneMeshHeader {
    uint8_t version;                // Protocol version (1)
    ZoneMeshPacketType type;        // Packet type (see above)
    uint8_t ttl;                    // Time to live (hops, max 10)
    uint8_t flags;                  // ACK_REQUIRED, CRITICAL, etc.
    uint8_t sourceMac[6];           // Source device MAC
    uint8_t destMac[6];             // Dest MAC (FF:FF:FF:FF:FF:FF = broadcast)
    char sourceZone[32];            // Source zone name
    char destZone[32];              // Destination zone name
    uint16_t payloadLength;         // Payload size
    uint16_t checksum;              // Header + payload checksum
}; // Total: 86 bytes

API Reference

EspHub Integration

Zone mesh се инициализира автоматично в EspHub::begin():

EspHub hub;

void setup() {
    hub.begin(); // Auto-initializes zone mesh with MAC-based name
    // Device name: "esphub_AABBCC" (from MAC)
    // Default zone: "main"
}

void loop() {
    hub.loop(); // Calls meshDeviceManager.loop() internally
}

MeshDeviceManager API

Инициализация

// Manual initialization (if not using EspHub)
#include "Protocols/Mesh/MeshDeviceManager.h"

MeshDeviceManager meshMgr;

void setup() {
    meshMgr.begin("kitchen.hub", "kitchen");
    //             └─ device name  └─ zone name

    // Set capabilities for coordinator election
    CoordinatorCapabilities caps;
    caps.freeRam = ESP.getFreeHeap();
    caps.uptime = millis() / 1000;
    caps.hasExternalPower = true; // AC powered
    meshMgr.setCapabilities(caps);
}

Device Management

// Get all devices in current zone
std::vector<MeshDevice> devices = meshMgr.getZoneDevices();
for (const auto& dev : devices) {
    Serial.printf("Device: %s, Online: %s, Zone: %s\n",
                  dev.name.c_str(),
                  dev.isOnline ? "Yes" : "No",
                  dev.zoneName.c_str());
}

// Check if device is online
if (meshMgr.isDeviceOnline("kitchen.temp.sensor")) {
    Serial.println("Temperature sensor is online");
}

// Get zone info
String myZone = meshMgr.getMyZoneName();
bool isCoord = meshMgr.isCoordinator();
Serial.printf("My zone: %s, Coordinator: %s\n",
              myZone.c_str(), isCoord ? "Yes" : "No");

Subscription Management

// Subscribe to endpoint (local or remote)
bool success = meshMgr.subscribeToEndpoint(
    "kitchen.temp.value.real",  // Full endpoint path
    "livingroom.dashboard"      // Subscriber device name
);

if (success) {
    Serial.println("Subscription successful");
} else {
    Serial.println("Subscription failed - check routing");
}

// Unsubscribe
meshMgr.unsubscribeFromEndpoint(
    "kitchen.temp.value.real",
    "livingroom.dashboard"
);

Publishing Data

// Publish to subscribers (coordinator only)
if (meshMgr.isCoordinator()) {
    PlcValue temperature;
    temperature.type = PlcValueType::REAL;
    temperature.value.fVal = 25.5;

    bool published = meshMgr.publishToSubscribers(
        "kitchen.temp.value.real",
        temperature
    );

    if (published) {
        Serial.println("Data published to all subscribers");
    }
}

Statistics & Monitoring

// Zone statistics
const ZoneStatistics& stats = meshMgr.getZoneStats();
Serial.printf("Packets received: %u\n", stats.packetsReceived);
Serial.printf("Packets sent: %u\n", stats.packetsSent);
Serial.printf("Packets dropped: %u\n", stats.packetsDropped);
Serial.printf("Coordinator changes: %u\n", stats.coordinatorChanges);

// Router statistics
ZoneRouter::RouterStatistics routerStats = meshMgr.getRouterStats();
Serial.printf("Packets routed: %u\n", routerStats.packetsRouted);
Serial.printf("Routing errors: %u\n", routerStats.routingErrors);
Serial.printf("Known zones: %d\n", routerStats.routeUpdates);

// Memory usage
size_t memUsage = meshMgr.getMemoryUsage();
Serial.printf("Zone mesh memory: %d bytes\n", memUsage);

// Known zones
std::vector<String> zones = meshMgr.getKnownZones();
Serial.printf("Known zones (%d):\n", zones.size());
for (const String& zone : zones) {
    Serial.printf("  - %s\n", zone.c_str());
}

ZoneManager API (Advanced)

// Get ZoneManager instance for advanced usage
ZoneManager* zoneMgr = meshMgr.getZoneManager();

// Get detailed zone information
const ZoneInfo& info = zoneMgr->getZoneInfo();
Serial.printf("Zone: %s\n", info.zoneName.c_str());
Serial.printf("Coordinator: %s\n", info.coordinatorDevice.c_str());
Serial.printf("Devices: %d\n", info.devices.size());
Serial.printf("Subscriptions: %u\n", info.subscriptionCount);

// Trigger manual coordinator election
zoneMgr->triggerElection();

// Send custom beacon
zoneMgr->sendBeacon();

// Get specific device
ZoneDevice* device = zoneMgr->getDevice("kitchen.light.1");
if (device) {
    Serial.printf("Device role: %d, RSSI: %d\n",
                  (int)device->role, device->rssi);
}

ZoneRouter API (Advanced)

// Get ZoneRouter instance
ZoneRouter* router = meshMgr.getZoneRouter();

// Check if route exists to zone
if (router->hasRoute("livingroom")) {
    Serial.println("Route to livingroom exists");

    // Get next hop MAC
    const uint8_t* nextHop = router->getRoute("livingroom");
    Serial.printf("Next hop: %02X:%02X:%02X:%02X:%02X:%02X\n",
                  nextHop[0], nextHop[1], nextHop[2],
                  nextHop[3], nextHop[4], nextHop[5]);
}

// Trigger route discovery
router->discoverRoutes();

// Get route count
size_t routes = router->getRouteCount();
Serial.printf("Active routes: %d\n", routes);

Configuration Examples

Example 1: Kitchen Hub (AC Powered, High RAM)

void setup() {
    Serial.begin(115200);

    MeshDeviceManager mesh;
    mesh.begin("kitchen.hub", "kitchen");

    // Configure as ideal coordinator
    CoordinatorCapabilities caps;
    caps.freeRam = 280000;          // 280KB free
    caps.uptime = 0;                // Just booted
    caps.currentLoad = 10;          // 10% load
    caps.deviceCount = 0;
    caps.hasExternalPower = true;   // AC powered
    caps.rssiAverage = -45;         // Good signal
    mesh.setCapabilities(caps);

    Serial.printf("Coordinator score: %u\n", caps.calculateScore());
    // Expected: High score due to RAM + external power
}

Example 2: Battery Sensor (Low Power)

void setup() {
    Serial.begin(115200);

    MeshDeviceManager mesh;
    mesh.begin("garden.temp.sensor", "garden");

    // Configure as member (not ideal coordinator)
    CoordinatorCapabilities caps;
    caps.freeRam = 150000;          // 150KB free
    caps.uptime = 86400;            // 1 day uptime
    caps.currentLoad = 5;           // 5% load
    caps.deviceCount = 0;
    caps.hasExternalPower = false;  // Battery powered
    caps.rssiAverage = -70;         // Weak signal
    mesh.setCapabilities(caps);

    Serial.printf("Coordinator score: %u\n", caps.calculateScore());
    // Expected: Lower score due to battery power
}

Example 3: Dashboard (Subscriber)

MeshDeviceManager mesh;

void setup() {
    mesh.begin("livingroom.dashboard", "livingroom");

    // Subscribe to sensors from different zones
    mesh.subscribeToEndpoint("kitchen.temp.value.real", "livingroom.dashboard");
    mesh.subscribeToEndpoint("garden.humidity.value.real", "livingroom.dashboard");
    mesh.subscribeToEndpoint("bedroom.light.state.bool", "livingroom.dashboard");
}

void loop() {
    mesh.loop();

    // Data will be received via mesh callbacks
    // Process incoming data here
}

Example 4: Multi-Zone Temperature Monitor

MeshDeviceManager mesh;

void setup() {
    mesh.begin("monitor.hub", "main");

    // Subscribe to all temperature sensors
    mesh.subscribeToEndpoint("kitchen.temp.value.real", "monitor.hub");
    mesh.subscribeToEndpoint("livingroom.temp.value.real", "monitor.hub");
    mesh.subscribeToEndpoint("bedroom.temp.value.real", "monitor.hub");
    mesh.subscribeToEndpoint("bathroom.temp.value.real", "monitor.hub");
}

void loop() {
    mesh.loop();

    // Print zone info every 30 seconds
    static unsigned long lastPrint = 0;
    if (millis() - lastPrint > 30000) {
        printZoneInfo();
        lastPrint = millis();
    }
}

void printZoneInfo() {
    Serial.println("\n=== Zone Mesh Status ===");
    Serial.printf("My Zone: %s\n", mesh.getMyZoneName().c_str());
    Serial.printf("Coordinator: %s\n", mesh.isCoordinator() ? "Yes" : "No");

    // Print known zones
    auto zones = mesh.getKnownZones();
    Serial.printf("Known zones: %d\n", zones.size());
    for (const String& zone : zones) {
        Serial.printf("  - %s\n", zone.c_str());
    }

    // Print devices in current zone
    auto devices = mesh.getZoneDevices();
    Serial.printf("Devices in zone: %d\n", devices.size());
    for (const auto& dev : devices) {
        Serial.printf("  - %s (%s)\n",
                      dev.name.c_str(),
                      dev.isOnline ? "online" : "offline");
    }

    // Print statistics
    const auto& stats = mesh.getZoneStats();
    Serial.printf("Packets RX: %u, TX: %u, Dropped: %u\n",
                  stats.packetsReceived,
                  stats.packetsSent,
                  stats.packetsDropped);
}

Timing & Performance

Beacon Intervals

Device Role Beacon Interval Rationale
Coordinator 30 seconds Fast discovery, stable presence
Member 60 seconds Lower bandwidth, battery friendly
Election 5 seconds Quick convergence

Timeouts

Event Timeout Rationale
Device offline 2 minutes 2 missed beacons (member)
Coordinator offline 2 minutes Trigger re-election
Route expiry 5 minutes Balance freshness vs traffic
Election duration 5 seconds Collect all votes

Performance Metrics

Operation                  Time        CPU Usage
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Coordinator election       <5s         Low
Zone discovery            <1s         Low
Subscription add          <1ms        Minimal
Data publish (local)      <5ms        Low
Data publish (remote)     <50ms       Medium
Route discovery           <2s         Medium
Packet forwarding         <10ms       Low

Troubleshooting

Device не влиза в зона

Симптоми: Device не се вижда в zone devices list

Решение:

  1. Проверете WiFi mode: WiFi.mode(WIFI_STA)
  2. Проверете ESP-NOW init: esp_now_init() == ESP_OK
  3. Проверете zone name: Същото име при всички devices
  4. Проверете beacons: Трябва да се изпращат на 60s interval
// Debug beacons
void debugBeacons() {
    Serial.println("Forcing beacon send...");
    zoneMgr->sendBeacon();
    delay(100);

    // Check if received
    auto devices = zoneMgr->getZoneDevices();
    Serial.printf("Devices seen: %d\n", devices.size());
}

Coordinator не се избира

Симптоми: Election се задейства, но няма coordinator

Решение:

  1. Проверете capabilities: setCapabilities() е извикан
  2. Проверете score calculation: caps.calculateScore()
  3. Проверете election timeout: По подразбиране 5s
  4. Проверете ESP-NOW broadcast: Трябва да работи
// Debug election
void debugElection() {
    CoordinatorCapabilities caps;
    caps.freeRam = ESP.getFreeHeap();
    caps.uptime = millis() / 1000;
    caps.currentLoad = 10;
    caps.hasExternalPower = true;

    Serial.printf("My score: %u\n", caps.calculateScore());
    Serial.printf("Role: %d\n", (int)zoneMgr->getRole());

    if (zoneMgr->getRole() == ZoneRole::UNASSIGNED) {
        Serial.println("Triggering manual election...");
        zoneMgr->triggerElection();
    }
}

Subscription не работи

Симптоми: subscribeToEndpoint() returns false

Решение:

  1. Local subscription (same zone):

    • Проверете дали има coordinator: isCoordinator()
    • Members трябва да изпращат request към coordinator
  2. Remote subscription (different zone):

    • Проверете route: router->hasRoute(targetZone)
    • Trigger discovery: router->discoverRoutes()
    • Проверете coordinators в двете зони
// Debug subscriptions
void debugSubscription() {
    String endpoint = "kitchen.temp.value.real";
    String targetZone = endpoint.substring(0, endpoint.indexOf('.'));
    String myZone = mesh.getMyZoneName();

    Serial.printf("Target zone: %s, My zone: %s\n",
                  targetZone.c_str(), myZone.c_str());

    if (targetZone != myZone) {
        // Remote subscription - check routing
        if (!router->hasRoute(targetZone)) {
            Serial.println("No route! Discovering...");
            router->discoverRoutes();
            delay(3000); // Wait for discovery
        }
    }

    bool success = mesh.subscribeToEndpoint(endpoint, "my.device");
    Serial.printf("Subscription: %s\n", success ? "OK" : "FAILED");
}

Packets се губят

Симптоми: High packetsDropped в statistics

Възможни причини:

  1. Checksum errors - RF interference
  2. Buffer overflow - Too many packets
  3. TTL expired - Too many hops
  4. Route not found - Missing route entry

Решение:

// Monitor packet loss
void monitorPackets() {
    static uint32_t lastRx = 0, lastDropped = 0;

    const auto& stats = mesh.getZoneStats();
    uint32_t newDropped = stats.packetsDropped - lastDropped;
    uint32_t newRx = stats.packetsReceived - lastRx;

    if (newDropped > 0) {
        float lossRate = (float)newDropped / (newRx + newDropped) * 100;
        Serial.printf("WARNING: Packet loss: %.1f%% (%u/%u)\n",
                      lossRate, newDropped, newRx + newDropped);
    }

    lastRx = stats.packetsReceived;
    lastDropped = stats.packetsDropped;
}

Memory leak

Симптоми: ESP.getFreeHeap() намалява с времето

Решение:

  1. Проверете device cleanup: Offline devices се премахват ли?
  2. Проверете subscription cleanup: Subscriptions се изтриват ли?
  3. Проверете route cleanup: Routes expire ли?
// Monitor memory
void monitorMemory() {
    static size_t lastFree = 0;
    size_t free = ESP.getFreeHeap();

    if (lastFree > 0) {
        int32_t delta = (int32_t)free - (int32_t)lastFree;
        Serial.printf("Free RAM: %u bytes (delta: %+d)\n", free, delta);

        if (delta < -1000) {
            Serial.println("WARNING: Memory leak detected!");
            Serial.printf("Zone mesh usage: %u bytes\n",
                          mesh.getMemoryUsage());
        }
    }

    lastFree = free;
}

Migration от painlessMesh

Zone mesh запазва backward compatibility API:

// Old painlessMesh code
meshDeviceManager.addDevice(nodeId, "device_name");
meshDeviceManager.updateDeviceLastSeen(nodeId);
MeshDevice* dev = meshDeviceManager.getDevice(nodeId);

// Still works! But deprecated - use zone mesh API instead

Recommended migration:

// Before (painlessMesh)
mesh.update();
mesh.sendBroadcast(msg);

// After (Zone Mesh)
meshMgr.loop();
meshMgr.publishToSubscribers(endpoint, value);

Best Practices

1. Zone Planning

  • Group by physical location: kitchen, livingroom, bedroom
  • Max 30 devices/zone: Split large rooms if needed
  • Name consistently: Use lowercase, no spaces

2. Coordinator Selection

  • Prefer AC powered devices for coordinators
  • Prefer devices with high RAM (ESP32 with PSRAM)
  • Avoid battery devices as coordinators

3. Subscription Management

  • Subscribe only to needed endpoints: Reduces bandwidth
  • Use local subscriptions when possible: Faster delivery
  • Cleanup subscriptions: Unsubscribe when not needed

4. Performance Optimization

  • Batch publishes: Publish multiple values together
  • Throttle updates: Don't publish faster than 10Hz
  • Use appropriate priorities: CRITICAL only for urgent data

5. Monitoring

  • Log statistics: Track packet loss, routing errors
  • Monitor memory: Check for leaks
  • Alert on coordinator changes: May indicate instability

Приложения

Zone mesh е подходящ за:

  • Smart home (200+ sensors, lights, switches)
  • Industrial automation (PLCs, sensors, actuators)
  • Agriculture (soil sensors, irrigation, climate)
  • Building management (HVAC, lighting, security)
  • Christmas light shows (400+ LED controllers)

Ограничения

  1. Max devices per zone: 30 (configurable via MAX_DEVICES_PER_ZONE)
  2. Max subscriptions per endpoint: 10 (configurable via MAX_SUBSCRIPTIONS_PER_DEVICE)
  3. Max hops: 10 (TTL limit)
  4. Max packet size: ~250 bytes (ESP-NOW limit)
  5. Route precision: ±5 seconds (discovery interval)

Performance Comparison

Metric painlessMesh Zone Mesh Improvement
Max devices ~50 400+ 8x
RAM/device 8KB (DHT) 1-2KB 75% less
Broadcast overhead High Low Unicast only
Route discovery ESP-NOW scan Beacon Faster
Coordinator None Automatic Better
Scalability Limited Excellent Zone-based

Future Enhancements

  • Web UI for zone configuration
  • REST API endpoints for management
  • MQTT bridge for zone events
  • Persistent subscription storage
  • Advanced routing algorithms (shortest path, load balancing)
  • Zone merging/splitting
  • Inter-coordinator direct links
  • Encryption support

Ресурси

  • Source code: lib/Protocols/Mesh/
  • Examples: docs/ZoneMesh_Guide.md (this file)
  • API reference: See "API Reference" section above
  • Architecture discussion: ProtocolChat.md

Support

За въпроси и issues:


Zone Mesh v1.0 - Built for ESP32, optimized for scale 🚀