Dialog instance和Gateway Instance是做什么用的?

本文介绍了SAP系统中不同类型的实例及其作用,包括CentralInstance、DialogInstance、GatewayInstance和DatabaseInstance等。阐述了它们如何分布于不同的主机上,以及如何通过多种类型的进程支持系统的运行。

每个SAP System可能会包含N个instance,每个Instance又包含N个Process。

Centrolal Instance和其他Instance的区别在于它包含一个Enqueue Process(99.9%正确*);

Dialog Instance主要为了解决系统的负载及平衡。并不是说Dialog Instance上就只有Dialog process,也可以有其他类型的Process,但一定不会有Enqueue Process。

Gateway Instance在实际应用中使用得更少,主要是为了处理SAP系统和其他外部系统(可以是别的R/3或者CRM/BW/...甚至别的软件厂商的系统)的通讯。

Database Instance是指在一个SAP系统中,数据库服务所在的那个Instance。

以上各种Instance可以安装在同一台主机上,也可以分布在N台主机上。尤其对大型系统来说,可以有好几十台服务器,以Dialog Instance的方式提供服务,这些主机甚至可以是异构的(或NT、或Linux、或UNIX等等),SAP系统的灵活度及复杂度可见一斑。

*:大型系统中的Central Instance可能会包含最多到四个Enqueu Process(SAP推荐最多不超过四个),这是为了避免在“加锁”这个环节产生瓶颈。

