first commit

This commit is contained in:
2026-02-02 11:47:53 +08:00
commit cc59e8b8da
35 changed files with 6733 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

88
globals/alarm.py Normal file
View File

@@ -0,0 +1,88 @@
import subprocess
import os
import logging
def push_audio_to_phone(device_id, local_audio_path, phone_audio_path): # 移除默认值
"""
将本地音频文件推送到手机指定路径
"""
# 检查本地文件是否存在
if not os.path.exists(local_audio_path):
logging.error(f"本地音频文件不存在: {local_audio_path}")
return False
# 执行ADB推送命令
cmd = ["adb", "-s", device_id, "push", local_audio_path, phone_audio_path]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode == 0:
logging.info(f"音频文件已推送到手机: {phone_audio_path}")
return True
else:
logging.error(f"推送音频文件失败: {result.stderr.strip()}")
return False
except subprocess.TimeoutExpired:
logging.error("推送音频文件超时")
return False
def play_audio_on_phone(device_id, phone_audio_path):
"""
控制手机播放指定路径的音频文件
"""
# 检查文件是否存在于手机
check_cmd = ["adb", "-s", device_id, "shell", "ls", phone_audio_path]
check_result = subprocess.run(check_cmd, capture_output=True, text=True)
if check_result.returncode != 0:
logging.error(f"音频文件在手机上不存在: {phone_audio_path}")
return False
# 使用am命令播放音频
cmd = [
"adb", "-s", device_id, "shell",
"am", "start",
"-a", "android.intent.action.VIEW",
"-t", "audio/*",
"-d", f"file://{phone_audio_path}"
]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
if result.returncode == 0:
logging.info(f"已成功触发手机播放音频: {phone_audio_path}")
return True
else:
logging.error(f"播放音频失败: {result.stderr.strip()}")
return False
except subprocess.TimeoutExpired:
logging.error("播放音频命令超时")
return False
def play_system_alarm(device_id):
"""
播放系统内置的警报声
"""
try:
# 方法1使用系统铃声URI
cmd = [
"adb", "-s", device_id, "shell",
"am", "start",
"-a", "android.intent.action.VIEW",
"-d", "content://settings/system/alarm_alert"
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
if result.returncode == 0:
logging.info("成功触发系统警报声")
return True
else:
logging.error(f"播放系统警报失败: {result.stderr.strip()}")
return False
except Exception as e:
logging.error(f"播放系统警报时出错: {str(e)}")
return False

479
globals/apis.py Normal file
View File

@@ -0,0 +1,479 @@
import requests
import json
import logging
import socket
from typing import Optional, Dict, Any
import globals.global_variable as global_variable
def send_tcp_command(command="StartMultiple", host="127.0.0.1", port=8888, timeout=10):
"""
使用TCP协议发送命令到指定地址和端口
参数:
command: 要发送的命令字符串(默认:"StartMultiple"
host: 目标主机地址(默认:"127.0.0.1"
port: 目标端口默认8888
timeout: 连接超时时间默认10
返回:
成功返回服务器响应字符串失败返回None
"""
# 创建TCP套接字
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
try:
# 设置超时时间
sock.settimeout(timeout)
# 连接到目标服务器
sock.connect((host, port))
logging.info(f"已成功连接到 {host}:{port}")
# 发送命令注意需要根据服务器要求的编码格式发送这里用UTF-8
sock.sendall(command.encode('utf-8'))
logging.info(f"已发送命令: {command}")
# 接收服务器响应缓冲区大小1024字节可根据实际情况调整
response = sock.recv(1024)
if response:
response_str = response.decode('utf-8')
logging.info(f"收到响应: {response_str}")
return response_str
else:
logging.info("未收到服务器响应")
return None
except ConnectionRefusedError:
logging.info(f"连接被拒绝,请检查 {host}:{port} 是否开启服务")
return None
except socket.timeout:
logging.info(f"连接超时({timeout}秒)")
return None
except Exception as e:
logging.info(f"发送命令时发生错误: {str(e)}")
return None
def get_breakpoint_list():
"""
获取需要处理的断点列表
"""
# 请求参数
params = {
'user_name': global_variable.GLOBAL_USERNAME
}
# 请求地址
url = "https://engineering.yuxindazhineng.com/index/index/get_name_all"
try:
# 发送GET请求
response = requests.get(url, params=params, timeout=30)
# 检查请求是否成功
if response.status_code == 200:
result = response.json()
# 检查接口返回状态
if result.get('code') == 0:
data = result.get('data', [])
logging.info("成功获取断点列表,数据条数:", len(data))
# 打印断点信息
# for item in data:
# logging.info(f"线路编码: {item.get('line_num')}, "
# f"线路名称: {item.get('line_name')}, "
# f"状态: {item.get('status')}, "
# f"用户: {item.get('name')}")
return data
else:
logging.info(f"接口返回错误: {result.get('code')}")
return [{"id": 37,
"user_name": "wangshun",
"name": "wangshun",
"line_num": "L193588",
"line_name": "CDWZQ-2标-155号路基左线-461221-461570-155左-平原",
"status": 3
}]
else:
logging.info(f"请求失败,状态码: {response.status_code}")
return []
except requests.exceptions.RequestException as e:
logging.info(f"请求异常: {e}")
return []
except ValueError as e:
logging.info(f"JSON解析错误: {e}")
return []
def get_measurement_task():
"""
获取测量任务
返回: 如果有状态为1的数据返回任务信息否则返回None
"""
try:
url = "https://engineering.yuxindazhineng.com/index/index/getOne"
# 获取用户名
user_name = global_variable.GLOBAL_USERNAME
if not user_name:
logging.error("未设置用户名,无法获取测量任务")
return None
# 构造请求参数
data = {
"user_name": user_name
}
logging.info(f"请求参数: user_name={user_name}")
response = requests.post(url, data=data, timeout=10)
response.raise_for_status()
data = response.json()
logging.info(f"接口返回数据: {data}")
if data.get('code') == 0 and data.get('data'):
task_data = data['data']
if task_data.get('status') == 1:
logging.info(f"获取到测量任务: {task_data}")
return task_data
else:
logging.info("获取到的任务状态不为1不执行测量")
return None
else:
logging.warning("未获取到有效任务数据")
return None
except Exception as e:
logging.error(f"获取测量任务失败: {str(e)}")
return None
def get_end_with_num():
"""
根据线路编码获取测量任务
返回: 如果有状态为1的数据返回任务信息否则返回None
"""
try:
url = "https://engineering.yuxindazhineng.com/index/index/getOne3"
# 获取用户名
user_name = global_variable.GLOBAL_USERNAME
line_num = global_variable.GLOBAL_LINE_NUM
if not line_num:
logging.error("未设置线路编码,无法获取测量任务")
return None
if not user_name:
logging.error("未设置用户名,无法获取测量任务")
return None
# 构造请求参数
data = {
"user_name": user_name,
"line_num": line_num
}
# logging.info(f"请求参数: user_name={user_name}, line_num={line_num}")
response = requests.post(url, data=data, timeout=10)
response.raise_for_status()
data = response.json()
logging.info(f"接口返回数据: {data}")
if data.get('code') == 0 and data.get('data'):
task_data = data['data']
if task_data.get('status') == 3:
logging.info(f"获取到测量任务: {task_data}")
return task_data
else:
logging.info("获取到的任务状态不为3不执行测量")
return None
else:
# logging.warning("未获取到有效任务数据")
return None
except Exception as e:
logging.error(f"获取测量任务失败: {str(e)}")
return None
def get_work_conditions_by_linecode(linecode: str) -> Optional[Dict[str, Dict]]:
"""
通过线路编码获取工况信息
Args:
linecode: 线路编码,如 "L118134"
Returns:
返回字典,格式为 {point_id: {"sjName": "", "workinfoname": "", "work_type": ""}}
如果请求失败返回None
"""
url="http://www.yuxindazhineng.com:3002/api/comprehensive_data/get_settlement_by_linecode"
max_retries = 3 # 最大重试次数
retry_count = 0 # 当前重试计数
while retry_count < max_retries:
try:
# 准备请求参数
payload = {"linecode": linecode}
headers = {
'Content-Type': 'application/json',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
logging.info(f"发送POST请求到: {url}")
logging.info(f"请求参数: {payload}")
# 发送POST请求
response = requests.post(
url,
json=payload,
headers=headers,
timeout=30
)
# 检查响应状态
if response.status_code != 200:
logging.error(f"HTTP请求失败状态码: {response.status_code}")
retry_count += 1
if retry_count < max_retries:
logging.info(f"准备重试... (剩余 {max_retries - retry_count} 次)")
continue # 继续重试
# 解析响应数据
try:
result = response.json()
except json.JSONDecodeError as e:
logging.error(f"JSON解析失败: {str(e)}")
retry_count += 1
if retry_count < max_retries:
logging.info(f"准备重试... (剩余 {max_retries - retry_count} 次)")
continue # 继续重试
# 检查API返回码
if result.get('code') != 0:
logging.error(f"API返回错误: {result.get('message', '未知错误')}")
return None
# 提取数据
data_list = result.get('data', [])
if not data_list:
logging.warning("未找到工况数据")
return {}
# 处理数据,提取所需字段
work_conditions = {}
for item in data_list:
point_id = item.get('aname')
if point_id:
work_conditions[point_id] = {
"sjName": item.get('sjName', ''),
"workinfoname": item.get('next_workinfo', ''),
"work_type": item.get('work_type', '')
}
logging.info(f"成功提取 {len(work_conditions)} 个测点的工况信息")
return work_conditions
except requests.exceptions.RequestException as e:
logging.error(f"网络请求异常: {str(e)}")
retry_count += 1
if retry_count < max_retries:
logging.info(f"准备重试... (剩余 {max_retries - retry_count} 次)")
except json.JSONDecodeError as e:
logging.error(f"JSON解析失败: {str(e)}")
retry_count += 1
if retry_count < max_retries:
logging.info(f"准备重试... (剩余 {max_retries - retry_count} 次)")
except Exception as e:
logging.error(f"获取工况信息时发生未知错误: {str(e)}")
retry_count += 1
if retry_count < max_retries:
logging.info(f"准备重试... (剩余 {max_retries - retry_count} 次)")
# 达到最大重试次数仍失败
logging.error(f"已达到最大重试次数 ({max_retries} 次),请求失败")
return None
def get_user_max_variation(username: str) -> Optional[int]:
"""
调用POST接口根据用户名获取用户的max_variation信息
Args:
username: 目标用户名,如 "chzq02-02guoyu"
Returns:
成功返回用户的max_variation整数值
失败返回None
"""
# 接口基础配置
api_url = "http://www.yuxindazhineng.com:3002/api/accounts/get"
timeout = 30 # 超时时间(避免请求长时间阻塞)
# 1. 准备请求参数与头部
# 接口要求的POST参数JSON格式
payload = {"username": username}
# 请求头部指定JSON格式模拟浏览器UA避免被接口拦截
headers = {
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
try:
# 2. 发送POST请求
response = requests.post(
url=api_url,
json=payload, # 自动将字典转为JSON字符串无需手动json.dumps()
headers=headers,
timeout=timeout
)
# 3. 检查HTTP响应状态200表示请求成功到达服务器
response.raise_for_status() # 若状态码非200如404、500直接抛出HTTPError
logging.info(f"接口请求成功HTTP状态码{response.status_code}")
# 4. 解析JSON响应处理文档中提到的"网页解析失败"风险)
try:
response_data = response.json()
except json.JSONDecodeError as e:
logging.error(f"接口返回数据非JSON格式解析失败{str(e)}")
logging.error(f"接口原始返回内容:{response.text[:500]}") # 打印前500字符便于排查
return None
# 5. 检查接口业务逻辑是否成功(按需求中"code=0表示查询成功"
if response_data.get("code") != 0:
logging.error(f"接口查询失败,业务错误信息:{response_data.get('message', '未知错误')}")
return None
# 6. 验证返回数据结构并提取max_variation
data_list = response_data.get("data", [])
if not data_list:
logging.warning(f"查询到用户名 {username},但未返回账号数据")
return None
# 检查第一条数据是否包含max_variation
first_user = data_list[0]
if "max_variation" not in first_user:
logging.warning(f"用户 {username} 的返回数据中缺少 max_variation 字段")
return None
max_variation = first_user["max_variation"]
logging.info(f"成功查询到用户 {username} 的 max_variation{max_variation}")
# 7. 直接返回max_variation的值
return max_variation
# 处理请求过程中的异常(网络问题、超时等)
except requests.exceptions.RequestException as e:
logging.error(f"接口请求异常(网络/超时/服务器不可达):{str(e)}")
# 若为连接错误,提示检查文档中提到的"不支持的网页类型"或域名有效性
if "ConnectionRefusedError" in str(e) or "Failed to establish a new connection" in str(e):
logging.error(f"建议排查1. 接口域名 {api_url} 是否可访问2. 服务器是否正常运行3. 端口3002是否开放")
return None
# 处理其他未知异常
except Exception as e:
logging.error(f"获取用户 {username} 的 max_variation 时发生未知错误:{str(e)}")
return None
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
def get_line_info_and_save_global(user_name: str) -> bool:
"""
调用get_name_all接口提取status=3的line_num和line_name存入全局字典
:param user_name: 接口请求参数,如"wangshun"
:return: 执行成功返回True失败/异常返回False
"""
# 接口基础配置
api_url = "https://engineering.yuxindazhineng.com/index/index/get_name_all"
request_params = {"user_name": user_name} # GET请求参数
timeout = 10 # 请求超时时间(秒),避免卡进程
try:
# 1. 发送GET请求
response = requests.get(
url=api_url,
params=request_params, # GET参数用params传递自动拼接到URL后规范且防乱码
timeout=timeout,
verify=False # 禁用SSL验证适配HTTPS接口
)
# 2. 校验HTTP状态码先确保请求本身成功
if response.status_code != 200:
logging.error(f"接口请求失败HTTP状态码异常{response.status_code},响应内容:{response.text}")
return False
# 3. 解析JSON响应接口返回是JSON格式需解析为字典
try:
response_data = response.json()
except Exception as e:
logging.error(f"接口返回内容非合法JSON无法解析{response.text},错误:{str(e)}")
return False
# 4. 校验业务状态码接口约定code=0成功-1失败
business_code = response_data.get("code")
if business_code == 0:
logging.info("接口业务请求成功,开始解析数据")
elif business_code == -1:
logging.error(f"接口业务请求失败业务状态码code=-1返回数据{response_data}")
return False
else:
logging.warning(f"接口返回未知业务状态码:{business_code},请确认接口文档")
return False
# 5. 提取data字段校验数据是否存在
api_data_list = response_data.get("data")
if not api_data_list:
logging.warning("接口业务成功但data字段为空或无数据")
return False
# 6. 校验data是否为列表类型
if not isinstance(api_data_list, list):
logging.error(f"data字段不是列表类型实际类型{type(api_data_list)},内容:{api_data_list}")
return False
found_valid_data = False
# 7. 遍历列表提取所有status=3的数据
for item in api_data_list:
# 确保每个item是字典
if not isinstance(item, dict):
logging.warning(f"列表中的元素不是字典类型,跳过:{item}")
continue
# 获取字段值
data_status = item.get("status")
line_num = item.get("line_num")
line_name = item.get("line_name")
# 校验status是否为3且目标字段非空
if data_status == 3 and line_num and line_name:
# # 存入全局字典key=line_numvalue=line_name
# global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT[line_num] = line_name
# 存入全局字典key=line_namevalue=line_num
global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT[line_name] = line_num
# 如果line_name不在列表中则添加
if line_name not in global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST:
global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST.append(line_name)
logging.info(f"找到status=3的线路信息line_num={line_num}, line_name={line_name}")
found_valid_data = True
if found_valid_data:
logging.info(f"成功提取所有status=3的线路信息当前全局字典数据{global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT}")
return True
else:
logging.warning("data列表中未找到任何status=3且字段完整的线路信息")
return False
# 捕获所有请求相关异常(超时、连接失败、网络异常等)
except requests.exceptions.Timeout:
logging.error(f"调用get_name_all接口超时超时时间{timeout}秒,请求参数:{request_params}")
return False
except requests.exceptions.ConnectionError:
logging.error(f"调用get_name_all接口连接失败检查网络或接口地址是否正确{api_url}")
return False
except Exception as e:
logging.error(f"调用get_name_all接口时发生未知异常{str(e)}", exc_info=True) # exc_info=True打印异常堆栈方便排查
return False

763
globals/driver_utils.py Normal file
View File

@@ -0,0 +1,763 @@
import logging
import time
import subprocess
import traceback
import socket
import os
import requests
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from appium.webdriver.appium_service import AppiumService
from appium.options.android import UiAutomator2Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException, InvalidSessionIdException, WebDriverException
import globals.global_variable as global_variable
def init_appium_driver(device_id, app_package="com.bjjw.cjgc", app_activity=".activity.LoginActivity"):
"""
初始化Appium驱动的全局函数
参数:
device_id: 设备ID
app_package: 应用包名,默认为"com.bjjw.cjgc"
app_activity: 应用启动Activity默认为".activity.LoginActivity"
返回:
(driver, wait): (WebDriver实例, WebDriverWait实例),如果初始化失败则抛出异常
"""
logging.info(f"设备 {device_id} 开始初始化Appium驱动")
# 创建并配置Appium选项
options = UiAutomator2Options()
options.platform_name = "Android"
options.device_name = device_id
options.app_package = app_package
options.app_activity = app_activity
options.automation_name = "UiAutomator2"
options.no_reset = True
options.auto_grant_permissions = True
options.new_command_timeout = 28800
options.udid = device_id
# 增加uiautomator2服务器启动超时时间
options.set_capability('uiautomator2ServerLaunchTimeout', 60000) # 60秒
# 增加连接超时设置
options.set_capability('connection_timeout', 120000) # 120秒
try:
# 连接Appium服务器
driver_url = "http://127.0.0.1:4723/wd/hub"
logging.info(f"设备 {device_id} 正在连接Appium服务器: {driver_url}")
driver = webdriver.Remote(driver_url, options=options)
logging.info(f"设备 {device_id} Appium服务器连接成功")
# 初始化等待对象
wait = WebDriverWait(driver, 20)
logging.info(f"设备 {device_id} WebDriverWait初始化成功")
# 等待应用稳定
time.sleep(2)
logging.info(f"设备 {device_id} Appium驱动初始化完成")
return driver, wait
except Exception as e:
logging.error(f"设备 {device_id} 初始化驱动失败: {str(e)}")
logging.error(f"错误类型: {type(e).__name__}")
logging.error(f"错误堆栈: {traceback.format_exc()}")
# 如果驱动已创建,尝试关闭
if 'driver' in locals() and driver:
try:
driver.quit()
except:
pass
raise
def check_session_valid(driver, device_id=None):
"""
检查当前会话是否有效
参数:
driver: WebDriver实例
device_id: 设备ID可选
返回:
bool: 会话有效返回True否则返回False
"""
if device_id is None:
device_id = global_variable.GLOBAL_DEVICE_ID
device_str = f"设备 {device_id} " if device_id else ""
if not driver:
logging.debug(f"{device_str}驱动实例为空")
return False
try:
# 首先检查driver是否有session_id属性
if not hasattr(driver, 'session_id') or not driver.session_id:
logging.debug(f"{device_str}驱动缺少有效的session_id")
return False
# 尝试获取当前上下文
current_context = driver.current_context
logging.debug(f"{device_str}会话检查通过,当前上下文: {current_context}")
return True
except InvalidSessionIdException:
logging.debug(f"{device_str}会话已失效")
return False
except WebDriverException as e:
error_msg = str(e).lower()
# 明确的会话失效错误
if any(phrase in error_msg for phrase in [
"session is either terminated or not started",
"could not proxy command to the remote server",
"socket hang up",
"connection refused",
"max retries exceeded"
]):
logging.debug(f"{device_str}会话连接错误: {error_msg[:100]}")
return False
else:
logging.debug(f"{device_str}WebDriver异常但可能不是会话失效: {error_msg[:100]}")
return True
except (ConnectionError, ConnectionRefusedError, ConnectionResetError) as e:
logging.debug(f"{device_str}网络连接错误: {str(e)}")
return False
except Exception as e:
error_msg = str(e)
# 检查是否是连接相关错误
if any(phrase in error_msg.lower() for phrase in [
"10054", "10061", "connection", "connect", "refused", "urllib3"
]):
logging.debug(f"{device_str}连接相关异常: {error_msg[:100]}")
return False
else:
logging.debug(f"{device_str}检查会话时出现其他异常: {error_msg[:100]}")
return True # 对于真正的未知异常保守返回True
def reconnect_driver(device_id, old_driver=None, app_package="com.bjjw.cjgc", app_activity=".activity.LoginActivity"):
"""
重新连接Appium驱动不重新启动应用
参数:
device_id: 设备ID
old_driver: 旧的WebDriver实例可选
app_package: 应用包名
app_activity: 应用启动Activity
返回:
(driver, wait): 新的WebDriver和WebDriverWait实例
"""
# 使用传入的device_id或从全局变量获取
if not device_id:
device_id = global_variable.GLOBAL_DEVICE_ID
# 修复device_id参数类型问题并使用全局设备ID作为备用
actual_device_id = device_id
# 检查device_id是否为有效的字符串格式
if not actual_device_id or (isinstance(actual_device_id, str) and ("session=" in actual_device_id or len(actual_device_id.strip()) == 0)):
# 尝试从old_driver获取设备ID
if old_driver and hasattr(old_driver, 'capabilities'):
capability_device_id = old_driver.capabilities.get('udid')
if capability_device_id:
actual_device_id = capability_device_id
logging.warning(f"检测到device_id参数无效已从old_driver中提取设备ID: {actual_device_id}")
# 如果仍然没有有效的设备ID使用全局变量
if not actual_device_id or (isinstance(actual_device_id, str) and ("session=" in actual_device_id or len(actual_device_id.strip()) == 0)):
actual_device_id = global_variable.GLOBAL_DEVICE_ID
logging.warning(f"无法获取有效设备ID使用全局变量GLOBAL_DEVICE_ID: {actual_device_id}")
device_id = actual_device_id # 使用修正后的设备ID
logging.info(f"设备 {device_id} 开始重新连接驱动(不重启应用)")
# # 首先安全关闭旧驱动
# if old_driver:
# safe_quit_driver(old_driver, device_id)
max_reconnect_attempts = 3
reconnect_delay = 5 # 秒
for attempt in range(max_reconnect_attempts):
try:
logging.info(f"设备 {device_id}{attempt + 1}次尝试重新连接")
# 确保Appium服务器运行
if not ensure_appium_server_running():
logging.warning(f"设备 {device_id} Appium服务器未运行尝试启动")
time.sleep(reconnect_delay)
continue
# 创建并配置Appium选项 - 重点:设置 autoLaunch=False 不自动启动应用
options = UiAutomator2Options()
options.platform_name = "Android"
options.device_name = device_id
options.app_package = app_package
options.app_activity = app_activity
options.automation_name = "UiAutomator2"
options.no_reset = True
options.auto_grant_permissions = True
options.new_command_timeout = 3600
options.udid = device_id
# 关键设置:不自动启动应用
options.set_capability('autoLaunch', False)
options.set_capability('skipUnlock', True)
options.set_capability('skipServerInstallation', True)
options.set_capability('skipDeviceInitialization', True)
# 增加uiautomator2服务器启动超时时间
options.set_capability('uiautomator2ServerLaunchTimeout', 60000) # 60秒
# 增加连接超时设置
options.set_capability('connection_timeout', 120000) # 120秒
# 连接Appium服务器
logging.info(f"设备 {device_id} 正在连接Appium服务器不启动应用")
driver = webdriver.Remote("http://localhost:4723", options=options)
logging.info(f"设备 {device_id} Appium服务器连接成功")
# 初始化等待对象
wait = WebDriverWait(driver, 20)
logging.info(f"设备 {device_id} WebDriverWait初始化成功")
# 不启动应用,直接附加到当前运行的应用
try:
# 获取当前运行的应用
current_package = driver.current_package
logging.info(f"设备 {device_id} 当前运行的应用: {current_package}")
# 如果当前运行的不是目标应用,尝试切换到目标应用
if current_package != app_package:
logging.info(f"设备 {device_id} 当前应用不是目标应用,尝试启动目标应用")
launch_app_manually(driver, app_package, app_activity)
else:
logging.info(f"设备 {device_id} 已成功连接到运行中的目标应用")
except Exception as attach_error:
logging.warning(f"设备 {device_id} 获取当前应用信息失败: {str(attach_error)}")
# 即使获取当前应用失败,也继续使用连接
# 验证新会话是否有效
if check_session_valid(driver, device_id):
logging.info(f"设备 {device_id} 重新连接成功")
return driver, wait
else:
logging.warning(f"设备 {device_id} 新创建的会话无效,将重试")
safe_quit_driver(driver, device_id)
except Exception as e:
logging.error(f"设备 {device_id}{attempt + 1}次重新连接失败: {str(e)}")
if attempt < max_reconnect_attempts - 1:
wait_time = reconnect_delay * (attempt + 1)
logging.info(f"设备 {device_id} 将在{wait_time}秒后重试重新连接")
time.sleep(wait_time)
else:
logging.error(f"设备 {device_id} 所有重新连接尝试均失败")
# 所有尝试都失败后才关闭旧驱动
if old_driver:
safe_quit_driver(old_driver, device_id)
raise
# 所有尝试都失败
raise Exception(f"设备 {device_id} 重新连接失败,已尝试{max_reconnect_attempts}")
# def ensure_appium_server_running(port=4723):
# """使用完整的环境变量启动Appium解决路径缺失问题"""
# try:
# # 获取当前用户的环境变量
# env = os.environ.copy()
# # ========== 仅修改这部分Windows下Node.js/Appium常见路径 ==========
# additional_paths = [
# # Windows默认Node.js安装路径64位
# os.path.join("C:\\", "Program Files\\nodejs"),
# # Windows32位Node.js路径
# os.path.join("C:\\", "Program Files (x86)\\nodejs"),
# # npm全局安装路径Windows核心appium一般装在这里
# os.path.expanduser("~\\AppData\\Roaming\\npm"),
# # 系统默认路径(防止基础命令缺失)
# os.path.join("C:\\", "Windows\\System32"),
# os.path.join("C:\\", "Windows\\System"),
# # 自定义Node.js/npm路径可选根据你的实际安装路径加
# # "D:\\Program Files\\nodejs", # 若你装在D盘解开注释并修改
# ]
# # ========== Windows路径修改结束 ==========
# # 更新PATH环境变量跨系统通用os.pathsep自动适配Windows的;和macOS的:
# current_path = env.get('PATH', '')
# new_path = current_path + os.pathsep + os.pathsep.join(additional_paths)
# env['PATH'] = new_path
# # 构建启动命令(和原函数一致,无修改)
# appium_cmd = f"appium -p {port} --log-level error"
# # 使用完整环境启动跨系统通用Windows下正常执行
# process = subprocess.Popen(
# appium_cmd,
# shell=True, # Windows下字符串命令必须开启和macOS一致
# env=env, # 传入补全后的Windows环境变量核心
# stdout=subprocess.PIPE, # 捕获输出,控制台不刷屏
# stderr=subprocess.PIPE,
# text=True # 输出为字符串,无需手动解码(跨系统通用)
# )
# logging.info(f"Appium启动进程已创建PID: {process.pid}")
# # 等待并校验服务启动成功需确保wait_for_appium_start已定义timeout有值
# return wait_for_appium_start(port)
# except Exception as e:
# logging.error(f"Windows下启动Appium时出错: {str(e)}")
# return False
def is_port_in_use(port):
"""检查端口是否被占用"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(('127.0.0.1', port)) == 0
def kill_system_process(process_name):
"""杀掉系统进程"""
try:
# Windows
subprocess.run(f"taskkill /F /IM {process_name}", shell=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
except Exception:
pass
def start_appium_server(port=4723):
"""启动 Appium 服务,强制指定路径兼容性"""
# 1. 先尝试清理可能占用的 node 进程
if is_port_in_use(port):
logging.warning(f"端口 {port} 被占用,尝试清理 node.exe...")
kill_system_process("node.exe")
time.sleep(2)
# 2. 构造启动命令
# 注意:这里增加了 --base-path /wd/hub 解决 404 问题
# --allow-cors 允许跨域,有时候能解决连接问题
appium_cmd = f"appium -p {port} --base-path /wd/hub --allow-cors"
logging.info(f"正在启动 Appium: {appium_cmd}")
try:
# 使用 shell=True 在 Windows 上更稳定
# creationflags=subprocess.CREATE_NEW_CONSOLE 可以让它在后台运行不弹出窗口
subprocess.Popen(appium_cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
logging.info("Appium 启动命令已发送,等待服务就绪...")
except Exception as e:
logging.error(f"启动 Appium 进程失败: {e}")
def check_server_status(port):
"""检测服务器状态,兼容 Appium 1.x 和 2.x 路径"""
base_url = f"http://127.0.0.1:{port}"
check_paths = ["/wd/hub/status", "/status"] # 优先检查 /wd/hub
for path in check_paths:
try:
url = f"{base_url}{path}"
response = requests.get(url, timeout=1)
if response.status_code == 200:
return True
except:
pass
return False
def ensure_appium_server_running(port=4723):
"""确保 Appium 服务器正在运行,如果没运行则启动它"""
# 1. 第一次快速检测
if check_server_status(port):
logging.info(f"Appium 服务已在端口 {port} 运行")
return True
# 2. 如果没运行,启动它
logging.warning(f"Appium 未在端口 {port} 运行,准备启动...")
start_appium_server(port)
# 3. 循环等待启动成功(最多等待 20 秒)
max_retries = 20
for i in range(max_retries):
if check_server_status(port):
logging.info("Appium 服务启动成功并已就绪!")
return True
time.sleep(1)
if i % 5 == 0:
logging.info(f"等待 Appium 启动中... ({i}/{max_retries})")
logging.error("Appium 服务启动超时!请检查 appium 命令是否在命令行可直接运行。")
return False
# ... (保留 init_appium_driver, safe_quit_driver 等其他函数) ...
def wait_for_appium_start(port, timeout=10):
"""
检测指定端口的Appium服务是否真正启动并可用
:param port: Appium服务端口
:param timeout: 最大等待时间(秒)
:return: 服务就绪返回True超时/失败返回False
"""
# Appium官方状态查询接口
check_url = f"http://localhost:{port}/wd/hub/status"
# 检测开始时间
start_check_time = time.time()
logging.info(f"开始检测Appium服务是否就绪端口{port},最大等待{timeout}")
while time.time() - start_check_time < timeout:
try:
# 发送HTTP请求超时1秒避免单次检测卡太久
response = requests.get(
url=check_url,
timeout=1,
verify=False # 本地接口禁用SSL验证
)
# 接口返回200HTTP成功状态码且JSON中status=0Appium服务正常
if response.status_code == 200 and response.json().get("status") == 0:
logging.info(f"Appium服务检测成功端口{port}已就绪")
return True
except Exception as e:
# 捕获所有异常连接拒绝、超时、JSON解析失败等说明服务未就绪
logging.debug(f"本次检测Appium服务未就绪{str(e)}") # 调试日志,不刷屏
# 检测失败休眠1秒后重试
time.sleep(1)
# 循环结束→超时
logging.error(f"检测超时!{timeout}秒内Appium服务端口{port}仍未就绪")
return False
def safe_quit_driver(driver, device_id=None):
"""
安全关闭驱动的全局函数
参数:
driver: WebDriver实例
device_id: 设备ID可选
"""
if device_id is None:
device_id = global_variable.GLOBAL_DEVICE_ID
device_str = f"设备 {device_id} " if device_id else ""
logging.info(f"{device_str}开始关闭驱动")
if not driver:
logging.info(f"{device_str}没有可关闭的驱动实例")
return
# 检查driver是否为WebDriver实例或是否有quit方法
if not hasattr(driver, 'quit'):
logging.warning(f"{device_str}驱动对象类型无效不具有quit方法: {type(driver).__name__}")
return
max_quit_attempts = 3
for attempt in range(max_quit_attempts):
try:
logging.info(f"{device_str}尝试关闭驱动 (尝试 {attempt + 1}/{max_quit_attempts})")
driver.quit()
logging.info(f"{device_str}驱动已成功关闭")
return
except Exception as e:
logging.error(f"{device_str}关闭驱动时出错 (尝试 {attempt + 1}/{max_quit_attempts}): {str(e)}")
if attempt < max_quit_attempts - 1:
# 等待一段时间后重试
wait_time = 2
logging.info(f"{device_str}将在 {wait_time} 秒后重试")
time.sleep(wait_time)
else:
logging.critical(f"{device_str}尝试多次关闭驱动失败,可能导致资源泄漏")
def is_app_launched(driver, package_name="com.bjjw.cjgc"):
"""
检查应用是否已启动
参数:
driver: WebDriver实例
package_name: 应用包名,默认为"com.bjjw.cjgc"
返回:
bool: 如果应用已启动则返回True否则返回False
"""
try:
# 通过检查当前活动的包名来确认应用是否已启动
current_package = driver.current_package
return current_package == package_name
except Exception as e:
logging.error(f"检查应用启动状态时出错: {str(e)}")
return False
def launch_app_manually(driver, device_id, package_name="com.bjjw.cjgc", activity=".activity.LoginActivity"):
"""
手动启动应用
参数:
driver: WebDriver实例
package_name: 应用包名,默认为"com.bjjw.cjgc"
activity: 应用启动Activity默认为".activity.LoginActivity"
"""
try:
if not device_id:
device_id = global_variable.GLOBAL_DEVICE_ID
# 尝试从driver获取设备ID
if driver and hasattr(driver, 'capabilities'):
device_id = driver.capabilities.get('udid')
device_str = f"设备 {device_id} " if device_id else ""
else:
device_str = ""
logging.info(f"{device_str}尝试手动启动应用: {package_name}/{activity}")
# 首先使用ADB命令退出应用
if device_id:
try:
# 使用ADB命令强制停止应用
stop_cmd = [
"adb", "-s", device_id,
"shell", "am", "force-stop",
package_name
]
stop_result = subprocess.run(stop_cmd, capture_output=True, text=True, timeout=15)
if stop_result.returncode == 0:
logging.info(f"{device_str}已使用ADB命令成功退出应用")
else:
logging.warning(f"{device_str}ADB退出应用失败: {stop_result.stderr}")
except Exception as stop_error:
logging.warning(f"{device_str}退出应用时出错: {str(stop_error)}")
# 首先尝试使用driver的execute_script方法启动应用
try:
if driver:
driver.execute_script("mobile: startActivity", {
"intent": f"{package_name}/{activity}"
})
logging.info(f"{device_str}已使用Appium startActivity命令启动应用")
except Exception as inner_e:
logging.warning(f"{device_str}使用Appium startActivity命令失败: {str(inner_e)}尝试使用ADB命令")
# 如果device_id可用使用ADB命令启动应用
if device_id:
cmd = [
"adb", "-s", device_id,
"shell", "am", "start",
"-n", f"{package_name}/{activity}"
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
if result.returncode == 0:
logging.info(f"{device_str}已使用ADB命令成功启动应用")
else:
logging.error(f"{device_str}ADB启动应用失败: {result.stderr}")
else:
logging.warning("无法获取设备ID无法使用ADB命令启动应用")
# 设置屏幕永不休眠
if device_id:
try:
# 使用ADB命令设置屏幕永不休眠
screen_timeout_cmd = [
"adb", "-s", device_id,
"shell", "settings", "put", "system", "screen_off_timeout", "0"
]
timeout_result = subprocess.run(screen_timeout_cmd, capture_output=True, text=True, timeout=15)
if timeout_result.returncode == 0:
logging.info(f"{device_str}已成功设置屏幕永不休眠")
else:
logging.warning(f"{device_str}设置屏幕永不休眠失败: {timeout_result.stderr}")
except Exception as timeout_error:
logging.warning(f"{device_str}设置屏幕永不休眠时出错: {str(timeout_error)}")
# 等待应用启动
time.sleep(5)
except Exception as e:
logging.error(f"手动启动应用时出错: {str(e)}")
logging.error(f"错误堆栈: {traceback.format_exc()}")
def go_main_click_tabber_button(driver, device_id, tabber_button_text, max_retries=3):
"""
跳转到主页面并点击对应的导航菜单按钮(带重试机制)
参数:
driver: WebDriver实例
device_id: 设备ID
tabber_button_text: 导航菜单按钮的文本
max_retries: 最大重试次数
返回:
bool: 成功返回True失败返回False
"""
retry_count = 0
while retry_count < max_retries:
try:
logging.info(f"设备 {device_id}{retry_count + 1} 次尝试执行导航操作")
if not check_session_valid(driver, device_id):
logging.warning(f"设备 {device_id} 会话无效,尝试重新连接驱动...")
try:
# 重新连接获取新的driver
new_driver, _ = reconnect_driver(device_id, driver)
driver = new_driver # 更新driver引用
logging.info(f"设备 {device_id} 驱动重连成功")
except Exception as e:
logging.error(f"设备 {device_id} 驱动重连失败: {str(e)}")
retry_count += 1
if retry_count < max_retries:
time.sleep(2)
continue
else:
return False
# 检查当前是否已经在主页面
current_activity = driver.current_activity
logging.info(f"设备 {device_id} 当前Activity: {current_activity}")
if ".activity.MainActivity" in current_activity:
logging.info(f"设备 {device_id} 已在主页面")
else:
logging.info(f"设备 {device_id} 当前不在主页面,循环点击返回按钮,直到在主页面")
max_back_presses = 10 # 最大返回键次数
back_press_count = 0
while ".activity.MainActivity" not in current_activity and back_press_count < max_back_presses:
try:
if not check_session_valid(driver, device_id):
logging.warning(f"设备 {device_id} 会话无效,尝试重新连接驱动...")
if not reconnect_driver(device_id, driver):
logging.error(f"设备 {device_id} 驱动重连失败")
# 点击返回按钮
driver.back()
back_press_count += 1
time.sleep(1)
# 更新当前Activity
current_activity = driver.current_activity
logging.info(f"设备 {device_id} 点击返回按钮 {back_press_count} 次后当前Activity: {current_activity}")
except Exception as inner_e:
logging.warning(f"设备 {device_id} 点击返回按钮时出错: {str(inner_e)}")
break
# 检查是否成功回到主页面
if ".activity.MainActivity" not in current_activity:
logging.warning(f"设备 {device_id} 无法回到主页面当前Activity: {current_activity}")
# 不立即返回,继续重试逻辑
retry_count += 1
if retry_count < max_retries:
logging.info(f"设备 {device_id} 等待2秒后重试...")
time.sleep(2)
continue
else:
logging.error(f"设备 {device_id} 达到最大重试次数,无法回到主页面")
return False
# 现在已经在主页面,点击指定的导航菜单按钮
# logging.info(f"设备 {device_id} 已在主页面,尝试点击导航菜单按钮: {tabber_button_text}")
try:
tabber_button = driver.find_element(AppiumBy.ID, tabber_button_text)
# 点击按钮
tabber_button.click()
logging.info(f"设备 {device_id} 已成功点击导航菜单按钮: {tabber_button_text}")
# 等待页面加载
time.sleep(2)
# 验证操作是否成功
# 可以添加一些验证逻辑,比如检查是否跳转到目标页面
new_activity = driver.current_activity
logging.info(f"设备 {device_id} 点击后当前Activity: {new_activity}")
return True
except TimeoutException:
logging.error(f"设备 {device_id} 等待导航菜单按钮 '{tabber_button_text}' 超时")
except Exception as e:
logging.error(f"设备 {device_id} 点击导航菜单按钮 '{tabber_button_text}' 时出错: {str(e)}")
# 检查会话有效性并尝试重连
if not check_session_valid(driver, device_id):
logging.warning(f"设备 {device_id} 会话无效,尝试重新连接驱动...")
if reconnect_driver(device_id, driver):
logging.info(f"设备 {device_id} 驱动重连成功,继续重试")
# 重连成功后继续循环
retry_count += 1
if retry_count < max_retries:
time.sleep(2)
continue
else:
logging.error(f"设备 {device_id} 驱动重连失败")
return False
else:
# 会话有效但点击失败,可能是页面元素问题
logging.warning(f"设备 {device_id} 会话有效但点击失败,可能是页面加载问题")
# 如果点击按钮失败,增加重试计数
retry_count += 1
if retry_count < max_retries:
logging.info(f"设备 {device_id} 点击按钮失败等待2秒后第 {retry_count + 1} 次重试...")
time.sleep(2)
else:
logging.error(f"设备 {device_id} 达到最大重试次数 {max_retries},导航失败")
return False
except Exception as e:
logging.error(f"设备 {device_id}{retry_count + 1} 次尝试时出错: {str(e)}")
retry_count += 1
if retry_count < max_retries:
logging.info(f"设备 {device_id} 等待2秒后重试...")
time.sleep(2)
else:
logging.error(f"设备 {device_id} 达到最大重试次数 {max_retries},导航失败")
return False
return False
def check_connection_error(exception):
"""检查是否为连接拒绝错误"""
error_str = str(exception)
connection_errors = [
'远程主机强迫关闭了一个现有的连接',
'由于目标计算机积极拒绝,无法连接',
'ConnectionResetError',
'NewConnectionError',
'10054',
'10061'
]
return any(error in error_str for error in connection_errors)
def restart_appium_server(port=4723):
"""重启Appium服务器"""
try:
# 杀死可能存在的Appium进程
subprocess.run(['taskkill', '/f', '/im', 'node.exe'],
capture_output=True, shell=True)
time.sleep(2)
# 启动Appium服务器
# appium_command = f'appium -p {port}'
appium_command = f"appium -p {port} --base-path /wd/hub"
subprocess.Popen(appium_command, shell=True)
# 等待Appium启动
time.sleep(10)
return True
except Exception as e:
print(f"重启Appium服务器失败: {str(e)}")
return False
def is_appium_running(port=4723):
"""检查Appium服务器是否在运行"""
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
result = s.connect_ex(('127.0.0.1', port))
return result == 0
except:
return False

298
globals/ex_apis.py Normal file
View File

@@ -0,0 +1,298 @@
# external_apis.py
import requests
import json
import logging
import random
from typing import Dict, Tuple, Optional
import time
class WeatherAPI:
def __init__(self):
self.logger = logging.getLogger(__name__)
# 使用腾讯天气API
self.base_url = "https://wis.qq.com/weather/common"
def parse_city(self, city_string: str) -> Tuple[str, str, str]:
"""
解析城市字符串,返回省份、城市、区县
参数:
city_string: 完整的地址字符串
返回:
(province, city, county)
"""
# 匹配省份或自治区
province_regex = r"(.*?)(省|自治区)"
# 匹配城市或州
city_regex = r"(.*?省|.*?自治区)(.*?市|.*?州)"
# 匹配区、县或镇
county_regex = r"(.*?市|.*?州)(.*?)(区|县|镇)"
province = ""
city = ""
county = ""
import re
# 先尝试匹配省份或自治区
province_match = re.search(province_regex, city_string)
if province_match:
province = province_match.group(1).strip()
# 然后尝试匹配城市或州
city_match = re.search(city_regex, city_string)
if city_match:
city = city_match.group(2).strip()
else:
# 如果没有匹配到城市,则可能是直辖市或者直接是区/县
city = city_string
# 最后尝试匹配区、县或镇
county_match = re.search(county_regex, city_string)
if county_match:
county = county_match.group(2).strip()
# 如果有区、县或镇,那么前面的城市部分需要重新解析
if city_match:
city = city_match.group(2).strip()
# 特殊情况处理,去除重复的省市名称
if city and province and city.startswith(province):
city = city.replace(province, "").strip()
if county and city and county.startswith(city):
county = county.replace(city, "").strip()
# 去除后缀
city = city.rstrip('市州')
if county:
county = county.rstrip('区县镇')
# self.logger.info(f"解析结果 - 省份: {province}, 城市: {city}, 区县: {county}")
return province, city, county
def get_weather_by_qq_api(self, province: str, city: str, county: str) -> Optional[Dict]:
"""
使用腾讯天气API获取天气信息
参数:
province: 省份
city: 城市
county: 区县
返回:
天气信息字典 or None
"""
try:
params = {
'source': 'pc',
'weather_type': 'observe',
'province': province,
'city': city,
'county': county
}
response = requests.get(self.base_url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
if data.get('status') == 200:
observe_data = data.get('data', {}).get('observe', {})
return {
'weather': observe_data.get('weather', ''),
'temperature': observe_data.get('degree', ''),
'pressure': observe_data.get('pressure', '1013')
}
else:
self.logger.error(f"腾讯天气API错误: {data.get('message')}")
return None
except Exception as e:
self.logger.error(f"腾讯天气API调用失败: {str(e)}")
return None
def normalize_weather_text(self, weather_text: str) -> str:
"""
将天气描述标准化为: 晴;阴;雨;雪;风;其他
参数:
weather_text: 原始天气描述
返回:
标准化后的天气文本
"""
if not weather_text:
return '其他'
weather_text_lower = weather_text.lower()
# 晴
if any(word in weather_text_lower for word in ['', 'sunny', 'clear']):
return ''
# 阴
elif any(word in weather_text_lower for word in ['', '多云', 'cloudy', 'overcast']):
return ''
# 雨
elif any(word in weather_text_lower for word in ['', 'rain', 'drizzle', 'shower']):
return ''
# 雪
elif any(word in weather_text_lower for word in ['', 'snow']):
return ''
# 风
elif any(word in weather_text_lower for word in ['', 'wind']):
return ''
# 其他
else:
return '其他'
def adjust_pressure(self, pressure: float) -> float:
"""
调整气压值低于700时填700-750之间随机一个高于700就按实际情况填
参数:
pressure: 原始气压值
返回:
调整后的气压值
"""
try:
pressure_float = float(pressure)
if pressure_float < 700:
adjusted_pressure = random.randint(700, 750)
self.logger.info(f"气压值 {pressure_float} 低于700调整为: {adjusted_pressure:.1f}")
return round(adjusted_pressure, 1)
else:
self.logger.info(f"使用实际气压值: {pressure_float}")
return round(pressure_float, 1)
except (ValueError, TypeError):
self.logger.warning(f"气压值格式错误: {pressure}使用默认值720")
return round(random.randint(700, 750), 1)
def get_weather_by_address(self, address: str, max_retries: int = 2) -> Optional[Dict]:
"""
根据地址获取天气信息
参数:
address: 地址字符串
max_retries: 最大重试次数
返回:
{
'weather': '晴/阴/雨/雪/风/其他',
'temperature': 温度值,
'pressure': 气压值
} or None
"""
# self.logger.info(f"开始获取地址 '{address}' 的天气信息")
# 首先解析地址
province, city, county = self.parse_city(address)
if not province and not city:
self.logger.error("无法解析地址")
return self.get_fallback_weather()
# 获取天气信息
weather_data = None
for attempt in range(max_retries):
try:
# self.logger.info(f"尝试获取天气信息 (第{attempt + 1}次)")
weather_data = self.get_weather_by_qq_api(province, city, county)
if weather_data:
break
time.sleep(1) # 短暂延迟后重试
except Exception as e:
self.logger.warning(f"{attempt + 1}次尝试失败: {str(e)}")
time.sleep(1)
if not weather_data:
self.logger.warning("获取天气信息失败,使用备用数据")
return self.get_fallback_weather()
# 处理天气数据
try:
# 标准化天气文本
normalized_weather = self.normalize_weather_text(weather_data['weather'])
# 调整气压值
adjusted_pressure = self.adjust_pressure(weather_data['pressure'])
# 处理温度
temperature = float(weather_data['temperature'])
result = {
'weather': normalized_weather,
'temperature': round(temperature, 1),
'pressure': adjusted_pressure
}
self.logger.info(f"成功获取天气信息: {result}")
return result
except Exception as e:
self.logger.error(f"处理天气数据时出错: {str(e)}")
return self.get_fallback_weather()
def get_fallback_weather(self) -> Dict:
"""
获取备用天气数据当所有API都失败时使用
返回:
默认天气数据
"""
self.logger.info("使用备用天气数据")
return {
'weather': '',
'temperature': round(random.randint(15, 30), 1),
'pressure': round(random.randint(700, 750), 1)
}
def get_weather_simple(self, address: str) -> Tuple[str, float, float]:
"""
简化接口:直接返回天气、温度、气压
参数:
address: 地址字符串
返回:
(weather, temperature, pressure)
"""
weather_data = self.get_weather_by_address(address)
if weather_data:
return weather_data['weather'], weather_data['temperature'], weather_data['pressure']
else:
fallback = self.get_fallback_weather()
return fallback['weather'], fallback['temperature'], fallback['pressure']
# 创建全局实例
weather_api = WeatherAPI()
# 直接可用的函数
def get_weather_by_address(address: str) -> Optional[Dict]:
"""
根据地址获取天气信息(直接调用函数)
参数:
address: 地址字符串
返回:
{
'weather': '晴/阴/雨/雪/风/其他',
'temperature': 温度值,
'pressure': 气压值
} or None
"""
return weather_api.get_weather_by_address(address)
def get_weather_simple(address: str) -> Tuple[str, float, float]:
"""
简化接口:直接返回天气、温度、气压
参数:
address: 地址字符串
返回:
(weather, temperature, pressure)
"""
return weather_api.get_weather_simple(address)

View File

@@ -0,0 +1,17 @@
# 全局变量
GLOBAL_DEVICE_ID = "" # 设备ID
GLOBAL_USERNAME = "czyuzongwen" # 用户名
GLOBAL_CURRENT_PROJECT_NAME = "" # 当前测试项目名称
GLOBAL_LINE_NUM = "" # 线路编码
GLOBAL_BREAKPOINT_STATUS_CODES = [0,3] # 要获取的断点状态码列表
GLOBAL_UPLOAD_BREAKPOINT_LIST = []
GLOBAL_UPLOAD_BREAKPOINT_DICT = {}
GLOBAL_TESTED_BREAKPOINT_LIST = [] # 测量结束的断点列表
LINE_TIME_MAPPING_DICT = {} # 存储所有线路编码和对应的时间的全局字典
GLOBAL_BREAKPOINT_DICT = {} # 存储测量结束的断点名称和对应的线路编码的全局字典
GLOBAL_NAME_TO_ID_MAP = {} # 存储所有数据员姓名和对应的身份证号的全局字典
GLOBAL_UPLOAD_SUCCESS_BREAKPOINT_LIST = [] # 上传成功的`断点列表

59
globals/ids.py Normal file
View File

@@ -0,0 +1,59 @@
# ids.py
# 登录界面
LOGIN_USERNAME = "com.bjjw.cjgc:id/et_user_name"
LOGIN_PASSWORD = "com.bjjw.cjgc:id/et_user_psw"
LOGIN_BTN = "com.bjjw.cjgc:id/btn_login"
# 更新相关
UPDATE_WORK_BASE = "com.bjjw.cjgc:id/btn_update_basepoint"
UPDATE_LEVEL_LINE = "com.bjjw.cjgc:id/btn_update_line"
UPDATE_LEVEL_LINE_CONFIRM = "com.bjjw.cjgc:id/commit"
# 弹窗 & 加载
ALERT_DIALOG = "android:id/content"
LOADING_DIALOG = "android:id/custom"
# 底部导航栏
DOWNLOAD_TABBAR_ID = "com.bjjw.cjgc:id/img_1_layout"
MEASURE_TABBAR_ID = "com.bjjw.cjgc:id/img_3_layout"
# 测量相关
MEASURE_BTN_ID = "com.bjjw.cjgc:id/select_point_update_tip_tv"
MEASURE_LIST_ID = "com.bjjw.cjgc:id/line_list"
MEASURE_LISTVIEW_ID = "com.bjjw.cjgc:id/itemContainer"
MEASURE_NAME_TEXT_ID = "com.bjjw.cjgc:id/title"
MEASURE_NAME_ID = "com.bjjw.cjgc:id/sectName"
MEASURE_BACK_ID = "com.bjjw.cjgc:id/btn_back"
MEASURE_BACK_ID_2 = "com.bjjw.cjgc:id/stop_measure_btn"
# 天气、观测类型
MEASURE_TITLE_ID = "com.bjjw.cjgc:id/title_bar"
MEASURE_WEATHER_ID = "com.bjjw.cjgc:id/point_list_weather_sp"
MEASURE_TYPE_ID = "com.bjjw.cjgc:id/point_list_mtype_sp"
MEASURE_SELECT_ID = "android:id/select_dialog_listview"
SELECT_DIALOG_TEXT1_ID = "android:id/text1"
MEASURE_PRESSURE_ID = "com.bjjw.cjgc:id/point_list_barometric_et"
MEASURE_TEMPERATURE_ID = "com.bjjw.cjgc:id/point_list_temperature_et"
MEASURE_SAVE_ID = "com.bjjw.cjgc:id/select_point_order_save_btn"
# 日期选择器
DATE_START = "com.bjjw.cjgc:id/date"
DATE_END = "com.bjjw.cjgc:id/date_end"
SCRCOLL_YEAR = "com.bjjw.cjgc:id/wheelView1"
SCRCOLL_MONTH = "com.bjjw.cjgc:id/wheelView2"
SCRCOLL_DAY = "com.bjjw.cjgc:id/wheelView3"
SCRCOLL_CONFIRM = "com.bjjw.cjgc:id/okBtn"
SCRCOLL_CANCEL = "com.bjjw.cjgc:id/cancelBtn"
# 其他
CONNECT_LEVEL_METER = "com.bjjw.cjgc:id/point_conn_level_btn"
PINGCHAR_PROCESS = "com.bjjw.cjgc:id/point_measure_btn"
LEVEL_METER_MANAGER = "com.bjjw.cjgc:id/btn_back"
LEVEL_METER_MANAGER_LIST = "com.bjjw.cjgc:id/total_station_expandlist"
START_MEASURE = "com.bjjw.cjgc:id/btn_control_begin_or_end"
ALTER_MEASURE = "com.bjjw.cjgc:id/order_title"
MATCH_VISIABLE = "com.bjjw.cjgc:id/title_paired_devices"
BLUETOOTH_PAIR = "com.bjjw.cjgc:id/paired_devices"
REPID_MEASURE = "com.bjjw.cjgc:id/measure_remeasure_all_btn"
ONE = "com.bjjw.cjgc:id/auto_measure_all_station_text"