refactor: Rename core package to mesh_analyzer, formalize project setup, and introduce report regeneration with new configuration options.

This commit is contained in:
eddieoz
2025-11-28 19:04:46 +02:00
parent 2e1f06e6ff
commit ca612603cf
24 changed files with 83 additions and 38 deletions

View File

@@ -41,7 +41,11 @@ If `priority_nodes` is empty in `config.yaml`, the monitor will automatically se
* **Role Check**: Warns if the monitoring node itself is set to `ROUTER` or `ROUTER_CLIENT` (Monitoring is best done as `CLIENT`).
* **Hop Limit**: Warns if the default hop limit is > 3, which can cause network congestion.
### 5. Comprehensive Reporting
### 5. Data Persistence & Regeneration
* **JSON Data**: Saves all raw data (nodes, test results, analysis) to a JSON file alongside the Markdown report.
* **Regeneration Tool**: Includes `report_generate.py` to regenerate reports from JSON files, allowing for format updates or re-analysis without re-running tests.
### 6. Comprehensive Reporting
* Generates a detailed **Markdown Report** (`report-YYYYMMDD-HHMMSS.md`) after each test cycle.
* Includes:
* Executive Summary
@@ -82,6 +86,16 @@ python3 main.py --tcp 192.168.1.10
python3 main.py --ignore-no-position
```
### Regenerating Reports
To regenerate a report from a saved JSON file (e.g., to apply new analysis logic):
```bash
python3 report_generate.py reports/report-YYYYMMDD-HHMMSS.json
```
You can also specify a custom output filename:
```bash
python3 report_generate.py reports/report-YYYYMMDD-HHMMSS.json --output my_custom_report.md
```
## Configuration (Priority Testing)
To prioritize testing specific nodes (e.g., to check if a router is reachable), add their IDs to `config.yaml`:
@@ -91,15 +105,23 @@ priority_nodes:
- "!12345678"
- "!87654321"
# Auto-Discovery Settings
analysis_mode: router_clusters # 'distance' or 'router_clusters'
cluster_radius: 3000 # Meters
# Generate report after N full testing cycles
report_cycles: 1
# Timeout for traceroute response (in seconds)
# Active Testing Settings
traceroute_timeout: 90
# Minimum interval between tests (in seconds)
active_test_interval: 30
# Manual Geolocation Overrides
manual_positions:
"!12345678":
lat: 59.12345
lon: 24.12345
# Thresholds for Analysis
thresholds:
channel_utilization: 25.0 # Percent
@@ -140,7 +162,7 @@ INFO - Received Traceroute Packet: {...}
* **Action**: Check the hop count in the response (if visible/parsed) to verify the path.
## Project Structure
* `mesh_monitor/`: Source code.
* `mesh_analyzer/`: Source code.
* `monitor.py`: Main application loop.
* `analyzer.py`: Health check logic.
* `active_tests.py`: Traceroute logic.

View File

@@ -2,7 +2,7 @@
"""
Entry point for the Meshtastic Network Monitor.
"""
from mesh_monitor.monitor import main
from mesh_analyzer.monitor import main
if __name__ == "__main__":
main()

View File

@@ -276,7 +276,7 @@ class NetworkHealthAnalyzer:
if not test_results:
return issues
from mesh_monitor.route_analyzer import RouteAnalyzer
from mesh_analyzer.route_analyzer import RouteAnalyzer
route_analyzer = RouteAnalyzer(nodes)
relay_usage = route_analyzer._analyze_relay_usage(
[r for r in test_results if r.get('status') == 'success']

View File

@@ -5,7 +5,7 @@ import json
from datetime import datetime
from .utils import get_val, haversine, get_node_name
from mesh_monitor.route_analyzer import RouteAnalyzer
from mesh_analyzer.route_analyzer import RouteAnalyzer
logger = logging.getLogger(__name__)

3
pyproject.toml Normal file
View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"

View File

@@ -1,4 +1,4 @@
meshtastic
meshtastic
pypubsub
PyYAML

View File

@@ -18,11 +18,11 @@ import os
import argparse
from datetime import datetime
# Add mesh_monitor to path
sys.path.insert(0, os.path.dirname(__file__))
# Add mesh_analyzer to path (parent directory)
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from mesh_monitor.reporter import NetworkReporter
from mesh_monitor.route_analyzer import RouteAnalyzer
from mesh_analyzer.reporter import NetworkReporter
from mesh_analyzer.route_analyzer import RouteAnalyzer
def load_json_data(json_filepath):
@@ -100,7 +100,7 @@ def generate_report_from_json(json_filepath, output_path=None):
node['position']['longitude'] = pos['lon']
# Recreate analyzer and re-run analysis to populate cluster_data and ch_util_data
from mesh_monitor.analyzer import NetworkHealthAnalyzer
from mesh_analyzer.analyzer import NetworkHealthAnalyzer
analyzer = NetworkHealthAnalyzer(config=config)
# Re-run analysis to populate analyzer data structures AND get new issues
@@ -119,13 +119,13 @@ def generate_report_from_json(json_filepath, output_path=None):
# Monkey-patch the generate_report to use custom filename
original_generate = reporter.generate_report
def custom_generate(nodes, test_results, analysis_issues, local_node=None, router_stats=None, analyzer=None):
def custom_generate(nodes, test_results, analysis_issues, local_node=None, router_stats=None, analyzer=None, override_timestamp=None, override_location=None, save_json=True):
# Temporarily change the method to use custom filename
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
custom_filename = os.path.basename(output_path)
filepath = os.path.join(report_dir, custom_filename)
from mesh_monitor.route_analyzer import RouteAnalyzer
from mesh_analyzer.route_analyzer import RouteAnalyzer
route_analyzer = RouteAnalyzer(nodes)
route_analysis_local = route_analyzer.analyze_routes(test_results)

View File

@@ -9,10 +9,11 @@ import os
import json
from datetime import datetime
# Add mesh_monitor to path
# Add mesh_analyzer to path
sys.path.insert(0, os.path.dirname(__file__))
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from mesh_monitor.reporter import NetworkReporter
from mesh_analyzer.reporter import NetworkReporter
def create_mock_data():

18
setup.py Normal file
View File

@@ -0,0 +1,18 @@
from setuptools import setup, find_packages
setup(
name="mesh_analyzer",
version="0.1.0",
description="LoRa Mesh Analyzer for Meshtastic networks",
packages=find_packages(),
install_requires=[
"meshtastic",
"pypubsub",
"PyYAML",
],
entry_points={
"console_scripts": [
"mesh-analyzer=mesh_analyzer.monitor:main",
],
},
)

View File

@@ -6,9 +6,9 @@ from unittest.mock import MagicMock, patch, mock_open
# Add project root to path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from mesh_monitor.analyzer import NetworkHealthAnalyzer
from mesh_monitor.monitor import MeshMonitor
from mesh_monitor.active_tests import ActiveTester
from mesh_analyzer.analyzer import NetworkHealthAnalyzer
from mesh_analyzer.monitor import MeshMonitor
from mesh_analyzer.active_tests import ActiveTester
class TestNetworkMonitor(unittest.TestCase):
def setUp(self):
@@ -270,7 +270,7 @@ class TestNetworkMonitor(unittest.TestCase):
def test_local_config_check(self):
print("\nRunning Local Config Check Test...")
from mesh_monitor.monitor import MeshMonitor
from mesh_analyzer.monitor import MeshMonitor
from unittest.mock import MagicMock
# Mock the interface and node
@@ -301,7 +301,7 @@ class TestNetworkMonitor(unittest.TestCase):
def test_auto_discovery(self):
print("\nRunning Auto-Discovery Test...")
from mesh_monitor.active_tests import ActiveTester
from mesh_analyzer.active_tests import ActiveTester
# Mock Interface
mock_interface = MagicMock()
@@ -370,7 +370,7 @@ class TestNetworkMonitor(unittest.TestCase):
}
issues = self.analyzer.analyze(self.mock_nodes)
density_warnings = [i for i in issues if "High Density" in i]
density_warnings = [i for i in issues if "High Router Density" in i]
self.assertTrue(len(density_warnings) > 0, "Should detect high router density")
print(" [Pass] Router Density Check")
@@ -398,7 +398,7 @@ class TestNetworkMonitor(unittest.TestCase):
def test_reporting(self):
print("\nRunning Reporting Test...")
from mesh_monitor.reporter import NetworkReporter
from mesh_analyzer.reporter import NetworkReporter
# Initialize self.monitor mock since setUp doesn't do it
self.monitor = MagicMock()
@@ -463,8 +463,8 @@ class TestNetworkMonitor(unittest.TestCase):
def test_route_analysis(self):
"""Test the RouteAnalyzer and Reporter integration."""
print("\nRunning Route Analysis Test...")
from mesh_monitor.route_analyzer import RouteAnalyzer
from mesh_monitor.reporter import NetworkReporter
from mesh_analyzer.route_analyzer import RouteAnalyzer
from mesh_analyzer.reporter import NetworkReporter
# Mock Test Results with Routes
test_results = [

View File

@@ -1,5 +1,6 @@
import unittest
from mesh_monitor.analyzer import NetworkHealthAnalyzer
from mesh_analyzer.analyzer import NetworkHealthAnalyzer
import time
class TestAnalyzerEnhancements(unittest.TestCase):
def setUp(self):
@@ -37,7 +38,7 @@ class TestAnalyzerEnhancements(unittest.TestCase):
def test_network_size(self):
nodes = {}
for i in range(61):
nodes[f'!{i}'] = {'user': {'id': f'!{i}'}}
nodes[f'!{i}'] = {'user': {'id': f'!{i}'}, 'lastHeard': time.time()}
issues = self.analyzer.analyze(nodes)
self.assertTrue(any("Network Size" in i for i in issues))

View File

@@ -1,6 +1,6 @@
import unittest
from unittest.mock import Mock
from mesh_monitor.active_tests import ActiveTester
from mesh_analyzer.active_tests import ActiveTester
class TestHopCountCalculation(unittest.TestCase):

View File

@@ -1,6 +1,6 @@
import unittest
import time
from mesh_monitor.analyzer import NetworkHealthAnalyzer
from mesh_analyzer.analyzer import NetworkHealthAnalyzer
class TestNetworkSize(unittest.TestCase):
def setUp(self):

View File

@@ -1,5 +1,5 @@
import unittest
from mesh_monitor.analyzer import NetworkHealthAnalyzer
from mesh_analyzer.analyzer import NetworkHealthAnalyzer
class TestRouteQuality(unittest.TestCase):
def setUp(self):

View File

@@ -1,6 +1,6 @@
import unittest
from unittest.mock import MagicMock
from mesh_monitor.active_tests import ActiveTester
from mesh_analyzer.active_tests import ActiveTester
class TestRouterClusters(unittest.TestCase):
def setUp(self):

View File

@@ -1,5 +1,5 @@
import unittest
from mesh_monitor.analyzer import NetworkHealthAnalyzer
from mesh_analyzer.analyzer import NetworkHealthAnalyzer
class TestRouterEfficiency(unittest.TestCase):
def setUp(self):

View File

@@ -12,7 +12,7 @@ logger = logging.getLogger("Verification")
def verify_utils():
logger.info("Verifying utils.py...")
from mesh_monitor.utils import haversine, get_val, get_node_name
from mesh_analyzer.utils import haversine, get_val, get_node_name
# Test haversine
dist = haversine(0, 0, 1, 1)
@@ -49,21 +49,21 @@ def verify_modules():
interface = MockInterface()
# Test Analyzer
from mesh_monitor.analyzer import NetworkHealthAnalyzer
from mesh_analyzer.analyzer import NetworkHealthAnalyzer
analyzer = NetworkHealthAnalyzer()
issues = analyzer.analyze({})
assert isinstance(issues, list), "Analyzer did not return list"
logger.info("Analyzer instantiated and ran.")
# Test ActiveTester
from mesh_monitor.active_tests import ActiveTester
from mesh_analyzer.active_tests import ActiveTester
tester = ActiveTester(interface)
assert hasattr(tester, 'lock'), "ActiveTester missing lock"
assert isinstance(tester.lock, type(threading.Lock())), "ActiveTester lock is not a Lock"
logger.info("ActiveTester instantiated and has lock.")
# Test Reporter
from mesh_monitor.reporter import NetworkReporter
from mesh_analyzer.reporter import NetworkReporter
reporter = NetworkReporter()
logger.info("Reporter instantiated.")