mirror of https://github.com/meshcore-dev/MeshCore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
161 lines
6.1 KiB
161 lines
6.1 KiB
// scenario_longchain.cpp
|
|
// Tests multi-hop relay performance at chain lengths targeting multi-state range.
|
|
//
|
|
// Real-world target: reliable delivery across hundreds of miles via relay chains.
|
|
// Each node in a chain extends range by ~2-5 miles (SF12, open terrain).
|
|
// A 200-node chain = ~400-1000 miles theoretical range.
|
|
//
|
|
// This scenario finds where delivery rate collapses, what the per-hop loss
|
|
// accumulation looks like, and how ADAPTIVE vs DEFAULT compares at depth.
|
|
//
|
|
// Chain lengths: 10, 20, 30, 50, 75, 100, 150, 200 nodes
|
|
// SNR conditions: 8 dB (good), 5 dB (marginal), 3 dB (poor)
|
|
// Strategies: DEFAULT, ADAPTIVE
|
|
// Floods: 20 per config (enough to average out random backoff variance)
|
|
|
|
#include "SimBus.h"
|
|
#include "SimMetrics.h"
|
|
#include "RoutingStrategies.h"
|
|
#include <cstdio>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <cstring>
|
|
|
|
using namespace sim;
|
|
|
|
struct ChainResult {
|
|
int num_nodes;
|
|
float snr;
|
|
RoutingStrategy strategy;
|
|
float avg_delivery_rate;
|
|
float avg_latency_ms;
|
|
float avg_hops;
|
|
uint32_t total_collisions;
|
|
uint64_t total_airtime_ms;
|
|
};
|
|
|
|
static ChainResult runChain(int num_nodes, float snr, RoutingStrategy strat, int num_floods) {
|
|
SimBus bus;
|
|
bus.tick_ms = 5;
|
|
|
|
auto* model = new ChainModel(snr);
|
|
bus.channel_model = model;
|
|
|
|
for (int i = 0; i < num_nodes; i++) {
|
|
char name[32];
|
|
snprintf(name, sizeof(name), "node%d", i);
|
|
bus.addNode(name, (uint32_t)(i + 1) * 0xcafebabe);
|
|
}
|
|
|
|
for (auto& b : bus.nodes) b.node->routing_strategy = strat;
|
|
|
|
// Warmup — chain needs more time to propagate
|
|
uint64_t warmup_prop = (uint64_t)num_nodes * 400 + 2000;
|
|
bus.sendFloodText(0, "warmup"); bus.run(warmup_prop);
|
|
bus.sendFloodText(0, "warmup"); bus.run(warmup_prop);
|
|
bus.resetStats();
|
|
|
|
// Propagation budget scales with chain length
|
|
// Each hop takes ~400-800ms under DEFAULT; give generous margin
|
|
uint64_t prop_ms = (uint64_t)num_nodes * 600 + 3000;
|
|
|
|
for (int i = 0; i < num_floods; i++) {
|
|
bus.sendFloodText(0, "bench");
|
|
bus.run(prop_ms);
|
|
}
|
|
|
|
auto stats = bus.metrics.aggregate(num_floods);
|
|
uint64_t total_air = 0;
|
|
for (auto& b : bus.nodes) total_air += b.node->total_airtime_ms;
|
|
|
|
delete model;
|
|
return {
|
|
num_nodes, snr, strat,
|
|
stats.avg_delivery_rate, stats.avg_latency_ms, stats.avg_hops,
|
|
bus.totalCollisions(), total_air
|
|
};
|
|
}
|
|
|
|
static const char* stratName(RoutingStrategy s) {
|
|
return s == RoutingStrategy::DEFAULT ? "DEFAULT " : "ADAPTIVE";
|
|
}
|
|
|
|
int main() {
|
|
printf("MeshCore Long Relay Chain Scenario\n");
|
|
printf("=====================================\n");
|
|
printf("Target: multi-state range via relay chains\n");
|
|
printf("Each node ≈ 2-5 miles range (SF12 LoRa, open terrain)\n\n");
|
|
|
|
FILE* csv = fopen("longchain_results.csv", "w");
|
|
if (csv)
|
|
fprintf(csv, "num_nodes,snr,strategy,avg_delivery_rate,avg_latency_ms,"
|
|
"avg_hops,total_collisions,total_airtime_ms\n");
|
|
|
|
int chain_lengths[] = { 10, 20, 30, 50, 75, 100, 150, 200 };
|
|
float snr_vals[] = { 8.0f, 5.0f, 3.0f };
|
|
RoutingStrategy strats[] = { RoutingStrategy::DEFAULT, RoutingStrategy::ADAPTIVE };
|
|
|
|
for (float snr : snr_vals) {
|
|
printf("\n=== SNR = %.0f dB ===\n", snr);
|
|
printf("%-6s %-8s | dr%% lat(ms) hops coll air(ms) | Δdr Δlat Δair\n",
|
|
"nodes", "strat");
|
|
printf("%s\n", std::string(95, '-').c_str());
|
|
|
|
for (int n : chain_lengths) {
|
|
ChainResult def_r{};
|
|
bool have_def = false;
|
|
|
|
for (auto& strat : strats) {
|
|
int floods = 20;
|
|
// Longer chains need fewer floods to keep runtime reasonable
|
|
if (n >= 100) floods = 10;
|
|
if (n >= 150) floods = 5;
|
|
|
|
auto r = runChain(n, snr, strat, floods);
|
|
|
|
if (!have_def) {
|
|
def_r = r;
|
|
have_def = true;
|
|
printf("%-6d %-8s | %5.1f%% %7.0f %4.1f %-5u %12lld\n",
|
|
r.num_nodes, stratName(r.strategy),
|
|
r.avg_delivery_rate * 100.f, r.avg_latency_ms,
|
|
r.avg_hops, r.total_collisions,
|
|
(long long)r.total_airtime_ms);
|
|
} else {
|
|
float dr_d = (r.avg_delivery_rate - def_r.avg_delivery_rate) * 100.f;
|
|
float lat_p = def_r.avg_latency_ms > 0
|
|
? (r.avg_latency_ms - def_r.avg_latency_ms) / def_r.avg_latency_ms * 100.f : 0.f;
|
|
float air_p = def_r.total_airtime_ms > 0
|
|
? (float)((long long)r.total_airtime_ms - (long long)def_r.total_airtime_ms)
|
|
/ (float)def_r.total_airtime_ms * 100.f : 0.f;
|
|
printf("%-6d %-8s | %5.1f%% %7.0f %4.1f %-5u %12lld | %+5.1f%% %+6.0f%% %+6.0f%%\n",
|
|
r.num_nodes, stratName(r.strategy),
|
|
r.avg_delivery_rate * 100.f, r.avg_latency_ms,
|
|
r.avg_hops, r.total_collisions,
|
|
(long long)r.total_airtime_ms,
|
|
dr_d, lat_p, air_p);
|
|
}
|
|
|
|
if (csv)
|
|
fprintf(csv, "%d,%.1f,%s,%.4f,%.1f,%.2f,%u,%lld\n",
|
|
r.num_nodes, snr, stratName(r.strategy),
|
|
r.avg_delivery_rate, r.avg_latency_ms, r.avg_hops,
|
|
r.total_collisions, (long long)r.total_airtime_ms);
|
|
}
|
|
}
|
|
}
|
|
|
|
printf("\n=== RANGE ESTIMATE (@ 3 miles/hop, SF12) ===\n");
|
|
printf("%-10s %-12s %s\n", "Nodes", "Est. Range", "Notes");
|
|
int sizes[] = {10,20,30,50,75,100,150,200};
|
|
for (int n : sizes)
|
|
printf(" %-8d ~%-10s %s\n", n,
|
|
n<=10?"30 miles":n<=20?"60 miles":n<=30?"90 miles":
|
|
n<=50?"150 miles":n<=75?"225 miles":n<=100?"300 miles":
|
|
n<=150?"450 miles":"600 miles",
|
|
n<=20?"city-scale":n<=50?"regional":n<=100?"state-scale":"multi-state");
|
|
|
|
if (csv) fclose(csv);
|
|
printf("\nCSV written to longchain_results.csv\n");
|
|
return 0;
|
|
}
|
|
|