mirror of
https://github.com/eddieoz/LoRa-Mesh-Analyzer.git
synced 2026-03-28 17:42:59 +01:00
feat: Add configurable router density radius and active threshold, and improve report recommendations and issue presentation.
This commit is contained in:
@@ -125,9 +125,10 @@ class NetworkHealthAnalyzer:
|
||||
|
||||
# 2. Analyze Each Router
|
||||
for r in routers:
|
||||
# A. Neighbors (2km)
|
||||
# A. Neighbors (within configured radius)
|
||||
nearby_routers = 0
|
||||
total_neighbors = 0
|
||||
radius = self.router_density_threshold
|
||||
|
||||
for node_id, node in nodes.items():
|
||||
if node_id == r['id']: continue
|
||||
@@ -137,7 +138,7 @@ class NetworkHealthAnalyzer:
|
||||
|
||||
if lat and lon:
|
||||
dist = haversine(r['lat'], r['lon'], lat, lon)
|
||||
if dist < 2000:
|
||||
if dist < radius:
|
||||
total_neighbors += 1
|
||||
# Check if it's also a router
|
||||
# (Simplified check, ideally we'd check against the routers list but this is O(N))
|
||||
@@ -181,8 +182,9 @@ class NetworkHealthAnalyzer:
|
||||
'lat': r['lat'],
|
||||
'lon': r['lon'],
|
||||
'role': r['role'],
|
||||
'neighbors_2km': total_neighbors,
|
||||
'routers_2km': nearby_routers,
|
||||
'neighbors': total_neighbors,
|
||||
'routers_nearby': nearby_routers,
|
||||
'radius': radius,
|
||||
'ch_util': ch_util,
|
||||
'relay_count': relay_count,
|
||||
'status': ", ".join(status_issues) if status_issues else "OK"
|
||||
|
||||
@@ -164,6 +164,24 @@ class NetworkReporter:
|
||||
f.write("No significant network issues detected.\n\n")
|
||||
return
|
||||
|
||||
# Helper to clean issue strings (remove recommendations)
|
||||
def clean_issue(issue):
|
||||
# Topology: High Router Density
|
||||
if "Best positioned seems to be" in issue:
|
||||
return issue.split("Best positioned seems to be")[0].strip()
|
||||
if "Consider changing" in issue:
|
||||
return issue.split("Consider changing")[0].strip()
|
||||
|
||||
# Network Size
|
||||
if "If using" in issue:
|
||||
return issue.split("If using")[0].strip()
|
||||
|
||||
# Efficiency
|
||||
if "Consolidate?" in issue:
|
||||
return issue.split("Consolidate?")[0].strip()
|
||||
|
||||
return issue
|
||||
|
||||
# Group issues by type
|
||||
congestion = []
|
||||
config = []
|
||||
@@ -171,16 +189,19 @@ class NetworkReporter:
|
||||
other = []
|
||||
|
||||
for issue in analysis_issues:
|
||||
# Clean the issue string first
|
||||
cleaned_issue = clean_issue(issue)
|
||||
|
||||
if "Congestion" in issue or "Spam" in issue:
|
||||
congestion.append(issue)
|
||||
congestion.append(cleaned_issue)
|
||||
elif "Config" in issue or "Role" in issue:
|
||||
config.append(issue)
|
||||
config.append(cleaned_issue)
|
||||
elif "Topology" in issue or "Density" in issue or "hops away" in issue:
|
||||
topology.append(issue)
|
||||
topology.append(cleaned_issue)
|
||||
elif "Efficiency" in issue or "Route Quality" in issue:
|
||||
pass # Handled in separate sections
|
||||
else:
|
||||
other.append(issue)
|
||||
other.append(cleaned_issue)
|
||||
|
||||
if congestion:
|
||||
f.write("### Congestion & Airtime\n")
|
||||
@@ -203,7 +224,7 @@ class NetworkReporter:
|
||||
f.write("\n")
|
||||
|
||||
# Separate section for Efficiency
|
||||
efficiency = [i for i in analysis_issues if "Efficiency" in i]
|
||||
efficiency = [clean_issue(i) for i in analysis_issues if "Efficiency" in i]
|
||||
if efficiency:
|
||||
f.write("### Router Efficiency Analysis\n")
|
||||
f.write("Analysis of router placement, congestion, and relay performance.\n\n")
|
||||
@@ -213,7 +234,7 @@ class NetworkReporter:
|
||||
f.write("\n")
|
||||
|
||||
# Separate section for Route Quality
|
||||
quality = [i for i in analysis_issues if "Route Quality" in i]
|
||||
quality = [clean_issue(i) for i in analysis_issues if "Route Quality" in i]
|
||||
if quality:
|
||||
f.write("### Route Quality Analysis\n")
|
||||
f.write("Analysis of path efficiency and stability.\n\n")
|
||||
@@ -228,11 +249,19 @@ class NetworkReporter:
|
||||
f.write("No routers found.\n\n")
|
||||
return
|
||||
|
||||
f.write("| Name | Role | Neighbors (2km) | Routers (2km) | ChUtil | Relayed | Status |\n")
|
||||
# Get radius from first stat entry (default to 2000m if missing)
|
||||
radius_m = router_stats[0].get('radius', 2000)
|
||||
radius_km = radius_m / 1000.0
|
||||
|
||||
f.write(f"| Name | Role | Neighbors ({radius_km:.1f}km) | Routers ({radius_km:.1f}km) | ChUtil | Relayed | Status |\n")
|
||||
f.write("|---|---|---|---|---|---|---|\n")
|
||||
|
||||
for s in router_stats:
|
||||
f.write(f"| {s['name']} | {s['role']} | {s['neighbors_2km']} | {s['routers_2km']} | {s['ch_util']:.1f}% | {s['relay_count']} | {s['status']} |\n")
|
||||
# Handle backward compatibility if keys are missing
|
||||
neighbors = s.get('neighbors', s.get('neighbors_2km', 0))
|
||||
routers_nearby = s.get('routers_nearby', s.get('routers_2km', 0))
|
||||
|
||||
f.write(f"| {s['name']} | {s['role']} | {neighbors} | {routers_nearby} | {s['ch_util']:.1f}% | {s['relay_count']} | {s['status']} |\n")
|
||||
f.write("\n")
|
||||
|
||||
|
||||
@@ -347,22 +376,36 @@ class NetworkReporter:
|
||||
# Fallback for generic density issue if not caught above
|
||||
recs.append("- **Optimize Placement:** Routers are too close together. Convert redundant routers to clients.")
|
||||
|
||||
# 2. Congestion
|
||||
if any("Congestion" in i for i in analysis_issues):
|
||||
recs.append("- **Reduce Traffic:** High channel utilization detected. Identify spamming nodes or reduce broadcast frequency.")
|
||||
# 2. Efficiency (Router Performance)
|
||||
if any("Ineffective" in i for i in analysis_issues):
|
||||
recs.append("- **Review Ineffective Routers:** Some routers have neighbors but aren't relaying packets. Consider repositioning them or checking their antenna/LOS.")
|
||||
|
||||
if any("Redundant" in i for i in analysis_issues):
|
||||
# This might overlap with Topology, but good to have specific advice
|
||||
recs.append("- **Reduce Redundancy:** Routers marked as 'Redundant' have too many other routers nearby. Change their role to CLIENT to save airtime.")
|
||||
|
||||
# 3. Configuration
|
||||
# 3. Congestion
|
||||
if any("Congestion" in i or "Congested" in i for i in analysis_issues):
|
||||
recs.append("- **Reduce Traffic:** High channel utilization detected. Identify spamming nodes, reduce broadcast frequency, or increase channel speed (if possible).")
|
||||
|
||||
# 4. Configuration
|
||||
if any("ROUTER_CLIENT" in i for i in analysis_issues):
|
||||
recs.append("- **Fix Roles:** Deprecated `ROUTER_CLIENT` role detected. Change these nodes to `CLIENT` or `CLIENT_MUTE`.")
|
||||
|
||||
if any("Network Size" in i for i in analysis_issues):
|
||||
recs.append("- **Adjust Presets:** Network size exceeds recommendations for the current estimated preset. Consider switching to a faster preset (e.g. LONG_MODERATE or SHORT_FAST).")
|
||||
|
||||
# 4. Hardware / Signal
|
||||
if any("poor SNR" in i for i in analysis_issues):
|
||||
recs.append("- **Check Hardware:** Nodes with poor SNR at close range may have antenna issues or bad placement.")
|
||||
# 5. Route Quality / Signal
|
||||
if any("poor SNR" in i or "Weak signal" in i for i in analysis_issues):
|
||||
recs.append("- **Check Hardware/LOS:** Nodes with poor SNR or weak signals may have antenna issues, bad placement, or obstructions.")
|
||||
|
||||
if any("Long path" in i for i in analysis_issues):
|
||||
recs.append("- **Optimize Paths:** Long paths (>3 hops) detected. Consider adding a strategically placed relay to shorten the path.")
|
||||
|
||||
if any("Favorite Router" in i for i in analysis_issues):
|
||||
recs.append("- **Check Favorites:** Routes are using 'Favorite Router' nodes. Ensure this is intentional, as it forces specific paths.")
|
||||
|
||||
# 5. Connectivity (Traceroute Failures)
|
||||
# 6. Connectivity (Traceroute Failures)
|
||||
failures = [r for r in test_results if r.get('status') != 'success']
|
||||
if failures:
|
||||
recs.append(f"- **Investigate Connectivity:** {len(failures)} nodes failed traceroute tests. Check if they are online or if the path is broken.")
|
||||
@@ -370,6 +413,8 @@ class NetworkReporter:
|
||||
if not recs:
|
||||
f.write("Network looks healthy! Keep up the good work.\n")
|
||||
else:
|
||||
for r in recs:
|
||||
# Deduplicate recommendations
|
||||
unique_recs = sorted(list(set(recs)))
|
||||
for r in unique_recs:
|
||||
f.write(f"{r}\n")
|
||||
f.write("\n")
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# Configuration for Meshtastic Network Monitor
|
||||
|
||||
# List of Node IDs to prioritize for active testing (Traceroute, etc.)
|
||||
# Format: "!<NodeID>"
|
||||
priority_nodes:
|
||||
# # List of Node IDs to prioritize for active testing (Traceroute, etc.)
|
||||
# # If priority nodes is enabled, then only those nodes will be tested.
|
||||
# # If this session is disabled, then auto discovery will run automatically
|
||||
# # Format: "!<NodeID>"
|
||||
# priority_nodes:
|
||||
# - "!12345678"
|
||||
|
||||
# Logging Level [warn|info|debug]
|
||||
@@ -12,7 +14,8 @@ log_level: info
|
||||
# Analysis Mode: 'distance' (default) or 'router_clusters'
|
||||
analysis_mode: distance
|
||||
|
||||
# Radius for router cluster analysis (in meters)
|
||||
# Radius for router cluster analysis (nodes close to routers in meters).
|
||||
# Only used if analysis_mode is 'router_clusters'
|
||||
cluster_radius: 2000
|
||||
|
||||
# Roles to prioritize for auto-discovery
|
||||
@@ -50,6 +53,7 @@ thresholds:
|
||||
channel_utilization: 25.0 # Percent
|
||||
air_util_tx: 7.0 # Percent
|
||||
router_density_threshold: 2000 # Meters (Minimum distance between routers)
|
||||
active_threshold_seconds: 7200 # 2 Hours (Nodes seen within this time are considered active)
|
||||
|
||||
# Network Size Settings
|
||||
max_nodes_for_long_fast: 60
|
||||
|
||||
Reference in New Issue
Block a user