Indicator for Network Status

本文介绍了一个用于GNOME桌面环境的轻量级网络指示器应用。该应用通过Python实现,能够实时显示网络上传和下载速度,并根据不同状态更换图标。文章提供了完整的源代码,便于读者理解和使用。
GNOME可以用的最小的网络指示器

#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Created on Tue Nov 11 21:26:17 2014

@author: terry
"""

import pygtk
pygtk.require('2.0')
import sys
import os
import shutil
import json
import time
from threading import Thread, Event
import subprocess
import psutil as ps
import copy
import re
import gtk
gtk.gdk.threads_init()
import appindicator
import logging



class StatusUpdateIndicator(Thread):
    B_UNITS = ['', 'KB', 'MB', 'GB', 'TB']
    interval = 2

    def __init__(self):
        Thread.__init__(self)
        self.isRunning = True
        self.last = ps.cpu_times()
        self.last_net_usage = [0, 0]  # (up, down)

        self.network_indicator = appindicator.Indicator ("indicator-sysmonitor", "sysmonitor", appindicator.CATEGORY_APPLICATION_STATUS)
        self.network_indicator.set_status (appindicator.STATUS_ACTIVE)
        self.network_indicator.set_icon("gnome-netstatus-idle")
        self.network_indicator.set_label("Init...")

        # create a menu
        self.menu = gtk.Menu()
        image = gtk.ImageMenuItem(gtk.STOCK_QUIT)
        image.connect("activate", self.quit)
        image.show()
        self.menu.append(image)
        self.menu.show()
        self.network_indicator.set_menu(self.menu)

    def bytes_to_human(self,bytes_):
        unit = 0
        while bytes_ > 1024:
            unit += 1
            bytes_ /= 1024

        return '{}{}'.format(int(bytes_), self.B_UNITS[unit])

    def update(self):
        """It returns the bytes sent and recieved in bytes/second"""
        current = [0, 0]
        for _, iostat in ps.network_io_counters(pernic=True).items():
            current[0] += iostat.bytes_recv
            current[1] += iostat.bytes_sent
        dummy = copy.deepcopy(current)

        current[0] -= self.last_net_usage[0]
        current[1] -= self.last_net_usage[1]
        self.last_net_usage = dummy
        current[0] /= self.interval
        current[1] /= self.interval
        if current[0] == 0 and current[1] == 0:
            self.network_indicator.set_icon("gnome-netstatus-idle")
        elif current[0] > 0 and current[1] == 0:
            self.network_indicator.set_icon("gnome-netstatus-rx")
        elif current[0] == 0 and current[1] > 0:
            self.network_indicator.set_icon("gnome-netstatus-tx")
        else:
            self.network_indicator.set_icon("gnome-netstatus-txrx")
        traffic = '{}/s | {}/s'.format(self.bytes_to_human(current[1]),
                                    self.bytes_to_human(current[0]))
#        print traffic
        self.network_indicator.set_label(traffic)

    def run(self):
        """It is the main loop."""
        while self.isRunning:
            self.update()
            time.sleep(self.interval)

    def quit(self, widget, data=None):
        self.isRunning = False
        gtk.main_quit()


def main():
    gtk.main()
    return 0

if __name__ == "__main__":
    indicator = StatusUpdateIndicator()
    indicator.setDaemon(True)
    indicator.start()
    main()

#network_indicator = appindicator.Indicator("indicator-sysmonitor",
#                                            "sysmonitor",
#                                            appindicator.CATEGORY_SYSTEM_SERVICES)
#network_indicator.set_status(appindicator.STATUS_ACTIVE)
#network_indicator.set_label("Init...")
#network_indicator.set_icon("gnome-netstatus-idle")
#
#updater = StatusUpdater(network_indicator)

在pyqt界面上添加按钮,实现中英切换,import sys import cv2 import numpy as np import json import os import time import datetime from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QLabel, QVBoxLayout, QHBoxLayout, QPushButton, QMessageBox, QLineEdit, QSizePolicy, QGroupBox, QFrame, QStackedLayout) from PyQt5.QtCore import Qt, pyqtSignal, QThread, QMutex, QMutexLocker, QTimer from PyQt5.QtGui import QImage, QPixmap, QIntValidator, QFont, QColor, QPalette from ultralytics import YOLO from PyQt5.QtNetwork import QTcpSocket, QAbstractSocket # 配置参数 CAPTURE_INDEX = 0 OBJ_LIST = ['without-safetybelt', 'safetybelt'] # 修改为安全带类别 DETECTOR_PATH = 'C:/weights/safetybelt/best.pt' # 双语文本字典 TEXT = { "main_title": ["AI智能安全带检测系统", "AI Smart Safety Belt Detection System"], "video_title": ["实时监控画面", "Live Monitoring"], "network_settings": ["网络设置", "Network Settings"], "server_ip": ["服务器IP:", "Server IP:"], "port": ["端口号:", "Port:"], "set_network": ["设置网络", "Set Network"], "connection_status": ["连接状态", "Connection Status"], "not_connected": ["未连接", "Not Connected"], "connected": ["已连接", "Connected"], "disconnected": ["断开连接", "Disconnected"], "detection_status": ["检测状态", "Detection Status"], "belt_status": ["安全带佩戴状态", "Safety Belt Status"], "waiting_detection": ["-- 等待检测 --", "-- Waiting for Detection --"], "safe_state": ["安全状态", "Safe State"], "danger_state": ["危险状态!", "Danger State!"], "no_person": ["未检测到人员", "No Person Detected"], "status_explanation": ["状态说明:", "Status Explanation:"], "safe_explanation": ["安全: 所有人员佩戴安全带", "Safe: All personnel wearing safety belts"], "danger_explanation": ["危险: 检测到未佩戴安全带", "Danger: Detected personnel not wearing safety belts"], "info_explanation": ["信息: 未检测到人员", "Info: No personnel detected"], "loading_camera": ["正在加载摄像头...", "Loading camera..."], "system_started": ["系统已启动,正在初始化...", "System started, initializing..."], "sending_status": ["发送状态", "Sending Status"], "not_sent": ["未发送", "Not Sent"], "preparing_send": ["准备发送...", "Preparing to send..."], "send_success": ["发送成功", "Send Successful"], "send_failed": ["发送失败", "Send Failed"], "error": ["错误", "Error"], "invalid_ip": ["无效的IP地址格式", "Invalid IP address format"], "invalid_port": ["端口号必须为1-65535之间的整数", "Port must be an integer between 1-65535"], "settings_success": ["设置成功", "Settings Successful"], "network_updated": ["已更新网络设置", "Network settings updated"], "config_error": ["配置错误", "Configuration Error"], "config_failed": ["配置文件格式错误,已使用默认配置", "Configuration file format error, using default settings"], "connecting": ["正在连接", "Connecting to"], "last_update": ["最后更新", "Last update"], "connecting_status": ["正在连接...", "Connecting..."] } # 样式常量 - 现代深色主题 MAIN_BG_COLOR = "#1e1e2e" # 深蓝紫色背景 PANEL_BG_COLOR = "#252536" # 面板背景 BUTTON_COLOR = "#4169E1" # 皇家蓝按钮 BUTTON_HOVER_COLOR = "#5a7df1" # 浅蓝色悬停 BUTTON_PRESSED_COLOR = "#2a4fc0" # 深蓝色按下 STATUS_SAFE_COLOR = "#32CD32" # 安全绿色 STATUS_DANGER_COLOR = "#FF4500" # 危险橙色 STATUS_INFO_COLOR = "#1E90FF" # 信息蓝色 TEXT_COLOR = "#e0e0ff" # 浅紫色文本 BORDER_COLOR = "#4a4a6a" # 边框颜色 ACCENT_COLOR = "#9370DB" # 强调色(紫色) # 语言设置 (0=中文, 1=英文) LANGUAGE = 0 # 默认中文 def tr(key): """翻译函数,根据LANGUAGE设置返回对应语言的文本""" return TEXT[key][LANGUAGE] if key in TEXT else key # TCP客户端线程 class TCPClientThread(QThread): connectionStateChanged = pyqtSignal(str) def __init__(self, server_ip, server_port=7978): super().__init__() self.server_ip = server_ip self.server_port = server_port self.running = True self.client_socket = None def run(self): while self.running: self.client_socket = QTcpSocket() self.client_socket.connectToHost(self.server_ip, self.server_port) if self.client_socket.waitForConnected(1000): self.connectionStateChanged.emit(tr("connected")) while self.running and self.client_socket.state() == QAbstractSocket.ConnectedState: self.msleep(100) else: self.connectionStateChanged.emit(tr("disconnected")) time.sleep(5) def send_data(self, message): if not (self.client_socket and self.client_socket.state() == QAbstractSocket.ConnectedState): return -1 try: byte_written = self.client_socket.write(message.encode('utf-8')) if byte_written > 0: self.client_socket.waitForBytesWritten(1000) return byte_written return -1 except Exception as e: print(f"发送异常: {str(e)}") return -1 def stop(self): self.running = False if self.client_socket: self.client_socket.abort() self.wait() # 视频处理线程 class VideoThread(QThread): update_signal = pyqtSignal(np.ndarray, str) def __init__(self): super().__init__() self._run_flag = True self.cap = cv2.VideoCapture(0) self.model_belt = YOLO(DETECTOR_PATH) # 修改变量名为安全带检测 self.data_lock = QMutex() self.current_data = {'safety': 'without-safetybelt'} # 默认状态为未佩戴安全带 # FPS相关属性 self.frame_count = 0 self.start_time = time.time() self.fps = 0 def run(self): while self._run_flag: ret, frame = self.cap.read() if ret: # 安全带检测 results_belt = self.model_belt(frame) # 修改变量名 annotated_frame = frame.copy() # 初始化安全状态 has_belt = False has_no_belt = False # 安全带检测逻辑 if results_belt[0].boxes is not None: # 修改变量名 boxes_belt = results_belt[0].boxes # 修改变量名 valid_boxes_belt = [box for box in boxes_belt if box.conf.item() > 0.5] # 修改变量名 for box in valid_boxes_belt: # 修改变量名 cls_id = int(box.cls) cls_name = self.model_belt.names[cls_id] # 修改变量名 if cls_name in OBJ_LIST: x1, y1, x2, y2 = map(int, box.xyxy[0]) # 根据是否佩戴安全带设置不同颜色 if cls_name == 'safetybelt': # 佩戴安全带 color = (0, 255, 0) # 绿色 has_belt = True else: # 未佩戴安全带 color = (0, 0, 255) # 红色 has_no_belt = True # 绘制检测框 cv2.rectangle(annotated_frame, (x1, y1), (x2, y2), color, 2) cv2.putText(annotated_frame, cls_name, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) # 确定整体安全状态 if has_no_belt: safety_status = 'without-safetybelt' # 只要有一个人未佩戴安全带 elif has_belt: safety_status = 'safetybelt' # 所有人都佩戴了安全带 else: safety_status = '' # 未检测到人员 # 计算并显示FPS(在视频帧上绘制) self.frame_count += 1 if (time.time() - self.start_time) > 1.0: self.fps = self.frame_count / (time.time() - self.start_time) self.frame_count = 0 self.start_time = time.time() # 在视频帧上绘制FPS fps_text = f"FPS: {self.fps:.1f}" cv2.putText(annotated_frame, fps_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1, cv2.LINE_AA) self.update_signal.emit(annotated_frame, safety_status) self.update_data(safety_status) def update_data(self, safety): with QMutexLocker(self.data_lock): self.current_data.update({ 'safety': safety or 'without-safetybelt' }) def stop(self): self._run_flag = False self.cap.release() self.wait() # 主窗口类 class MainWindow(QMainWindow): def __init__(self, server_ip='127.0.0.1', server_port=7978): super().__init__() self.setWindowTitle(tr("main_title")) # 修改标题为安全带检测 self.setGeometry(100, 100, 1200, 750) # 设置主窗口样式 self.setStyleSheet(f""" background-color: {MAIN_BG_COLOR}; color: {TEXT_COLOR}; font-family: 'Segoe UI', Arial, sans-serif; """) # 初始化成员变量 self.video_thread = None self.tcp_client = None self.first_frame_received = False # 标记是否接收到第一帧 # 初始化界面 main_widget = QWidget() main_layout = QHBoxLayout(main_widget) main_layout.setContentsMargins(20, 20, 20, 20) main_layout.setSpacing(20) # 左侧视频显示区域 video_frame = QFrame() video_frame.setFrameShape(QFrame.StyledPanel) video_frame.setStyleSheet(f""" background-color: {PANEL_BG_COLOR}; border-radius: 12px; border: 1px solid {BORDER_COLOR}; """) video_layout = QVBoxLayout(video_frame) video_layout.setContentsMargins(15, 15, 15, 15) video_layout.setSpacing(15) # 视频标题 video_title = QLabel(tr("video_title")) video_title.setAlignment(Qt.AlignCenter) video_title.setStyleSheet(""" font-size: 20px; font-weight: bold; color: #b0b0ff; padding: 8px; border-bottom: 2px solid #4a4a6a; """) video_layout.addWidget(video_title) # 视频显示标签 self.video_label = QLabel() self.video_label.setAlignment(Qt.AlignCenter) self.video_label.setMinimumSize(720, 540) self.video_label.setStyleSheet(""" background-color: #000000; border-radius: 8px; border: 1px solid #4a4a6a; """) # 添加摄像头加载提示(直接覆盖在视频标签上) self.camera_status_label = QLabel(tr("loading_camera")) self.camera_status_label.setAlignment(Qt.AlignCenter) self.camera_status_label.setStyleSheet(""" font-size: 24px; font-weight: bold; color: #9370DB; background-color: rgba(30, 30, 46, 180); border-radius: 8px; padding: 20px; """) self.camera_status_label.setVisible(True) # 初始显示加载提示 # 使用布局将提示标签覆盖在视频标签上 video_container = QWidget() video_container_layout = QStackedLayout(video_container) video_container_layout.setStackingMode(QStackedLayout.StackAll) video_container_layout.addWidget(self.video_label) video_container_layout.addWidget(self.camera_status_label) video_layout.addWidget(video_container, 1) main_layout.addWidget(video_frame, 70) # 右侧控制面板 control_frame = QFrame() control_frame.setStyleSheet(f""" background-color: {PANEL_BG_COLOR}; border-radius: 12px; border: 1px solid {BORDER_COLOR}; """) control_layout = QVBoxLayout(control_frame) control_layout.setContentsMargins(20, 20, 20, 20) control_layout.setSpacing(20) # 系统标题 title_label = QLabel(tr("main_title")) # 修改为安全带检测 title_label.setAlignment(Qt.AlignCenter) title_label.setStyleSheet(""" font-size: 24px; font-weight: bold; color: {ACCENT_COLOR}; padding: 10px 0; border-bottom: 2px solid #4a4a6a; """.format(ACCENT_COLOR=ACCENT_COLOR)) control_layout.addWidget(title_label) # 网络设置组 network_group = QGroupBox(tr("network_settings")) network_group.setStyleSheet(f""" QGroupBox {{ font-size: 18px; font-weight: bold; color: {TEXT_COLOR}; border: none; }} QGroupBox::title {{ subcontrol-origin: margin; left: 10px; padding: 0 5px; color: {ACCENT_COLOR}; }} """) network_layout = QVBoxLayout(network_group) network_layout.setSpacing(15) network_layout.setContentsMargins(15, 25, 15, 15) # IP地址行 ip_row = QHBoxLayout() ip_label = QLabel(tr("server_ip")) ip_label.setStyleSheet("font-size: 16px;") self.ip_line_edit = QLineEdit(server_ip) self.ip_line_edit.setStyleSheet(f""" QLineEdit {{ background-color: #1e1e2e; color: {TEXT_COLOR}; border: 1px solid {BORDER_COLOR}; border-radius: 6px; padding: 8px 12px; font-size: 16px; selection-background-color: {ACCENT_COLOR}; }} QLineEdit:focus {{ border: 2px solid {ACCENT_COLOR}; }} """) ip_row.addWidget(ip_label) ip_row.addWidget(self.ip_line_edit, 1) network_layout.addLayout(ip_row) # 端口行 port_row = QHBoxLayout() port_label = QLabel(tr("port")) port_label.setStyleSheet("font-size: 16px;") self.port_line_edit = QLineEdit(str(server_port)) self.port_line_edit.setValidator(QIntValidator(1, 65535, self)) self.port_line_edit.setStyleSheet(f""" QLineEdit {{ background-color: #1e1e2e; color: {TEXT_COLOR}; border: 1px solid {BORDER_COLOR}; border-radius: 6px; padding: 8px 12px; font-size: 16px; selection-background-color: {ACCENT_COLOR}; }} QLineEdit:focus {{ border: 2px solid {ACCENT_COLOR}; }} """) port_row.addWidget(port_label) port_row.addWidget(self.port_line_edit, 1) network_layout.addLayout(port_row) # 按钮行 button_row = QHBoxLayout() button_row.setSpacing(15) self.set_network_button = QPushButton(tr("set_network")) self.set_network_button.setCursor(Qt.PointingHandCursor) self.set_network_button.setStyleSheet(f""" QPushButton {{ background-color: {BUTTON_COLOR}; color: white; border: none; border-radius: 8px; padding: 12px 20px; font-size: 16px; font-weight: bold; min-width: 120px; }} QPushButton:hover {{ background-color: {BUTTON_HOVER_COLOR}; }} QPushButton:pressed {{ background-color: {BUTTON_PRESSED_COLOR}; }} """) # 连接状态显示 self.connect_label = QLabel(f"{tr('connection_status')}: {tr('not_connected')}") self.connect_label.setStyleSheet(""" font-size: 16px; font-weight: bold; padding: 10px; background-color: #1e1e2e; border-radius: 8px; border: 1px solid #4a4a6a; """) button_row.addWidget(self.set_network_button) button_row.addWidget(self.connect_label, 1) network_layout.addLayout(button_row) control_layout.addWidget(network_group) # 状态显示组 status_group = QGroupBox(tr("detection_status")) status_group.setStyleSheet(f""" QGroupBox {{ font-size: 18px; font-weight: bold; color: {TEXT_COLOR}; border: none; }} QGroupBox::title {{ subcontrol-origin: margin; left: 10px; padding: 0 5px; color: {ACCENT_COLOR}; }} """) status_layout = QVBoxLayout(status_group) status_layout.setSpacing(15) status_layout.setContentsMargins(15, 25, 15, 15) # 安全带状态显示 status_box = QFrame() status_box.setStyleSheet(f""" background-color: #1e1e2e; border-radius: 12px; border: 1px solid #4a4a6a; """) status_box_layout = QVBoxLayout(status_box) status_box_layout.setContentsMargins(20, 20, 20, 20) status_box_layout.setSpacing(15) status_title = QLabel(tr("belt_status")) # 修改为安全带 status_title.setStyleSheet(""" font-size: 18px; font-weight: bold; color: #b0b0ff; padding-bottom: 5px; border-bottom: 1px solid #4a4a6a; """) status_title.setAlignment(Qt.AlignCenter) status_box_layout.addWidget(status_title) self.belt_display = QLabel(tr("waiting_detection")) # 修改变量名 self.belt_display.setAlignment(Qt.AlignCenter) self.belt_display.setStyleSheet(f""" font-size: 28px; font-weight: bold; padding: 25px 0; border-radius: 8px; background-color: #2a2a3a; """) status_box_layout.addWidget(self.belt_display) # 状态说明 status_info = QLabel(tr("status_explanation")) status_info.setStyleSheet("font-size: 16px; font-weight: bold;") # 状态指示器 status_indicator = QWidget() indicator_layout = QVBoxLayout(status_indicator) indicator_layout.setSpacing(5) # 安全状态 safe_indicator = QHBoxLayout() safe_label = QLabel(tr("safe_explanation")) # 修改为安全带 safe_label.setStyleSheet("font-size: 14px;") safe_dot = QLabel("●") safe_dot.setStyleSheet(f"color: {STATUS_SAFE_COLOR}; font-size: 20px;") safe_indicator.addWidget(safe_dot) safe_indicator.addWidget(safe_label) safe_indicator.addStretch() indicator_layout.addLayout(safe_indicator) # 危险状态 danger_indicator = QHBoxLayout() danger_label = QLabel(tr("danger_explanation")) # 修改为安全带 danger_label.setStyleSheet("font-size: 14px;") danger_dot = QLabel("●") danger_dot.setStyleSheet(f"color: {STATUS_DANGER_COLOR}; font-size: 20px;") danger_indicator.addWidget(danger_dot) danger_indicator.addWidget(danger_label) danger_indicator.addStretch() indicator_layout.addLayout(danger_indicator) # 信息状态 info_indicator = QHBoxLayout() info_label = QLabel(tr("info_explanation")) info_label.setStyleSheet("font-size: 14px;") info_dot = QLabel("●") info_dot.setStyleSheet(f"color: {STATUS_INFO_COLOR}; font-size: 20px;") info_indicator.addWidget(info_dot) info_indicator.addWidget(info_label) info_indicator.addStretch() indicator_layout.addLayout(info_indicator) status_box_layout.addWidget(status_info) status_box_layout.addWidget(status_indicator) status_layout.addWidget(status_box) control_layout.addWidget(status_group) # 添加控制面板到主布局 main_layout.addWidget(control_frame, 30) self.setCentralWidget(main_widget) # 连接设置网络按钮的信号 self.set_network_button.clicked.connect(self.update_network) self.load_config() # 加载配置文件 self.setup_tcp() # 建立TCP连接 # 初始化视频线程 self.video_thread = VideoThread() self.video_thread.update_signal.connect(self.update_frame) self.video_thread.start() # 状态栏初始化 self.statusBar().setStyleSheet(f""" background-color: {PANEL_BG_COLOR}; color: {TEXT_COLOR}; border-top: 1px solid {BORDER_COLOR}; font-size: 12px; padding: 4px; """) self.statusBar().showMessage(tr("system_started")) self.send_status_label = QLabel(f"{tr('sending_status')}: {tr('not_sent')}") self.statusBar().addPermanentWidget(self.send_status_label) self.setup_timers() # 初始化定时器 def update_send_status(self, message, color="white"): """更新发送状态显示""" color_map = { "green": STATUS_SAFE_COLOR, "red": STATUS_DANGER_COLOR, "blue": STATUS_INFO_COLOR, "orange": "#ff8c00", "white": TEXT_COLOR } hex_color = color_map.get(color.lower(), TEXT_COLOR) self.send_status_label.setStyleSheet(f"color: {hex_color}; font-size: 12px; font-weight: bold;") self.send_status_label.setText(f"{tr('sending_status')}: {message}") # 状态栏更新 current_time = datetime.datetime.now().strftime('%H:%M:%S') self.statusBar().showMessage(f"{tr('last_update')}: {current_time} - {message}", 3000) def send_class_data(self): """发送检测数据""" self.update_send_status(tr("preparing_send"), "blue") sent_bytes = -1 try: # 基础校验 if not hasattr(self, 'tcp_client') or not self.tcp_client.client_socket: self.update_send_status(tr("not_connected"), "red") return if self.tcp_client.client_socket.state() != QAbstractSocket.ConnectedState: self.update_send_status(tr("not_connected"), "red") return # 获取数据 with QMutexLocker(self.video_thread.data_lock): data = self.video_thread.current_data.copy() # 生成报告 xml_data = self.generate_report(data) # 执行发送 sent_bytes = self.tcp_client.send_data(xml_data) expected_bytes = len(xml_data.encode('utf-8')) # 结果处理 if sent_bytes == expected_bytes: status_msg = tr("send_success") else: status_msg = f"{tr('send_failed')} ({sent_bytes}/{expected_bytes} {tr('bytes') if LANGUAGE == 0 else 'bytes'})" color = "green" if sent_bytes == expected_bytes else "orange" self.update_send_status(status_msg, color) except Exception as e: error_msg = f"{tr('send_failed')}: {str(e)} | {tr('sent_bytes') if LANGUAGE == 0 else 'Sent'}: {sent_bytes}{tr('bytes') if LANGUAGE == 0 else 'bytes'}" print(error_msg) self.update_send_status(error_msg[:40], "red") # 异常时强制重置连接 if hasattr(self, 'tcp_client'): self.tcp_client.stop() def generate_report(self, data): """生成报告字符串""" # 判断安全模式(safetybelt=1,其他=0) mode = 1 if data.get('safety') == 'safetybelt' else 0 # 修改为安全带检测 # 构建字符串 report_str = ( "{json;report;" f"mode:{mode};}}" ) return report_str def update_network(self): """更新网络设置""" new_ip = self.ip_line_edit.text() new_port = self.port_line_edit.text() # 输入验证 if not self.validate_ip(new_ip): QMessageBox.critical(self, tr("error"), tr("invalid_ip")) return try: port = int(new_port) if not (1 <= port <= 65535): raise ValueError except ValueError: QMessageBox.critical(self, tr("error"), tr("invalid_port")) return # 更新界面显示 self.ip_line_edit.setText(new_ip) self.port_line_edit.setText(str(port)) # 重启TCP连接 self.setup_tcp() # 保存配置 self.save_config() # 显示成功消息 QMessageBox.information(self, tr("settings_success"), f"{tr('network_updated')}:\nIP: {new_ip}\n{tr('port')}: {port}", QMessageBox.Ok) def save_config(self): """保存配置到文件""" config_folder = 'C:/config' if not os.path.exists(config_folder): os.makedirs(config_folder) config_path = os.path.join(config_folder, 'config.txt') with open(config_path, 'w') as f: f.write("\n".join([ self.ip_line_edit.text(), self.port_line_edit.text() ])) def validate_ip(self, ip): """IP地址验证方法""" try: parts = ip.split('.') if len(parts) != 4: return False return all(0 <= int(part) < 256 for part in parts) except: return False def setup_timers(self): """设置定时器""" self.class_timer = QTimer() self.class_timer.setInterval(1000) # 每秒发送一次数据 self.class_timer.timeout.connect(self.send_class_data) self.class_timer.start() def setup_tcp(self): """创建TCP连接""" # 获取当前IP current_ip = self.ip_line_edit.text() try: current_port = int(self.port_line_edit.text()) except ValueError: current_port = 7978 # 如果已有连接,先停止 if hasattr(self, 'tcp_client') and self.tcp_client: self.tcp_client.stop() self.tcp_client.wait() # 创建新连接 self.tcp_client = TCPClientThread(current_ip, current_port) self.tcp_client.connectionStateChanged.connect(self.update_connection_status) self.tcp_client.start() self.statusBar().showMessage(f"{tr('connecting')} {current_ip}...", 3000) def load_config(self): """加载配置文件""" config_folder = 'C:/config' config_path = os.path.join(config_folder, 'config.txt') try: if os.path.exists(config_path): with open(config_path, 'r') as f: lines = [line.strip() for line in f.readlines()] # 读取IP地址(第1行)和端口(第2行) if len(lines) >= 1: self.ip_line_edit.setText(lines[0]) if len(lines) >= 2: self.port_line_edit.setText(lines[1]) print(f"配置加载成功:IP={lines[0]}") except Exception as e: print(f"配置加载失败: {str(e)}") QMessageBox.warning(self, tr("config_error"), f"{tr('config_failed')}\n{tr('error')}:{str(e)}") def update_frame(self, frame, safety_status): # 如果是第一帧,隐藏提示 if not self.first_frame_received: self.camera_status_label.setVisible(False) self.first_frame_received = True # 更新安全带状态显示 if safety_status == 'safetybelt': # 佩戴安全带 self.belt_display.setText(tr("safe_state")) # 修改为安全带状态 self.belt_display.setStyleSheet(f""" font-size: 28px; font-weight: bold; color: {STATUS_SAFE_COLOR}; padding: 25px 0; border-radius: 8px; background-color: #2a2a3a; border: 2px solid {STATUS_SAFE_COLOR}; """) elif safety_status == 'without-safetybelt': # 未佩戴安全带 self.belt_display.setText(tr("danger_state")) self.belt_display.setStyleSheet(f""" font-size: 28px; font-weight: bold; color: {STATUS_DANGER_COLOR}; padding: 25px 0; border-radius: 8px; background-color: #2a2a3a; border: 2px solid {STATUS_DANGER_COLOR}; """) else: # 未检测到人员 self.belt_display.setText(tr("no_person")) self.belt_display.setStyleSheet(f""" font-size: 28px; font-weight: bold; color: {STATUS_INFO_COLOR}; padding: 25px 0; border-radius: 8px; background-color: #2a2a3a; border: 2px solid {STATUS_INFO_COLOR}; """) # 显示视频帧(包含FPS信息) rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = rgb_image.shape qt_image = QImage(rgb_image.data, w, h, ch * w, QImage.Format_RGB888) pixmap = QPixmap.fromImage(qt_image) self.video_label.setPixmap(pixmap.scaled( self.video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) def update_connection_status(self, status): """更新连接状态显示""" self.connect_label.setText(f"{tr('connection_status')}: {status}") if status == tr("connected"): self.connect_label.setStyleSheet(f""" font-size: 16px; font-weight: bold; color: {STATUS_SAFE_COLOR}; padding: 10px; background-color: #1e1e2e; border-radius: 8px; border: 2px solid {STATUS_SAFE_COLOR}; """) else: self.connect_label.setStyleSheet(f""" font-size: 16px; font-weight: bold; color: {STATUS_DANGER_COLOR}; padding: 10px; background-color: #1e1e2e; border-radius: 8px; border: 2px solid {STATUS_DANGER_COLOR}; """) def closeEvent(self, event): """关闭事件处理""" self.video_thread.stop() if hasattr(self, 'tcp_client'): self.tcp_client.stop() if hasattr(self, 'class_timer') and self.class_timer.isActive(): self.class_timer.stop() event.accept() if __name__ == "__main__": app = QApplication(sys.argv) # 设置应用字体 font = QFont("Segoe UI", 10) app.setFont(font) # 从配置文件加载初始端口 config_folder = 'C:/config' config_path = os.path.join(config_folder, 'config.txt') default_port = 7978 # 如果配置文件不存在,创建配置目录 if not os.path.exists(config_folder): os.makedirs(config_folder) # 尝试加载配置 if os.path.exists(config_path): try: with open(config_path, 'r') as f: lines = f.readlines() if len(lines) >= 2: default_port = int(lines[1].strip()) except: pass window = MainWindow(server_port=default_port) window.show() sys.exit(app.exec_())
08-21
import 'package:flutter/material.dart'; import 'package:wifi_scan/wifi_scan.dart'; import 'package:wifi_iot/wifi_iot.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import '../../../core/localization/app_localizations.dart'; import '../models/wifi.dart'; import '../viewmodels/WiFiListScreen_viewmodel.dart'; class WiFiListScreen extends StatefulWidget { const WiFiListScreen({super.key}); @override WiFiListScreenState createState() => WiFiListScreenState(); } class WiFiListScreenState extends State<WiFiListScreen> { List<WiFiAccessPoint> accessPoints = []; bool isScanning = false; late WiFiViewModel viewModel; // 已保存的网络数据 List<Map<String, String>> savedNetworks = []; // 动态的可用网络列表,将从扫描结果更新 List<Map<String, String>> availableNetworks = []; // 创建一个Map,用于存储每个SSID对应的最强信号接入点 Map<String, Map<String, String>> uniqueNetworksMap = {}; @override void initState() { super.initState(); _checkPermissionsAndStartScan(); _loadSavedNetworks(); } Future<void> _checkPermissionsAndStartScan() async { var status = await Permission.location.request(); if (status.isGranted) { _startScan(); } else { print("Location permission denied"); } } Future<void> _loadSavedNetworks() async { final vm = context.read<WiFiViewModel>(); viewModel = vm; final wifiList = await vm.getWiFi(); setState(() { // 正确转换 WiFi 对象为 Map savedNetworks = wifiList.map((wifi) { return { 'ssid': (wifi.ssid ?? '未知网络').toString(), 'status': '未连接', 'bssid': (wifi.bssid ?? '').toString(), 'level': (wifi.level?.toString() ?? '-100').toString(), // 添加信号强度 }; }).toList(); }); } Future<void> _startScan() async { setState(() => isScanning = true); // 获取扫描结果 accessPoints = await WiFiScan.instance.getScannedResults() ?? []; setState(() { for (var ap in accessPoints) { String ssid = ap.ssid.isNotEmpty ? ap.ssid : '隐藏网络'; int level = ap.level; // 如果这个SSID还没有记录,或者当前接入点的信号比已记录的强(注意:level是负数,所以大的表示信号强) if (!uniqueNetworksMap.containsKey(ssid) || level > int.parse(uniqueNetworksMap[ssid]!['level']!)) { uniqueNetworksMap[ssid] = { 'ssid': ssid, 'status': '未连接', 'level': level.toString(), 'capabilities': ap.capabilities, 'bssid': ap.bssid, 'frequency': ap.frequency.toString(), }; } } // 将Map的值转换成列表 availableNetworks = uniqueNetworksMap.values.toList(); isScanning = false; }); } // 根据信号强度获取连接状态描述 String _getConnectionStatus(int level) { if (level >= -50) return '信号强'; if (level >= -60) return '信号良好'; if (level >= -70) return '信号一般'; return '信号较弱'; } Future<void> _connectToWiFi(String ssid, String password) async { bool isConnected = await WiFiForIoTPlugin.connect( ssid, password: password, security: NetworkSecurity.WPA, ); if (isConnected) { setState(() { // 更新已保存网络的状态 for (var network in savedNetworks) { if (network['ssid'] == ssid) { network['status'] = '已连接'; } else { network['status'] = '未连接'; } } // 更新可用网络的状态 for (var network in availableNetworks) { if (network['ssid'] == ssid) { savedNetworks.add({ 'ssid': (network['ssid'] ?? '未知网络').toString(), 'status': '已连接', 'bssid': (network['bssid'] ?? '').toString(), 'level': (network['level'].toString()).toString(), // 添加信号强度 }); availableNetworks.remove(network); } } print('---------------------$savedNetworks'); }); } else { print("Failed to connect"); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 未连接状态显示 Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: const Text( '未连接', style: TextStyle(fontSize: 14, color: Colors.grey), ), ), // 设备连接标题和搜索按钮 Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '设备连接', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black, ), ), ElevatedButton( onPressed: _startScan, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 8, ), ), child: isScanning ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation<Color>( Colors.white, ), ), ) : const Text('搜索'), ), ], ), ), Expanded( child: isScanning ? const Center(child: CircularProgressIndicator()) : SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 已保存网络区域 _buildNetworkSection('已保存网络', savedNetworks), // 可用网络区域(使用真实扫描数据) _buildNetworkSection('可用网络', availableNetworks), ], ), ), ), ], ), ), ); } Widget _buildNetworkSection( String title, List<Map<String, String>> networks, ) { if (networks.isEmpty) return const SizedBox.shrink(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black54, ), ), ), // 网络列表 ...networks.map((network) => _buildNetworkItem(network)).toList(), ], ); } Widget _buildNetworkItem(Map<String, String> network) { return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), leading: _getSignalIcon(int.tryParse(network['level'] ?? '-100') ?? -100), title: Text( network['ssid']!, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.normal, color: Colors.black, ), ), subtitle: network['level'] != null ? Text( " ${_getConnectionStatus(int.tryParse(network['level'] ?? '-100') ?? -100)}", ) : null, trailing: Text( network['status']!, style: TextStyle( fontSize: 14, color: network['status'] == '已连接' ? Colors.green : Colors.grey, ), ), onTap: () { _showPasswordDialog(network['ssid']!); }, ); } // 根据信号强度显示对应的WiFi图标 Widget _getSignalIcon(int level) { Color color; IconData icon; if (level >= -50) { color = Colors.green; icon = Icons.wifi; } else if (level >= -60) { color = Colors.blue; icon = Icons.wifi; } else if (level >= -70) { color = Colors.orange; icon = Icons.wifi; } else { color = Colors.red; icon = Icons.wifi; } return Icon(icon, color: color, size: 24); } void _showPasswordDialog(String ssid) { TextEditingController passwordController = TextEditingController(); showDialog( context: context, builder: (context) => AlertDialog( title: Text("连接到 $ssid"), content: TextField( controller: passwordController, obscureText: true, decoration: const InputDecoration( labelText: '密码', border: OutlineInputBorder(), hintText: '请输入WiFi密码', ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text("取消"), ), ElevatedButton( onPressed: () { Navigator.pop(context); _connectToWiFi(ssid, passwordController.text); }, child: const Text("连接"), ), ], ), ); } } 点击可用的网络连接成功后,从可用网络上移除,并且加到已保存的网络中,页面更新
10-15
import 'package:flutter/material.dart'; import 'package:wifi_scan/wifi_scan.dart'; import 'package:wifi_iot/wifi_iot.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:network_info_plus/network_info_plus.dart'; // 新增:用于获取当前连接WiFi信息 import '../../../core/localization/app_localizations.dart'; import '../models/wifi.dart'; import '../viewmodels/WiFiListScreen_viewmodel.dart'; class WiFiListScreen extends StatefulWidget { const WiFiListScreen({super.key}); @override WiFiListScreenState createState() => WiFiListScreenState(); } class WiFiListScreenState extends State<WiFiListScreen> { List<WiFiAccessPoint> accessPoints = []; bool isScanning = false; late WiFiViewModel viewModel; final NetworkInfo _networkInfo = NetworkInfo(); // 新增:网络信息实例 // 已保存的网络数据 List<Map<String, String>> savedNetworks = []; // 动态的可用网络列表,将从扫描结果更新 List<Map<String, String>> availableNetworks = []; // 创建一个Map,用于存储每个SSID对应的最强信号接入点 Map<String, Map<String, String>> uniqueNetworksMap = {}; String? _currentConnectedSSID; // 新增:当前连接的WiFi SSID String _connectionStatus = '未连接'; // 新增:连接状态文本 @override void initState() { super.initState(); _checkPermissionsAndGetCurrentWiFi(); // 修改:先获取当前连接状态 } // 新增:检查权限并获取当前连接状态 Future<void> _checkPermissionsAndGetCurrentWiFi() async { var status = await Permission.location.request(); if (status.isGranted) { await _getCurrentConnectedWiFi(); // 获取当前连接状态 _loadSavedNetworks(); _startScan(); } else { print("Location permission denied"); } } // 新增:获取当前设备连接的WiFi信息 Future<void> _getCurrentConnectedWiFi() async { try { // 获取当前连接的WiFi名称 String? wifiName = await _networkInfo.getWifiName(); String? bssid = await _networkInfo.getWifiBSSID(); String? ipAddress = await _networkInfo.getWifiIP(); String? subnetMask = await _networkInfo.getWifiSubmask(); String? gateway = await _networkInfo.getWifiGatewayIP(); if (wifiName != null && wifiName.isNotEmpty && wifiName != "null") { // 在Android上,WiFi名称可能带有双引号,需要处理 if (wifiName.startsWith('"') && wifiName.endsWith('"')) { wifiName = wifiName.substring(1, wifiName.length - 1); } // 判断可用网络中是否包含当前连接的网络 setState(() { _currentConnectedSSID = wifiName; _connectionStatus = '已连接到 $wifiName'; // 判断已保存网络中是否包含当前连接的网络 bool isAlreadySaved = savedNetworks.any( (item) => item['ssid'] == wifiName, ); // 如果不包含,则添加到已保存网络列表 if (!isAlreadySaved) { savedNetworks.add({ 'ssid': wifiName!, 'status': '已连接', 'bssid': bssid ?? '', 'level': '-50', // 假设一个默认值 'capabilities': '', 'frequency': '0', }); } else { // 如果已包含,更新其状态为已连接 savedNetworks = savedNetworks.map((item) { if (item['ssid'] == wifiName) { return {...item, 'status': '已连接'}; } else if (item['status'] == '已连接') { return {...item, 'status': '未连接'}; } else { return item; } }).toList(); } savedNetworks = savedNetworks.map((item) { if (item['ssid'] == wifiName) { return {...item, 'status': '已连接'}; } else if (item['status'] == '已连接') { return {...item, 'status': '未连接'}; } else { return item; } }).toList(); }); } else { setState(() { _currentConnectedSSID = null; _connectionStatus = '未连接'; }); } } catch (e) { setState(() { _currentConnectedSSID = null; _connectionStatus = '未连接'; }); } } Future<void> _loadSavedNetworks() async { final vm = context.read<WiFiViewModel>(); viewModel = vm; final wifiList = await vm.getWiFi(); setState(() { savedNetworks = wifiList.map((wifi) { final ssid = wifi.ssid ?? '未知网络'; final isConnected = ssid == _currentConnectedSSID; return <String, String>{ 'ssid': ssid, 'status': isConnected ? '已连接' : '未连接', 'bssid': (wifi.bssid ?? '').toString(), 'level': (wifi.level?.toString() ?? '-100').toString(), 'capabilities': wifi.capabilities ?? '', 'frequency': (wifi.frequency?.toString() ?? '0').toString(), }; }).toList(); }); } Future<void> _startScan() async { setState(() => isScanning = true); // 获取扫描结果 accessPoints = await WiFiScan.instance.getScannedResults() ?? []; setState(() { uniqueNetworksMap.clear(); // 清空之前的记录 for (var ap in accessPoints) { String ssid = ap.ssid.isNotEmpty ? ap.ssid : '隐藏网络'; int level = ap.level; // 如果这个SSID还没有记录,或者当前接入点的信号比已记录的强 if (!uniqueNetworksMap.containsKey(ssid) || level > int.parse(uniqueNetworksMap[ssid]!['level']!)) { final isConnected = ssid == _currentConnectedSSID; uniqueNetworksMap[ssid] = { 'ssid': ssid, 'status': isConnected ? '已连接' : '未连接', 'level': level.toString(), 'capabilities': ap.capabilities, 'bssid': ap.bssid, 'frequency': ap.frequency.toString(), }; } } // 将Map的值转换成列表,并过滤掉已保存的网络(除非是当前连接的网络) final savedSSIDs = savedNetworks .map((network) => network['ssid']) .toSet(); availableNetworks = uniqueNetworksMap.values.where((network) { final ssid = network['ssid']!; // 显示未保存的网络,或者是当前连接的网络(即使已保存也要显示在可用网络中) return !savedSSIDs.contains(ssid) || ssid == _currentConnectedSSID; }).toList(); isScanning = false; }); } // 根据信号强度获取连接状态描述 String _getConnectionStatus(int level) { if (level >= -50) return '信号强'; if (level >= -60) return '信号良好'; if (level >= -70) return '信号一般'; return '信号较弱'; } // 修改连接方法,添加网络状态更新逻辑 Future<void> _connectToWiFi( Map<String, String> network, String password, ) async { final ssid = network['ssid']!; final bssid = network['bssid']!; // 尝试连接 final isConnected = await WiFiForIoTPlugin.connect( ssid, password: password, bssid: bssid, security: _getSecurity(network['capabilities'] ?? ''), ); if (isConnected) { // 更新当前连接状态 setState(() { _currentConnectedSSID = ssid; _connectionStatus = '已连接到 $ssid'; }); // 从可用网络移除 setState(() { availableNetworks.removeWhere((item) => item['bssid'] == bssid); // 更新已保存网络状态 savedNetworks = savedNetworks.map((item) { if (item['ssid'] == ssid) { return {...item, 'status': '已连接'}; } else { return {...item, 'status': '已保存'}; } }).toList(); // 如果这个网络不在已保存列表中,添加它 final isAlreadySaved = savedNetworks.any( (item) => item['ssid'] == ssid, ); if (!isAlreadySaved) { savedNetworks.add({ 'ssid': ssid, 'status': '已连接', 'bssid': bssid, 'level': network['level'] ?? '-100', 'capabilities': network['capabilities'] ?? '', 'frequency': network['frequency'] ?? '0', }); } }); // 保存到数据库 await viewModel.insertWiFi( WiFi( ssid: ssid, bssid: bssid, capabilities: network['capabilities'] ?? '', level: int.tryParse(network['level'] ?? '-100') ?? -100, frequency: int.tryParse(network['frequency'] ?? '0') ?? 0, lastConnected: DateTime.now().toIso8601String(), ), ); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('已成功连接到 $ssid'), backgroundColor: Colors.green), ); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('连接 $ssid 失败'), backgroundColor: Colors.red), ); } } // 安全类型检测 NetworkSecurity _getSecurity(String capabilities) { if (capabilities.contains('WPA2') || capabilities.contains('WPA')) { return NetworkSecurity.WPA; } if (capabilities.contains('WEP')) return NetworkSecurity.WEP; return NetworkSecurity.NONE; } // 新增:断开网络连接 Future<void> _disconnectFromWiFi(String ssid) async { bool isDisconnected = await WiFiForIoTPlugin.disconnect(); if (isDisconnected) { setState(() { _currentConnectedSSID = null; _connectionStatus = '未连接'; // 更新网络状态 for (var network in savedNetworks) { if (network['ssid'] == ssid) { network['status'] = '已保存'; } } }); ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('已断开与 $ssid 的连接'))); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 连接状态显示(动态显示当前连接状态) Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text( _connectionStatus, style: TextStyle( fontSize: 14, color: _currentConnectedSSID != null ? Colors.green : Colors.grey, ), ), ), // 设备连接标题和搜索按钮 Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '设备连接', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black, ), ), ElevatedButton( onPressed: _startScan, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 8, ), ), child: isScanning ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation<Color>( Colors.white, ), ), ) : const Text('搜索'), ), ], ), ), Expanded( child: isScanning ? const Center(child: CircularProgressIndicator()) : SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 已保存网络区域(包括已连接的) if (savedNetworks.isNotEmpty) _buildNetworkSection('已保存网络', savedNetworks, true), // 可用网络区域 if (availableNetworks.isNotEmpty) _buildNetworkSection( '可用网络', availableNetworks, false, ), // 如果没有网络显示空状态 if (savedNetworks.isEmpty && availableNetworks.isEmpty) const Center( child: Padding( padding: EdgeInsets.only(top: 100), child: Text( '未发现网络', style: TextStyle( fontSize: 16, color: Colors.grey, ), ), ), ), ], ), ), ), ], ), ), ); } Widget _buildNetworkSection( String title, List<Map<String, String>> networks, bool isSavedSection, ) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black54, ), ), ), // 网络列表 ...networks .map((network) => _buildNetworkItem(network, isSavedSection)) .toList(), ], ); } Widget _buildNetworkItem(Map<String, String> network, bool isSavedItem) { final isConnected = network['status'] == '已连接'; return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), leading: _getSignalIcon(int.tryParse(network['level'] ?? '-100') ?? -100), title: Text( network['ssid']!, style: TextStyle( fontSize: 16, fontWeight: isConnected ? FontWeight.bold : FontWeight.normal, color: isConnected ? Colors.green : Colors.black, ), ), subtitle: network['level'] != null ? Text( " ${_getConnectionStatus(int.tryParse(network['level'] ?? '-100') ?? -100)}", ) : null, trailing: isSavedItem && isConnected ? TextButton( onPressed: () => _disconnectFromWiFi(network['ssid']!), child: const Text('未连接', style: TextStyle(color: Colors.grey)), ) : Text( network['status']!, style: TextStyle( fontSize: 14, color: isConnected ? Colors.green : Colors.grey, ), ), onTap: () { if (isSavedItem && isConnected) { _disconnectFromWiFi(network['ssid']!); } else if (!isConnected) { _showPasswordDialog(network); } }, ); } // 根据信号强度显示对应的WiFi图标 Widget _getSignalIcon(int level) { Color color; IconData icon; if (level >= -50) { color = Colors.green; icon = Icons.wifi; } else if (level >= -60) { color = Colors.blue; icon = Icons.wifi; } else if (level >= -70) { color = Colors.orange; icon = Icons.wifi; } else { color = Colors.red; icon = Icons.wifi; } return Icon(icon, color: color, size: 24); } void _showPasswordDialog(Map<String, String> network) { TextEditingController passwordController = TextEditingController(); showDialog( context: context, builder: (context) => AlertDialog( title: Text("连接到 ${network['ssid']}"), content: TextField( controller: passwordController, obscureText: true, decoration: const InputDecoration( labelText: '密码', border: OutlineInputBorder(), hintText: '请输入WiFi密码', ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text("取消"), ), ElevatedButton( onPressed: () { Navigator.pop(context); _connectToWiFi(network, passwordController.text); }, child: const Text("连接"), ), ], ), ); } } 期望_networkInfo 拿到level 这些信息
10-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值