import subprocess import re import time import requests import json from appium import webdriver from appium.webdriver.common.appiumby import AppiumBy from appium.options.android import UiAutomator2Options from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from urllib3.connection import port_by_scheme # ======================= # 基础工具函数 # ======================= def run_command(command): """执行系统命令并返回输出""" result = subprocess.run(command, shell=True, capture_output=True, text=True) return result.stdout.strip() # ======================= # API请求函数 # ======================= def get_new_port(yh_id): """从服务器获取新的端口号""" url = "https://engineering.yuxindazhineng.com/index/index/get_yh_port" headers = { "Content-Type": "application/x-www-form-urlencoded" } data = { "yh_id": yh_id } try: print(f"🔍 查询服务器新端口号,用户ID: {yh_id}") response = requests.post(url, headers=headers, data=data, timeout=10) if response.status_code == 200: result = response.json() if result.get("code") == 0: print(f"✅ 查询成功,新端口号: {result.get('data', '未知')}") return result.get("data", None) else: print(f"❌ 查询失败: {result.get('msg', '未知错误')}") return None else: print(f"❌ 服务器响应错误: {response.status_code}") return None except requests.exceptions.RequestException as e: print(f"❌ 网络请求失败: {e}") return None def get_accounts_from_server(yh_id): """从服务器获取账户信息""" url = "http://www.yuxindazhineng.com:3002/api/accounts/get_uplaod_data" headers = { "Content-Type": "application/json" } data = { "yh_id": yh_id } try: print(f"🔍 查询服务器账户信息,用户ID: {yh_id}") response = requests.post(url, headers=headers, json=data, timeout=10) if response.status_code == 200: result = response.json() if result.get("code") == 0: print(f"✅ 查询成功,找到 {result.get('total', 0)} 个账户") return result.get("data", []) else: print(f"❌ 查询失败: {result.get('message', '未知错误')}") return [] else: print(f"❌ 服务器响应错误: {response.status_code}") return [] except requests.exceptions.RequestException as e: print(f"❌ 网络请求失败: {e}") return [] except json.JSONDecodeError as e: print(f"❌ JSON解析失败: {e}") return [] def update_device_info(account_id, device_name, device_port, device_ip): """更新设备信息到服务器""" url = "http://www.yuxindazhineng.com:3002/api/accounts/update" headers = { "Content-Type": "application/json" } data = { "account_id": str(account_id), "account_data": { "device_name": str(device_name), "device_port": str(device_port), "device_ip": str(device_ip) } } try: print(f"🔄 更新设备信息,账户ID: {account_id}") print(f" 设备信息: 名称={device_name}, 端口={device_port}, IP={device_ip}") response = requests.post(url, headers=headers, json=data, timeout=10) if response.status_code == 200: result = response.json() if result.get("code") == 0: print(f"✅ 更新成功: {result.get('message', '未知信息')}") return True else: print(f"❌ 更新失败: {result.get('message', '未知错误')}") return False else: print(f"❌ 服务器响应错误: {response.status_code}") return False except requests.exceptions.RequestException as e: print(f"❌ 网络请求失败: {e}") return False # ======================= # Appium 启动 # ======================= def start_appium(): appium_port = 4723 print(f"🚀 启动 Appium Server(端口 {appium_port})...") subprocess.Popen( ["appium.cmd", "-a", "127.0.0.1", "-p", str(appium_port)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) # 检查端口是否就绪(替代固定sleep) max_wait = 30 # 最大等待30秒 start_time = time.time() while time.time() - start_time < max_wait: try: # 尝试连接Appium端口,验证是否就绪 import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(1) result = sock.connect_ex(("127.0.0.1", appium_port)) sock.close() if result == 0: # 端口就绪 print(f"✅ Appium Server 启动成功(端口 {appium_port})") return True except Exception: pass time.sleep(1) print(f"❌ Appium Server 启动超时({max_wait}秒)") return False # ======================= # 启动沉降观测 App # ======================= def start_settlement_app(device_id, device_ip, device_port): print(f"📱 使用 Appium 连接设备: {device_id}") options = UiAutomator2Options() options.platform_name = "Android" options.device_name = device_id options.udid = device_id # ⚠️ TODO:替换为你的真实信息 options.app_package = "com.bjjw.cjgc" options.app_activity = ".activity.LoginActivity" options.automation_name = "UiAutomator2" options.no_reset = True options.auto_grant_permissions = True options.new_command_timeout = 28800 # 超时增强(无线 ADB 必须) options.set_capability("uiautomator2ServerLaunchTimeout", 60000) options.set_capability("adbExecTimeout", 120000) driver = webdriver.Remote( "http://127.0.0.1:4723", options=options ) # 使用ADB命令启动Activity try: adb_command = f"adb -s {device_id} shell am start -n com.bjjw.cjgc/.activity.LoginActivity" result = subprocess.run(adb_command, shell=True, capture_output=True, text=True) if result.returncode == 0: time.sleep(1) # 等待Activity启动 except Exception: return False print("✅ 沉降观测 App 已成功启动") # ======================= # 获取用户名文本框内容 # ======================= app_username = None try: print("🔍 尝试获取用户名文本框内容...") # 创建显式等待对象 wait = WebDriverWait(driver, 15) # 最多等待15秒 # 等待用户名文本框可点击 username_field = wait.until( EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/et_user_name")) ) # 获取文本框中的文本内容 app_username = username_field.text # 如果文本框是空的,尝试使用get_attribute获取文本 if not app_username: app_username = username_field.get_attribute("text") # 如果还是空的,尝试获取其他属性 if not app_username: app_username = username_field.get_attribute("content-desc") or username_field.get_attribute("label") if app_username: print(f"✅ 成功获取到用户名: {app_username}") else: print("⚠️ 用户名文本框为空") except Exception as e: print(f"❌ 获取用户名失败: {e}") return driver, app_username # ======================= # 无线 ADB 建链主流程 # ======================= def setup_adb_wireless(yh_id="68ef0e02b0138d25e2ac9918"): port = get_new_port(yh_id) # port = 3435 print(f"🚀 开始无线 ADB 建链(端口 {port})") print(f"📋 用户ID: {yh_id}") # 从服务器获取账户信息 accounts = get_accounts_from_server(yh_id) if not accounts: print("❌ 未从服务器获取到账户信息,终止流程") return devices_output = run_command("adb devices") lines = devices_output.splitlines()[1:] usb_devices = [] for line in lines: if not line.strip(): continue device_id = line.split()[0] # 跳过已经是无线的 if ":" in device_id: continue usb_devices.append(device_id) if not usb_devices: print("❌ 未检测到 USB 设备") return for serial in usb_devices: print(f"\n🔎 处理设备: {serial}") # 获取 WLAN IP ip_info = run_command(f"adb -s {serial} shell ip addr show wlan0") ip_match = re.search(r'inet\s+(\d+\.\d+\.\d+\.\d+)', ip_info) if not ip_match: print("⚠️ 获取 IP 失败,请确认已连接 WiFi") continue device_ip = ip_match.group(1) print(f"📍 设备 IP: {device_ip}") # 切 TCP 模式 run_command(f"adb -s {serial} tcpip {port}") time.sleep(2) # 无线连接 connect_result = run_command(f"adb connect {device_ip}:{port}") if "connected" not in connect_result.lower(): print(f"❌ 无线连接失败: {connect_result}") continue wireless_id = f"{device_ip}:{port}" print(f"✅ 无线 ADB 成功: {wireless_id}") # ===== 后续自动化 ===== start_appium() driver, app_username = start_settlement_app(wireless_id, device_ip, port) if not app_username: print("⚠️ 未获取到App中的用户名,跳过服务器更新") continue # 在账户列表中查找匹配的用户名 matched_account = None for account in accounts: if account.get("username") == app_username: matched_account = account break if not matched_account: print(f"❌ 未找到与用户名 '{app_username}' 匹配的账户") continue print(f"✅ 找到匹配账户: {matched_account.get('cl_name')} ({matched_account.get('username')})") print(f" account_id: {matched_account.get('account_id')}") # 更新设备信息到服务器 device_name = serial # 使用设备序列号作为设备名称 # 构建更新数据 update_data = { "account_id": matched_account.get("account_id"), "device_name": device_name, "device_port": port, "device_ip": device_ip } success = update_device_info( account_id=matched_account.get("account_id"), device_name=device_name, device_port=port, device_ip=device_ip ) if success: print(f"🎉 所有操作完成! 账户 {matched_account.get('username')} 的设备信息已更新") else: print(f"⚠️ 设备信息更新失败,但无线连接和App启动已完成") # 关闭Appium连接 if driver: print("🔄 关闭Appium连接...") driver.quit() break # 处理完第一个设备后退出,如需处理多个设备可移除此行 # ======================= # 程序入口 # ======================= if __name__ == "__main__": # 配置参数 USER_ID = "68ef0e02b0138d25e2ac9918" # 替换为实际的用户ID setup_adb_wireless(USER_ID)