first version

This commit is contained in:
tcsenpai 2025-01-30 15:41:36 +01:00
commit 6ee97f8760
8 changed files with 367 additions and 0 deletions

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# uv files
pyproject.toml
.python-version
uv.lock
# Virtual environments
.venv
# PyInstaller files
*.spec
pyinstall.sh
# Plots
plots/

47
README.md Normal file
View File

@ -0,0 +1,47 @@
# Sigmon
A tool to monitor WiFi signal strength and metrics continuously in the terminal.
It uses `iwconfig` to get the metrics and `plotext` to plot them.
![sigmon](./sigmon.png)
## Features
- Fast, lightweight and simple
- Continuous monitoring of WiFi signal strength and metrics
- Average signal strength is displayed too
- Plots are saved as PNG files with averages
## Requirements
- `iw`
- Python 3.10+ (may work on older versions, but not tested)
### Install `iw` on Ubuntu (and probably other Debian-based systems)
```bash
sudo apt-get install iw
```
## Usage from binary
Download the binary from the [releases](https://github.com/tcsenpai/sigmon/releases) page.
```bash
./sigmon
```
## Usage from source
### Installation
```bash
pip install -r requirements.txt
```
### Usage
```bash
python src/main.py
```

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
plotext

BIN
sigmon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 KiB

11
src/libs/metrics.py Normal file
View File

@ -0,0 +1,11 @@
class Metrics:
"""
Class to store the metrics of the network adapter
"""
def __init__(self, signal_strength: str, bitrate: str, is_power_save_enabled: bool):
self.signal_strength = signal_strength
self.bitrate = bitrate
self.is_power_save_enabled = is_power_save_enabled
def __str__(self):
return f"Signal strength: {self.signal_strength} dBm, Bitrate: {self.bitrate} Mb/s, Power save enabled: {self.is_power_save_enabled}"

159
src/libs/plotter.py Normal file
View File

@ -0,0 +1,159 @@
"""
This module is used to plot the metrics of the network adapter on the cli
"""
import plotext as plt
import time
from typing import List
from datetime import datetime
from libs.metrics import Metrics
from libs.session import SessionMetrics
import os
class MetricsPlotter:
def __init__(self, max_points: int = 50):
self.signal_strengths: List[float] = []
self.timestamps: List[str] = []
self.max_points = max_points
self.min_seen = 0
self.max_seen = -100
self.session = SessionMetrics()
# Create plots directory if it doesn't exist
os.makedirs("plots", exist_ok=True)
def save_plot(self):
"""Save the current plot with averages"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"plots/sigmon_{timestamp}.png"
# Calculate average
avg_signal = sum(self.signal_strengths) / len(self.signal_strengths)
# Create the final plot
plt.clf()
plt.plotsize(150, 50) # Larger size for better image quality
# Calculate dynamic y-axis limits
y_min = max(-100, self.min_seen - 10)
y_max = min(0, self.max_seen + 10)
plt.ylim(y_min, y_max)
# Set x-axis limits
x_max = len(self.signal_strengths)
plt.xlim(0, x_max)
# Plot signal strength
plt.plot(self.signal_strengths, marker="dot", color="green", label="Signal")
# Plot average line
avg_line = [avg_signal] * len(self.signal_strengths)
plt.plot(avg_line, color="red", label=f"Avg: {avg_signal:.1f} dBm")
plt.theme("matrix")
plt.title("WiFi Signal Strength Over Time")
plt.xlabel("Time (seconds)")
plt.ylabel("Signal Strength (dBm)")
# Build and save the plot
plt.build()
plt.save_fig(filename)
print(f"\nPlot saved as: {filename}")
def update_plot(self, metrics):
# Convert signal strength to float and ensure it's negative
try:
signal = float(metrics.signal_strength)
# Update min/max seen values
self.min_seen = min(self.min_seen, signal)
self.max_seen = max(self.max_seen, signal)
except ValueError:
print(f"Warning: Invalid signal strength value: {metrics.signal_strength}")
signal = 0
current_time = datetime.now().strftime("%H:%M:%S")
# Add new data points
self.signal_strengths.append(signal)
self.timestamps.append(current_time)
# Keep only last max_points
if len(self.signal_strengths) > self.max_points:
self.signal_strengths.pop(0)
self.timestamps.pop(0)
# Clear the terminal
plt.clear_terminal()
# Create the plot
plt.clf()
plt.plotsize(100, 30)
# Calculate dynamic y-axis limits
y_min = max(-100, self.min_seen - 10) # Don't go below -100
y_max = min(0, self.max_seen + 10) # Don't go above 0
plt.ylim(y_min, y_max)
# Set x-axis limits
x_max = len(self.signal_strengths)
plt.xlim(0, x_max)
# Plot with a thicker line
# Color depends on the signal strength:
# - Red if signal strength is less than -60
# - Orange if signal strength is less than -50
# - Green if signal strength is greater than -50
signal_color = "red" if signal < -60 else "orange" if signal < -50 else "green"
plt.plot(self.signal_strengths, color="green")
plt.theme("matrix")
plt.title("WiFi Signal Strength Over Time")
plt.xlabel("Time (seconds)")
plt.ylabel("Signal Strength (dBm)")
# Show the plot
plt.show()
# Print current metrics below the plot
print(
f"Signal: {signal:.1f} dBm | Bitrate: {metrics.bitrate} Mb/s | Power Save: {'On' if metrics.is_power_save_enabled else 'Off'}"
)
status = (
"Strong signal"
if signal > -50
else (
"Good signal"
if signal > -60
else "Weak signal" if signal > -70 else "Very bad signal"
)
)
print(f"Status: {status}")
summary = self.session.get_session_summary()
print(summary)
self.session.add_metrics(metrics)
def plot_metrics_live(
get_metrics_func, adapter_name: str = "wlan0", interval: float = 1.0
):
"""
Continuously plot metrics in real-time
Args:
get_metrics_func: Function to get metrics
adapter_name: Name of the network adapter
interval: Update interval in seconds
"""
plotter = MetricsPlotter()
try:
while True:
metrics = get_metrics_func(adapter_name)
plotter.update_plot(metrics)
time.sleep(interval)
except KeyboardInterrupt:
print("\nStopping metrics plotting...")
print(plotter.session.get_session_summary())
plotter.save_plot()

35
src/libs/session.py Normal file
View File

@ -0,0 +1,35 @@
from typing import List
from .metrics import Metrics
from datetime import datetime
class SessionMetrics:
def __init__(self):
self.signal_readings: List[float] = []
self.bitrate_readings: List[float] = []
self.start_time = datetime.now()
def add_metrics(self, metrics: Metrics):
try:
self.signal_readings.append(float(metrics.signal_strength))
self.bitrate_readings.append(float(metrics.bitrate))
except ValueError:
print("Warning: Invalid metrics value encountered")
def get_session_summary(self) -> str:
if not self.signal_readings or not self.bitrate_readings:
return "No data collected in this session"
avg_signal = sum(self.signal_readings) / len(self.signal_readings)
avg_bitrate = sum(self.bitrate_readings) / len(self.bitrate_readings)
duration = datetime.now() - self.start_time
minutes = duration.total_seconds() / 60
return (
f"Session Summary:\n"
f"Duration: {minutes:.1f} minutes\n"
f"Average Signal: {avg_signal:.1f} dBm\n"
f"Average Bitrate: {avg_bitrate:.1f} Mb/s\n"
f"Samples collected: {len(self.signal_readings)}"
)

92
src/main.py Normal file
View File

@ -0,0 +1,92 @@
import os
import sys
import subprocess
import argparse
from libs.metrics import Metrics
from libs.plotter import plot_metrics_live
def get_metrics(adapter_name: str = "wlan0") -> Metrics:
"""
Get the metrics of the network adapter
"""
# Execute iwconfig command and return the output
iwconfig_output = subprocess.run(
["iwconfig", adapter_name], capture_output=True, text=True
)
output = iwconfig_output.stdout
# Initialize default values
signal_strength = "0"
bitrate = "0"
is_power_save_enabled = False
# Try different possible formats for signal level
try:
if "Signal level=" in output:
signal_strength = output.split("Signal level=")[1].split(" dBm")[0].strip()
elif "Signal level" in output:
signal_strength = output.split("Signal level")[1].split("dBm")[0].strip()
elif "signal:" in output.lower():
signal_strength = output.lower().split("signal:")[1].split("dBm")[0].strip()
# Try different possible formats for bit rate
if "Bit Rate=" in output:
bitrate = output.split("Bit Rate=")[1].split(" Mb/s")[0].strip()
elif "Bit Rate:" in output:
bitrate = output.split("Bit Rate:")[1].split(" Mb/s")[0].strip()
elif "tx bitrate:" in output.lower():
bitrate = output.lower().split("tx bitrate:")[1].split("mbit/s")[0].strip()
# Try different possible formats for power management
if "Power Management:" in output:
is_power_save_enabled = (
"on" in output.split("Power Management:")[1].lower().strip()
)
elif "power management" in output.lower():
is_power_save_enabled = (
"on" in output.lower().split("power management")[1].strip()
)
except (IndexError, ValueError) as e:
print(f"Warning: Error parsing iwconfig output: {e}")
print(f"Raw output: {output}")
return Metrics(signal_strength, bitrate, is_power_save_enabled)
def get_active_interface():
"""
Get the active interface name
"""
all_interfaces = subprocess.run(["iwconfig"], capture_output=True, text=True)
for interface in all_interfaces.stdout.split("\n"):
if "ESSID" in interface:
return interface.split(" ")[0].strip()
return None
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--interface", type=str, help="The interface to use")
args = parser.parse_args() # Check if we got an interface name
if not args.interface:
print("No interface name provided: we will try to find the active interface")
try:
active_interface = get_active_interface()
print(f"The active interface is {active_interface}")
except Exception as e:
print(f"Error getting active interface: {e}")
print("Using wlan0 as default interface")
active_interface = "wlan0"
else:
print(f"Using interface {args.interface}")
active_interface = args.interface
# Execute iwconfig command
metrics = get_metrics(active_interface)
print(metrics)
try:
plot_metrics_live(get_metrics, active_interface)
except KeyboardInterrupt:
print("[+] Done")