#!/bin/env python # 必须写在第一行 # -*- coding: utf-8 -*- ################################################# # LOAD_MODE__ import os import re import sys from PyQt5.QtCore import Qt, QRegExp, QEvent, QThread, QTimer, QObject, pyqtSignal from PyQt5.QtWidgets import QWidget, QApplication, QMessageBox, QDialog # from ui import param from PyQt5.QtGui import QRegExpValidator from messageBox import messageBox from collections import defaultdict from py39COM import Gateway, InCAM from py39Tools import TableWidget from ICO import ICO from ICNET import ICNET from EqHelper import EqHelper # from fastmcp import Api, Resource # import tkinter as tk import requests import threading from Ui_interface import Ui_Dialog as inerfaceui from Ui_job_input import Ui_ChildWindow as job_input_ui from Ui_get_layer import Ui_ChildWindow2 as get_layer_ui from Ui_draw_circle import Ui_ChildWindow3 as draw_circle_ui import subprocess import psutil import time import netifaces import json from pypinyin import lazy_pinyin from fastapi import FastAPI import uvicorn # from img import apprcc_rc # from pprint import pprint # from sublib import # print('apprcc_rc.rcc_version : %s' % apprcc_rc.rcc_version) #第一步:在终端运行python interface.py #第二步:点击按钮确认GUI功能 #第三步:保持 interface.py 运行状态,在另一个终端中测试:curl http://127.0.0.1:8000,会返回:{"message": "Hello, World!"}类似信息 ##测试调用curl -X POST http://127.0.0.1:8000/api/open_incam,返回json信息。对其他接口同样进行测试,如:curl -X POST http://127.0.0.1:8000/api/open_job # #第四步:运行该代码,在浏览器输入:http://127.0.0.1:8000/docs # MODE_END__ def get_pinyin(chinese): if not chinese: return '' str = chinese new_str = str[0:] # print(new_str) new_str1 =''.join(lazy_pinyin(new_str)) # print(new_str1) return new_str1 def GetUserInfo(mode=None): ''' 从指定URL获取主机信息(JSON格式) 返回值:成功返回JSON对象,失败返回None ''' getHtml = 'http://sheet.scc.com/gethosts/format/json' resp = requests.get(getHtml, timeout=60) if resp.status_code == 200: json_obj = json.loads(resp.content.decode()) return json_obj else: return None def get_local_ip(): """ 获取本机 IP 地址,优先返回 10.x.x.x,其次 192.168.x.x """ ip_list = [] for interface in netifaces.interfaces(): addrs = netifaces.ifaddresses(interface) if netifaces.AF_INET in addrs: for addr_info in addrs[netifaces.AF_INET]: ip = addr_info['addr'] if ip.startswith('10.'): return ip # 立即返回第一个10.x地址 ip_list.append(ip) #找到的是192开头的ip地址则转换成10开头的ip if ip.startswith('192.168.200.'): parts = ip.split('.') new_ip = f'10.61.26.{parts[3]}' return new_ip ip_list.append(ip) # 如果没有10.x地址,返回第一个192.x地址 for ip in ip_list: if ip.startswith('192.'): return ip # 都没有则返回第一个找到的IP return ip_list[0] if ip_list else "127.0.0.1" def get_username(): try: local_ip = get_local_ip() # print(f"Local IP address: {local_ip}") user_info = GetUserInfo(local_ip) if not user_info or not isinstance(user_info, list): print("用户信息为空或格式错误。") return None res= [x for x in user_info if x['ip_address'] == local_ip] if len(res) == 0: return None username = res[0]['user'] # print(f"准备调用 SubCenterGetUserInfo,传入 username: {username}") englishname = get_pinyin(username) return englishname except Exception as e: print(f"获取用户信息失败: {e}") return None #获取当前打开的窗口的进程 def get_window_processes(): try: # 使用wmctl获取窗口列表 output = subprocess.check_output(['wmctrl', '-lp']).decode('utf-8') window_pids = [] for line in output.splitlines(): parts = line.split() if len(parts) >= 3: window_pids.append(int(parts[2])) return list(set(window_pids)) # 去重 except FileNotFoundError: print("请先安装wmctrl: sudo apt install wmctrl") return [] # 判断incam是否在运行 def is_process_running(): """ 判断InCAMPro进程是否正在运行 返回: bool: True表示进程正在运行,False表示未运行 """ window_pids = get_window_processes() if not window_pids: print("无法获取窗口进程信息") return False for proc in psutil.process_iter(['pid', 'name']): if proc.info['pid'] in window_pids and proc.info['name'].lower() == 'incampro': return True return False # 获取incam的pid def get_incampro_pid(): """ 获取InCAMPro进程的PID 返回: int/None: 返回进程PID(如果找到),否则返回None """ window_pids = get_window_processes() if not window_pids: print("无法获取窗口进程信息") return None for proc in psutil.process_iter(['pid', 'name']): if proc.info['pid'] in window_pids and proc.info['name'].lower() == 'incampro': return proc.info['pid'] # 返回找到的PID return None # 没有找到则返回None # FUNCTION__ class Child_window(QDialog): """ 料号输入子窗口 """ def __init__(self, parent=None): super().__init__(parent) self.child_ui1 = job_input_ui() self.child_ui1.setupUi(self) # 添加确认按钮信号连接 self.child_ui1.confirm_btn.clicked.connect(self.confirm_input) self.material_number = None # 存储输入的料号 def get_material_number(self): return self.child_ui1.input_job.text().strip() def confirm_input(self): """验证输入并关闭窗口""" if self.get_material_number(): self.accept() # 关闭对话框并返回QDialog.Accepted else: QMessageBox.warning(self, "错误", "请输入有效料号") class ChildWindow2(QDialog): """层名选择对话框""" def __init__(self, layer_list, parent=None): super().__init__(parent) self.child_ui2 = get_layer_ui() self.child_ui2.setupUi(self, layer_list) # 初始化下拉框 self.child_ui2.get_layer.addItems(layer_list) if layer_list: self.child_ui2.get_layer.setCurrentIndex(0) # 连接信号 self.child_ui2.btn_ok.clicked.connect(self.accept) self.child_ui2.btn_ok.clicked.connect(self.store_selection) self.selected_layer = None def store_selection(self): """存储选择的层名""" self.selected_layer = self.child_ui2.get_layer.currentText() def get_selected_layer(self): """获取选择的层名""" return self.selected_layer class ChildWindow3(QDialog): """ 画圆子窗口 """ def __init__(self, parent=None): super().__init__(parent) self.child_ui3 = draw_circle_ui() self.child_ui3.setupUi(self) # 添加确认按钮信号连接 self.child_ui3.sure_btn.clicked.connect(self.accept) self.value = None def get_circle_data(self): """获取输入的圆参数""" try: x = float(self.child_ui3.x_input.text()) y = float(self.child_ui3.y_input.text()) sym = self.child_ui3.sym_input.text() # 保持为字符串 if not sym: # 检查是否为空 return None return (x, y, sym) except ValueError: return None class ComTestQT(QWidget): """ PYQT界面实现对应按钮操作incam动作 """ def __init__(self): # def __init__(self, pid=None, JOB=None, STEP=None): super(ComTestQT, self).__init__() self.main_ui = inerfaceui() self.main_ui.setupUi(self) self.setWindowFlags(Qt.WindowStaysOnTopHint) self.JOB = os.environ.get('JOB', None) # print(f"JOB: {self.JOB}") # print("111111111111111111111111111111") self.STEP = os.environ.get('STEP', None) # --启动pycharm.sh时,里面有export INCAM_DEBUG=yes设置此环境变量 INCAM_DEBUG = os.getenv('INCAM_DEBUG', None) # 接口定义 # if INCAM_DEBUG == 'yes': # 通过genesis gateway命令连结pid进行会话,不用在genesis环境下运行,直接用gateway的方式,可在pycharm环境下直接debug self.incam = Gateway() # 方法genesis_connect通过查询log-genesis文件获取的料号名 # self.JOB = self.incam.job_name # self.STEP = self.incam.step_name # self.pid = self.incam.pid # else: # self.incam = InCAM() # self.pid = os.getpid() self.ico = ICO(incam=self.incam) # self.icNet = ICNET(incam=self.incam) # # self.eqHelper = EqHelper(self.incam, self.JOB, self.STEP) # print(f"没有debug:{self.JOB}") # self.jobName = self.ico.SimplifyJobName(jobName=self.JOB) # self.dbSite = self.ico.GetDBSite(JOB=self.JOB) # self.SITE = self.ico.GetSite(JOB=self.JOB) # layerMatrix = self.ico.GetLayerMatrix() # self.incam.COM("get_user_group") # self.uGroup = self.incam.COMANS # self.incam.COM('get_user_name') # self.userName = self.incam.COMANS # self.incam.gateway_connect() self.child_window = None self.child_window2 = None self.child_window3 = None self.slot_func() # 全局引用自己,供 FastAPI 使用 global qt_app_instance qt_app_instance = self def slot_func(self): """ 统一定义槽函数 :return: """ self.main_ui.open_incam.clicked.connect(self.start_incam) self.main_ui.open_job.clicked.connect(self.show_child_window) self.main_ui.get_layername.clicked.connect(self.show_layer_dialog) self.main_ui.draw_circle.clicked.connect(self.show_draw_circle_dialog) # 连接信号到实际方法 api_signals.open_incam_signal.connect(self.start_incam) api_signals.open_job_signal.connect(self.show_child_window) api_signals.get_layername_signal.connect(self.show_layer_dialog) api_signals.draw_circle_signal.connect(self.show_draw_circle_dialog) ########运行incam程序###### def start_incam(self): """ 尝试通过终端命令启动 incam 或 pro """ if is_process_running(): print("已有InCAMPro进程正在运行,无法再次启动") # messageBox("Incam 已在运行") return else: try: self.file_path = "/home/genesis/.incam/login" self.file_path_enc = "/home/genesis/.incam/login.enc" account = get_username()#"songwenhua"#self.inputNewAccount.text().strip() password = "scc123456"#self.inputNewPassword.text().strip() if os.path.exists(self.file_path_enc): os.remove(self.file_path_enc) with open(self.file_path, "w", encoding="utf-8") as f: f.writelines('\n'.join((account, password))) home = os.path.expanduser("~") # 获取家目录路径 # subprocess.Popen(['gnome-terminal', '--working-directory', home, '--', 'bash', '-ic', 'incam; exec bash']) subprocess.Popen(['gnome-terminal', '--working-directory', home, '--', 'bash', '-ic', 'pro; exec bash']) print("Incam 已作为后台进程启动") # while not is_process_running(): time.sleep(10) if os.path.exists(self.file_path): os.remove(self.file_path) except FileNotFoundError: print("'gnome-terminal' 未找到,请安装它:sudo apt install gnome-terminal") except Exception as e: print("启动 incam 时发生异常:", str(e)) def show_child_window(self): """显示子窗口并等待输入""" # 重置子窗口状态 if self.child_window is None: self.child_window = Child_window(self) self.child_window.child_ui1.input_job.clear() if self.child_window.exec_() == QDialog.Accepted: short_name = self.child_window.get_material_number() self.open_JOB(short_name) def ensure_incam_ready(self): """确保 incam ico 都已准备就绪""" # if self.incam is None: self.incam = Gateway() self.ico = ICO(incam=self.incam) # else: # pid = get_incampro_pid() # print("22222222222222222222222") if not hasattr(self.incam, 'pid'): print("44444") # if pid is None: if not self.incam.pid: print("pid is None") return False # if self.ico is None: # self.ico = ICO(incam=self.incam) print("kkkkkkkkk") return True def open_JOB(self, short_name=None): """ 根据料号简称打开对应的全称JOB 参数: material_number (str): 要打开的料号,如果为None则使用当前JOB 返回: bool: True表示打开成功,False表示失败 """ try: # 1. 检查InCAMPro进程 pid = get_incampro_pid() if pid is None: QMessageBox.warning(self, "警告", "请先启动InCAMPro") return False # 初始化Gateway连接 # 等待 Gateway 真正准备好 print(f"pid11: {pid}") if not self.ensure_incam_ready(): print(f"pid: {pid}") QMessageBox.warning(self, "警告", "Gateway连接失败") return False # ---------------------------------------------- # print("111111111111111111111111111111111111111") # print(f"pid: {pid}") # # if not self.incam.pid: # # self.incam.connect(pid) # print("self.incam.pid:", self.incam.pid) # print("nnnnn: ",hasattr(self.incam, 'pid')) # # self.incam.pid = get_incampro_pid() # if not hasattr(self.incam, 'pid') or not self.incam.pid: # QMessageBox.warning(self, "警告", "Gateway连接失败") # return False # 获取JOB列表并验证料号 job_list = self.ico.GetJOBlist() if not job_list: QMessageBox.warning(self, "警告", "没有可用的JOB列表") return False # 查找匹配的全称料号 full_name = None for JOB in job_list: simplified = self.ico.SimplifyJobName(jobName=JOB) if simplified == short_name: full_name = JOB break if not full_name: QMessageBox.warning(self, "错误", f"未找到匹配全称的料号: {short_name}") return False # 打开全称料号 self.incam.COM(f'open_job,job={full_name},open_win=yes') self.JOB = full_name if self.ico.isJobOpen(full_name): QMessageBox.information(self, "成功", f"已打开料号: {full_name}") # return True else: QMessageBox.warning(self, "错误", f"打开料号失败: {full_name}") # return False except Exception as e: QMessageBox.critical(self, "异常", f"处理料号时出错: {str(e)}") # return False def show_layer_dialog(self): """显示层名选择对话框""" try: # 验证前置条件 # jobName=self.JOB print(f"JOB value before check: {self.JOB}")#已经得到了准确的job名称 if not self.check_preconditions(): return # 获取层名列表 # print(f"JOB v: {self.JOB}") layer_list = self.ico.GetLayerList() if not layer_list: QMessageBox.warning(self, "警告", "没有可用的Layer列表") return # 创建并显示对话框 dialog2 = ChildWindow2(layer_list, self) if dialog2.exec_() == QDialog.Accepted: selected_layer = dialog2.get_selected_layer() self.handle_selected_layer(selected_layer) except Exception as e: QMessageBox.critical(self, "错误", f"操作失败: {str(e)}") def check_preconditions(self): """检查必要前提条件""" if get_incampro_pid() is None: QMessageBox.warning(self, "警告", "请先启动InCAMPro") return False # 尝试获取当前激活的JOB try: if self.incam.job_name: self.JOB = self.incam.job_name # print(f"从incam获取的JOB: {self.JOB}") QMessageBox.information(self, "成功", f"从incam获取的JOB: {self.JOB}") except Exception as e: # print(f"获取incam job_name失败: {str(e)}") QMessageBox.warning(f"获取incam job_name失败: {str(e)}") # 检查JOB是否有效 if not self.JOB: QMessageBox.warning(self, "警告", "请打开JOB后再操作") return False # 尝试获取layer列表验证JOB有效性 try: self.ico.JOB= self.JOB layer_list = self.ico.GetLayerList() # print(f"获取到的层列表: {layer_list}") if not layer_list: QMessageBox.warning(self, "警告", "获取到的层列表为空") return False return True except Exception as e: QMessageBox.warning(self, "警告", f"获取层列表失败: {str(e)}") return False def handle_selected_layer(self, layer_name): """处理选择的层名""" print(f"选择的层名: {layer_name}") self.LAYER = layer_name QMessageBox.information(self, "成功", f"已选择层名: {layer_name}") def show_draw_circle_dialog(self): """显示画圆参数对话框""" if not self.check_preconditions(): return if not hasattr(self, 'LAYER') or not self.LAYER: QMessageBox.warning(self, "警告", "请先选择层") return dialog3 = ChildWindow3(self) if dialog3.exec_() == QDialog.Accepted: params = dialog3.get_circle_data() # print(f"获取的参数: {params}") if params: self.draw_circle_on_layer(*params) else: QMessageBox.warning(self, "警告", "请输入有效的数字参数") def draw_circle_on_layer(self, center_x, center_y, symbol): """在指定层上画圆""" try: self.ico.ClearAll() layer_name=self.LAYER self.ico.DispWork(layer=layer_name) self.ico.AddPad( x = center_x, y = center_y, sym = symbol ) self.ico.ClearLayer() QMessageBox.information(self, "成功", f"已在层 {self.LAYER} 上绘制圆形") except Exception as e: QMessageBox.critical(self, "错误", f"画圆时出错: {str(e)}") ########## FastAPI 部分 ########### class ApiSignalEmitter(QObject): open_incam_signal = pyqtSignal() open_job_signal = pyqtSignal() get_layername_signal = pyqtSignal() draw_circle_signal = pyqtSignal() api_signals = ApiSignalEmitter() # app = FastAPI(title="InCAM Remote Control API", description="通过HTTP控制InCAM自动化任务") app = FastAPI(title="InCAM 控制 API", version="1.0") @app.post("/api/open_incam") async def api_open_incam(): # qt_app_instance.start_incam() # 不再直接调用 myapp.open_job() # 而是发送信号给主线程处理 api_signals.open_incam_signal.emit() return {"status": "success", "action": "open_incam", "message": "InCAM已启动"} @app.post("/api/open_job") async def api_open_job(): # qt_app_instance.show_child_window() api_signals.open_job_signal.emit() return {"status": "success", "action": "open_job", "message": "Job窗口已打开"} @app.post("/api/get_layername") async def api_get_layername(): # qt_app_instance.show_layer_dialog() api_signals.get_layername_signal.emit() return {"status": "success", "action": "get_layername", "message": "层名获取成功"} @app.post("/api/draw_circle") async def api_draw_circle(): # qt_app_instance.show_draw_circle_dialog() api_signals.draw_circle_signal.emit() return {"status": "success", "action": "draw_circle", "message": "圆圈绘制完成"} @app.get("/") async def root(): return { "message": "InCAM 控制 API 运行中", "endpoints": [ "/api/open_incam", "/api/open_job", "/api/get_layername", "/api/draw_circle" ], "method": "POST" } # @app.get("/") # async def root(): # if qt_app_instance is None: # return {"status": "error", "message": "Qt app not initialized"} # return { # "status": "running", # "service": "InCAM Remote Control API", # "api_docs": "/docs", # "available_endpoints": [ # "/api/open_incam", # "/api/open_job", # "/api/get_layername", # "/api/draw_circle" # ] # } def run_fastapi(): """在后台线程运行 FastAPI""" uvicorn.run(app, host="127.0.0.1", port=8000, log_level="info") # def run_fastapi(): # """在后台线程运行 FastAPI""" # config = uvicorn.Config( # app=app, # host="127.0.0.1", # port=8000, # log_level="info", # lifespan="on" # 显式开启 lifespan 支持 # ) # server = uvicorn.Server(config) # server.run() qt_app_instance = None if __name__ == '__main__': qapp = QApplication(sys.argv) myapp = ComTestQT() myapp.show() print("PyQt5 GUI 已显示,将在1秒后启动 FastAPI...") # 在子线程中启动 FastAPI QTimer.singleShot( 1000, lambda: threading.Thread(target=run_fastapi, daemon=True).start() ) sys.exit(qapp.exec_()) # EXIT__ 以上是play.py的全部代码,并没有内联Uvicorn 的启动逻辑。
最新发布
10-29
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'; import '../../../core/network/tcp_client.dart'; import '../widgets/device_connection_dialog.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'; }); } 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 ?? '', 'passord': wifi.password ?? '', '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; // 如果是当前已经连接的网络,判断是否已经在已保存列表中 if (isConnected) { final isAlreadySaved = savedNetworks.any( (item) => item['ssid'] == ssid, ); if (!isAlreadySaved) { savedNetworks.add({ 'ssid': ssid, 'status': '已连接', 'bssid': ap.bssid, 'level': level.toString(), 'capabilities': ap.capabilities, 'passord': '', 'frequency': ap.frequency.toString(), }); // 从可用网络中移除 availableNetworks.removeWhere((item) => item['ssid'] == ssid); // 存入数据库 viewModel.insertWiFi( WiFi( ssid: ssid, bssid: ap.bssid ?? '', capabilities: ap.capabilities ?? '', level: level, frequency: ap.frequency ?? 0, password: '', // 因为获取不多到密码,暂时存空 lastConnected: DateTime.now().toIso8601String(), ), ); } continue; // 已处理,跳过后续逻辑 } 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'; }); // 如果这个网络不在已保存列表中,添加它 final isAlreadySaved = savedNetworks.any((item) => item['ssid'] == ssid); // 从可用网络移除 setState(() { availableNetworks.removeWhere((item) => item['bssid'] == bssid); // 更新已保存网络状态 savedNetworks = savedNetworks.map((item) { if (item['ssid'] == ssid) { return {...item, 'status': '已连接'}; } else { return {...item, 'status': '未连接'}; } }).toList(); if (!isAlreadySaved) { savedNetworks.add({ 'ssid': ssid, 'status': '已连接', 'bssid': bssid, 'level': network['level'] ?? '-100', 'capabilities': network['capabilities'] ?? '', 'frequency': network['frequency'] ?? '0', }); } }); if (!isAlreadySaved) { // 保存到数据库 await viewModel.insertWiFi( WiFi( ssid: ssid, bssid: bssid, capabilities: network['capabilities'] ?? '', level: int.tryParse(network['level'] ?? '-100') ?? -100, password: password, frequency: int.tryParse(network['frequency'] ?? '0') ?? 0, lastConnected: DateTime.now().toIso8601String(), ), ); } if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('已成功连接到 $ssid'), backgroundColor: Colors.green, ), ); } } else { if (mounted) { 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'] = '已保存'; } } }); if (mounted) { 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: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( _connectionStatus, style: TextStyle( fontSize: 14, color: _currentConnectedSSID != null ? Colors.green : Colors.grey, ), ), IconButton( icon: const Icon(Icons.settings_ethernet, size: 32), color: Colors.blue, onPressed: _showDeviceConnectionDialog, ), ], ), ), // 设备连接标题搜索按钮 // 在设备连接标题区域的Row中替换按钮代码: 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, ), ), IconButton( // 替换为图标按钮 onPressed: _startScan, icon: isScanning ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation<Color>( Colors.blue, ), ), ) : const Icon(Icons.refresh, color: Colors.blue), tooltip: '刷新WiFi列表', // 添加提示文本 ), ], ), ), Expanded( child: isScanning ? const Center(child: CircularProgressIndicator()) : SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 已保存网络区域(包括已连接的) _buildNetworkSection('已保存网络', savedNetworks, true), // 可用网络区域 _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: Text( network['status']!, style: TextStyle( fontSize: 14, color: isConnected ? Colors.green : Colors.grey, ), ), onTap: () { if (isSavedItem && isConnected) { _disconnectFromWiFi(network['ssid']!); } else if (isSavedItem && !isConnected) { if (network['passord'] != null && network['passord']!.isNotEmpty) { _connectToWiFi(network, network['passord']!); } else { _showPasswordDialog(network); } } 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( titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 10), // 调整标题内边距 contentPadding: const EdgeInsets.symmetric( horizontal: 24, vertical: 10, ), // 调整内容内边距 actionsPadding: const EdgeInsets.fromLTRB(24, 10, 24, 16), // 调整按钮内边距 title: Text( "连接到 ${network['ssid']}", style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), content: TextField( controller: passwordController, obscureText: true, style: const TextStyle(fontSize: 14), decoration: const InputDecoration( labelText: '密码', labelStyle: TextStyle(fontSize: 14), border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(10)), borderSide: BorderSide(color: Colors.grey), ), contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14), hintText: '请输入WiFi密码', hintStyle: TextStyle(fontSize: 14), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), style: TextButton.styleFrom(foregroundColor: Colors.grey[700]), child: const Text('取消', style: TextStyle(fontSize: 14)), ), ElevatedButton( onPressed: () { Navigator.pop(context); _connectToWiFi(network, passwordController.text); }, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).primaryColor, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), ), child: const Text( '连接', style: TextStyle(fontSize: 14, color: Colors.white), ), ), ], ), ); } void _showDeviceConnectionDialog() { TextEditingController ipController = TextEditingController(); TextEditingController portController = TextEditingController(); showDialog( context: context, builder: (context) => DeviceConnectionDialog( ipController: ipController, portController: portController, onCancel: () { Navigator.pop(context); ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('连接已取消'))); }, onConfirm: () { _connectToDevice(ipController.text, portController.text); Navigator.pop(context); }, ), ); } // 设备连接逻辑 void _connectToDevice(String ip, String port) { if (ip.isEmpty || port.isEmpty) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('请填写IP地址端口号'))); return; } viewModel.connectTcp(host: ip, port: int.tryParse(port) ?? 0); } } 改成comsuer的方式包裹
10-17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值