|
| 1 | +import subprocess |
| 2 | +import re |
| 3 | +import tkinter as tk |
| 4 | +from tkinter import ttk |
| 5 | +import matplotlib.pyplot as plt |
| 6 | +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg |
| 7 | + |
| 8 | +import time |
| 9 | + |
| 10 | +CHANNELS_24 = list(range(1, 14)) |
| 11 | +CHANNELS_5 = [36, 40, 44, 48, 149, 153, 157, 161, 165] |
| 12 | +ALL_CHANNELS = sorted(set(CHANNELS_24 + CHANNELS_5)) |
| 13 | + |
| 14 | +scanning = False |
| 15 | +channel_details = {} |
| 16 | + |
| 17 | +def parse_netsh_output(): |
| 18 | + try: |
| 19 | + output = subprocess.check_output( |
| 20 | + ["netsh", "wlan", "show", "networks", "mode=bssid"], |
| 21 | + encoding="cp866", # Русская кодировка |
| 22 | + errors="ignore" |
| 23 | + ) |
| 24 | + except subprocess.CalledProcessError: |
| 25 | + return {}, {} |
| 26 | + |
| 27 | + usage = {} |
| 28 | + details = {} |
| 29 | + |
| 30 | + networks = output.split("SSID ") |
| 31 | + for net in networks[1:]: |
| 32 | + ssid_match = re.search(r":\s*(.+)", net.splitlines()[0]) |
| 33 | + ssid = ssid_match.group(1).strip() if ssid_match else "Unknown" |
| 34 | + channel_matches = re.findall(r"(?:Канал|Channel)\s*:\s*(\d+)", net) |
| 35 | + for ch_str in channel_matches: |
| 36 | + ch = int(ch_str) |
| 37 | + usage[ch] = usage.get(ch, 0) + 1 |
| 38 | + if ch not in details: |
| 39 | + details[ch] = [] |
| 40 | + if ssid not in details[ch]: |
| 41 | + details[ch].append(ssid) |
| 42 | + return usage, details |
| 43 | + |
| 44 | +def plot_usage(channel_usage, details, band_filter): |
| 45 | + if band_filter == "2.4": |
| 46 | + channels = CHANNELS_24 |
| 47 | + elif band_filter == "5": |
| 48 | + channels = CHANNELS_5 |
| 49 | + else: |
| 50 | + channels = ALL_CHANNELS |
| 51 | + |
| 52 | + usage = [channel_usage.get(ch, 0) for ch in channels] |
| 53 | + max_usage = max(usage) if usage else 1 |
| 54 | + usage_percent = [int((u / max_usage) * 100) if max_usage > 0 else 0 for u in usage] |
| 55 | + |
| 56 | + fig, ax = plt.subplots(figsize=(10, 5)) |
| 57 | + bars = ax.bar([str(c) for c in channels], usage_percent, color='skyblue') |
| 58 | + ax.set_title("Загруженность Wi-Fi каналов") |
| 59 | + ax.set_ylabel("Загруженность (%)") |
| 60 | + ax.set_xlabel("Каналы") |
| 61 | + ax.set_ylim(0, 100) |
| 62 | + ax.grid(True, axis='y', linestyle='--', alpha=0.7) |
| 63 | + |
| 64 | + for bar, percent, ch in zip(bars, usage_percent, channels): |
| 65 | + ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 1, |
| 66 | + f"{percent}%\n({channel_usage.get(ch, 0)})", ha='center', va='bottom') |
| 67 | + |
| 68 | + return fig |
| 69 | + |
| 70 | +def update_scan(): |
| 71 | + global channel_details |
| 72 | + usage, details = parse_netsh_output() |
| 73 | + channel_details = details |
| 74 | + fig = plot_usage(usage, details, band_var.get()) |
| 75 | + |
| 76 | + for widget in plot_frame.winfo_children(): |
| 77 | + widget.destroy() |
| 78 | + |
| 79 | + canvas = FigureCanvasTkAgg(fig, master=plot_frame) |
| 80 | + canvas.draw() |
| 81 | + canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) |
| 82 | + |
| 83 | + text_output.delete(1.0, tk.END) |
| 84 | + for ch in sorted(details): |
| 85 | + if (band_var.get() == "2.4" and ch not in CHANNELS_24) or \ |
| 86 | + (band_var.get() == "5" and ch not in CHANNELS_5): |
| 87 | + continue |
| 88 | + ssids = ", ".join(details[ch]) |
| 89 | + text_output.insert(tk.END, f"Канал {ch} — {len(details[ch])} точек: {ssids}\n") |
| 90 | + |
| 91 | +def scan_loop(): |
| 92 | + global scanning |
| 93 | + scan_duration = int(duration_entry.get()) |
| 94 | + interval = int(interval_entry.get()) |
| 95 | + start_time = time.time() |
| 96 | + |
| 97 | + def loop_step(): |
| 98 | + nonlocal start_time |
| 99 | + if not scanning or (time.time() - start_time >= scan_duration): |
| 100 | + return |
| 101 | + update_scan() |
| 102 | + root.after(interval * 1000, loop_step) |
| 103 | + |
| 104 | + root.after(0, loop_step) |
| 105 | + |
| 106 | +def start_scan(): |
| 107 | + global scanning |
| 108 | + if scanning: |
| 109 | + return |
| 110 | + scanning = True |
| 111 | + scan_loop() |
| 112 | + |
| 113 | +def stop_scan(): |
| 114 | + global scanning |
| 115 | + scanning = False |
| 116 | + |
| 117 | +# ---------------- GUI ------------------ |
| 118 | + |
| 119 | +root = tk.Tk() |
| 120 | +root.title("Wi-FiChannelScanner") |
| 121 | + |
| 122 | +# Верхняя панель |
| 123 | +control_frame = ttk.Frame(root, padding=10) |
| 124 | +control_frame.pack(fill=tk.X) |
| 125 | + |
| 126 | +ttk.Label(control_frame, text="Диапазон:").pack(side=tk.LEFT) |
| 127 | +band_var = tk.StringVar(value="all") |
| 128 | +ttk.OptionMenu(control_frame, band_var, "all", "2.4", "5", "all").pack(side=tk.LEFT, padx=5) |
| 129 | + |
| 130 | +ttk.Label(control_frame, text="Сканировать секунд:").pack(side=tk.LEFT) |
| 131 | +duration_entry = ttk.Entry(control_frame, width=5) |
| 132 | +duration_entry.insert(0, "30") |
| 133 | +duration_entry.pack(side=tk.LEFT) |
| 134 | + |
| 135 | +ttk.Label(control_frame, text="Интервал обновления (сек):").pack(side=tk.LEFT) |
| 136 | +interval_entry = ttk.Entry(control_frame, width=5) |
| 137 | +interval_entry.insert(0, "5") |
| 138 | +interval_entry.pack(side=tk.LEFT) |
| 139 | + |
| 140 | +ttk.Button(control_frame, text="Старт", command=start_scan).pack(side=tk.LEFT, padx=10) |
| 141 | +ttk.Button(control_frame, text="Стоп", command=stop_scan).pack(side=tk.LEFT) |
| 142 | + |
| 143 | +# График |
| 144 | +plot_frame = ttk.Frame(root) |
| 145 | +plot_frame.pack(fill=tk.BOTH, expand=True) |
| 146 | + |
| 147 | +# Текстовый вывод |
| 148 | +text_output = tk.Text(root, height=10, wrap=tk.WORD) |
| 149 | +text_output.pack(fill=tk.BOTH, expand=False, padx=10, pady=5) |
| 150 | + |
| 151 | +root.mainloop() |
0 commit comments