2025-04-30 16:57:46 +08:00
|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
同步服务模块
|
|
|
|
|
用于与天风系统进行数据同步
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import aiohttp
|
|
|
|
|
import asyncio
|
2025-09-10 15:45:35 +08:00
|
|
|
|
import requests
|
2025-04-30 16:57:46 +08:00
|
|
|
|
from typing import Dict, Any, Optional
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
|
|
|
|
# 获取日志记录器
|
|
|
|
|
from utils.logger import get_logger
|
|
|
|
|
logger = get_logger("services.sync_service")
|
|
|
|
|
|
|
|
|
|
# 导入天风API配置
|
|
|
|
|
from config.tf_api_config import get_tf_api_config
|
|
|
|
|
|
|
|
|
|
# 获取配置
|
|
|
|
|
tf_config = get_tf_api_config()
|
|
|
|
|
|
|
|
|
|
# 接口配置
|
|
|
|
|
class TFApiConfig:
|
|
|
|
|
"""系统任务API配置"""
|
|
|
|
|
# 基础URL
|
|
|
|
|
BASE_URL = tf_config["base_url"]
|
|
|
|
|
|
|
|
|
|
# 接口路径
|
2025-09-09 10:41:27 +08:00
|
|
|
|
LOGIN_PATH = tf_config["endpoints"]["login"]
|
2025-04-30 16:57:46 +08:00
|
|
|
|
CREATE_TASK_PATH = tf_config["endpoints"]["create_task"]
|
|
|
|
|
CHOOSE_AMR_PATH = tf_config["endpoints"]["choose_amr"]
|
|
|
|
|
ADD_ACTION_PATH = tf_config["endpoints"]["add_action"]
|
|
|
|
|
CLOSURE_TASK_PATH = tf_config["endpoints"]["closure_task"]
|
|
|
|
|
GET_TASK_BLOCK_PATH = tf_config["endpoints"]["get_task_block"]
|
|
|
|
|
GET_TASK_BLOCK_ACTION_PATH = tf_config["endpoints"]["get_task_block_action"]
|
|
|
|
|
SET_TASK_IN_PROGRESS_PATH = tf_config["endpoints"]["set_task_in_progress"]
|
|
|
|
|
SET_TASK_COMPLETED_PATH = tf_config["endpoints"]["set_task_completed"]
|
|
|
|
|
SET_TASK_TERMINATED_PATH = tf_config["endpoints"]["set_task_terminated"]
|
|
|
|
|
SET_TASK_FAILED_PATH = tf_config["endpoints"]["set_task_failed"]
|
2025-08-13 15:27:04 +08:00
|
|
|
|
SET_TASK_DESCRIPTION_PATH = tf_config["endpoints"]["set_task_description"]
|
2025-09-09 10:41:27 +08:00
|
|
|
|
GET_AMR_INFO_PATH = tf_config["endpoints"]["get_amr_info"]
|
2025-04-30 16:57:46 +08:00
|
|
|
|
# 超时设置(秒)
|
|
|
|
|
TIMEOUT = tf_config["timeout"]
|
|
|
|
|
|
|
|
|
|
# 重试次数
|
|
|
|
|
RETRY_TIMES = tf_config["retry_times"]
|
|
|
|
|
|
|
|
|
|
# 模拟模式
|
|
|
|
|
MOCK_MODE = tf_config["mock_mode"]
|
|
|
|
|
|
|
|
|
|
# token请求头
|
|
|
|
|
TOKEN_HEADER = tf_config["token_header"]
|
|
|
|
|
|
|
|
|
|
class CreateTaskRequest(BaseModel):
|
|
|
|
|
"""创建任务请求参数"""
|
|
|
|
|
vwedTaskId: str
|
|
|
|
|
vwedTaskParentId: str
|
|
|
|
|
name: str
|
|
|
|
|
isPeriodic: int # 使用字符串类型,因为接口文档中显示为字符串
|
|
|
|
|
priority: int # 使用字符串类型,因为接口文档中显示为字符串
|
|
|
|
|
createTime: str # 格式: "2025-04-08 22:03:57"
|
|
|
|
|
sceneId: str
|
|
|
|
|
needAmr: int
|
|
|
|
|
class ChooseAmrRequest(BaseModel):
|
|
|
|
|
"""选择AMR请求参数"""
|
|
|
|
|
vwedTaskId: str # 任务id
|
|
|
|
|
stationName: str # 关键站点名称
|
|
|
|
|
priority: int = 1 # 优先级
|
|
|
|
|
appointAmrName: str = "" # 指定AMR名称
|
|
|
|
|
appointAmrGroupName: str = "" # 指定AMR分组名称
|
|
|
|
|
|
|
|
|
|
class AddActionRequest(BaseModel):
|
|
|
|
|
"""添加动作请求参数"""
|
|
|
|
|
taskBlockId: str
|
|
|
|
|
stationName: str
|
|
|
|
|
action: str
|
2025-07-30 15:11:59 +08:00
|
|
|
|
param: str
|
2025-08-13 15:27:04 +08:00
|
|
|
|
store: str
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
2025-09-09 10:41:27 +08:00
|
|
|
|
class LoginRequest(BaseModel):
|
|
|
|
|
"""登录请求参数"""
|
|
|
|
|
username: str
|
|
|
|
|
password: str
|
|
|
|
|
|
|
|
|
|
class LoginResponse(BaseModel):
|
|
|
|
|
"""登录响应模型"""
|
|
|
|
|
success: bool
|
|
|
|
|
message: str
|
|
|
|
|
code: int
|
|
|
|
|
result: Optional[Dict[str, Any]] = None
|
|
|
|
|
timestamp: Optional[int] = None
|
|
|
|
|
|
2025-04-30 16:57:46 +08:00
|
|
|
|
class ApiResponse(BaseModel):
|
|
|
|
|
"""API响应基础模型"""
|
|
|
|
|
success: bool
|
|
|
|
|
message: str
|
|
|
|
|
code: int
|
|
|
|
|
result: Optional[Dict[str, Any]] = None
|
|
|
|
|
timestamp: Optional[int] = None
|
|
|
|
|
|
|
|
|
|
async def create_task(task_record_id: str, task_name: str, is_periodic: bool, priority: int, parent_id: str,
|
|
|
|
|
token: str, map_id: str, is_agv: int) -> Optional[ApiResponse]:
|
|
|
|
|
"""
|
|
|
|
|
调用系统任务创建任务接口
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_record_id: VWED系统任务实例ID
|
|
|
|
|
task_name: 任务名称
|
|
|
|
|
is_periodic: 是否周期任务
|
|
|
|
|
priority: 优先级(1-5)
|
|
|
|
|
parent_id: 父任务ID,如果没有则为空
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
map_id: 相关地图ID
|
|
|
|
|
is_agv: 是否选择AGV
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[ApiResponse]: 接口响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
# 构造请求参数
|
|
|
|
|
request_data = CreateTaskRequest(
|
|
|
|
|
vwedTaskId=task_record_id,
|
|
|
|
|
vwedTaskParentId=parent_id,
|
|
|
|
|
name=task_name,
|
|
|
|
|
isPeriodic=int(is_periodic),
|
|
|
|
|
priority=str(priority),
|
|
|
|
|
createTime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
|
|
|
sceneId=map_id,
|
|
|
|
|
needAmr=is_agv
|
|
|
|
|
)
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.CREATE_TASK_PATH}"
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
2025-05-12 15:43:21 +08:00
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
|
|
|
|
logger.debug(f"使用认证令牌调用接口,令牌头: {TFApiConfig.TOKEN_HEADER}")
|
|
|
|
|
|
2025-04-30 16:57:46 +08:00
|
|
|
|
try:
|
|
|
|
|
logger.info(f"正在同步创建任务到天风系统: {task_record_id}")
|
|
|
|
|
logger.debug(f"创建任务请求参数: {request_data.model_dump_json()}")
|
2025-08-13 15:27:04 +08:00
|
|
|
|
# 优化超时配置,分别设置连接超时和总超时
|
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
2025-04-30 16:57:46 +08:00
|
|
|
|
async with session.post(
|
|
|
|
|
url,
|
|
|
|
|
json=request_data.model_dump(),
|
2025-08-13 15:27:04 +08:00
|
|
|
|
headers=headers
|
2025-04-30 16:57:46 +08:00
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.text()
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
response_data = json.loads(response_text)
|
2025-09-10 15:45:35 +08:00
|
|
|
|
# print(response_data, "-------------============================")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
if response_data.get("success"):
|
|
|
|
|
logger.info(f"成功同步任务到系统任务: {task_record_id}")
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(f"同步任务到系统任务失败: {response_data.get('message')}")
|
|
|
|
|
|
|
|
|
|
return response_data
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
except aiohttp.ClientError as e:
|
|
|
|
|
logger.error(f"调用系统任务创建任务接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"同步任务到系统任务时发生错误: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
async def create_choose_amr_task(task_id: str, key_station_name: str, amr_name: str = "", amr_group_name: str = "", token: str = None, priority: int = 1) -> Optional[ApiResponse]:
|
|
|
|
|
"""
|
|
|
|
|
创建选择AMR任务
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_id: 天风任务ID
|
|
|
|
|
key_station_name: 关键站点名称
|
|
|
|
|
amr_name: 指定的AMR名称,可选
|
|
|
|
|
amr_group_name: 指定的AMR分组名称,可选
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
priority: 优先级
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[ApiResponse]: 接口响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
# 构造请求参数
|
|
|
|
|
request_data = ChooseAmrRequest(
|
|
|
|
|
vwedTaskId=task_id,
|
|
|
|
|
stationName=key_station_name,
|
|
|
|
|
appointAmrName=amr_name,
|
|
|
|
|
appointAmrGroupName=amr_group_name,
|
|
|
|
|
priority=priority
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.CHOOSE_AMR_PATH}"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
2025-05-12 15:43:21 +08:00
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
logger.info(f"正在创建选择AMR任务: {task_id}, 站点: {key_station_name}")
|
|
|
|
|
|
2025-08-13 15:27:04 +08:00
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
2025-04-30 16:57:46 +08:00
|
|
|
|
async with session.post(
|
|
|
|
|
url,
|
|
|
|
|
json=request_data.model_dump(),
|
2025-08-13 15:27:04 +08:00
|
|
|
|
headers=headers
|
2025-04-30 16:57:46 +08:00
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.json()
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
if response_text.get("success", False):
|
|
|
|
|
logger.info(f"成功为任务选择AMR: {task_id}, AMR: {response_text.get('result', {}).get('amrName')}")
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(f"为任务选择AMR失败: {response_text.get('message', '未知错误')}")
|
|
|
|
|
|
|
|
|
|
return response_text
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"调用选择AMR接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
2025-08-13 15:27:04 +08:00
|
|
|
|
async def add_action(task_id: str, station_name: str, action: str, token: str = None, param: dict = None, store: str = None) -> Optional[ApiResponse]:
|
2025-04-30 16:57:46 +08:00
|
|
|
|
"""
|
|
|
|
|
调用系统任务添加动作接口
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_id: 系统任务ID
|
|
|
|
|
station_name: 站点名称
|
|
|
|
|
action: 动作
|
|
|
|
|
token: 认证令牌
|
2025-07-30 15:11:59 +08:00
|
|
|
|
param: 动作参数
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[ApiResponse]: 接口响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
# 构造请求参数
|
2025-07-30 15:11:59 +08:00
|
|
|
|
import json
|
2025-04-30 16:57:46 +08:00
|
|
|
|
request_data = AddActionRequest(
|
|
|
|
|
taskBlockId=task_id,
|
|
|
|
|
stationName=station_name,
|
2025-07-30 15:11:59 +08:00
|
|
|
|
action=action,
|
2025-08-13 15:27:04 +08:00
|
|
|
|
param=json.dumps(param),
|
|
|
|
|
store=store
|
2025-04-30 16:57:46 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.ADD_ACTION_PATH}"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
2025-05-12 15:43:21 +08:00
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
logger.info(f"正在为任务添加动作: {task_id}, 站点: {station_name}, 动作: {action}")
|
|
|
|
|
|
2025-08-13 15:27:04 +08:00
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
2025-04-30 16:57:46 +08:00
|
|
|
|
async with session.post(
|
|
|
|
|
url,
|
|
|
|
|
json=request_data.model_dump(),
|
2025-08-13 15:27:04 +08:00
|
|
|
|
headers=headers
|
2025-04-30 16:57:46 +08:00
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.text()
|
|
|
|
|
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
response_data = json.loads(response_text)
|
|
|
|
|
|
|
|
|
|
if response_data.get("success", False):
|
|
|
|
|
logger.info(f"成功为任务添加动作: {task_id}, 站点: {station_name}, 动作: {action}")
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(f"为任务添加动作失败: {response_data.get('message', '未知错误')}")
|
|
|
|
|
|
|
|
|
|
return response_data
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"调用添加动作接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
async def closure_task(task_id: str, token: str = None) -> Optional[ApiResponse]:
|
|
|
|
|
"""
|
|
|
|
|
调用系统任务封口任务接口
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_id: 系统任务ID
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[ApiResponse]: 接口响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.CLOSURE_TASK_PATH.format(task_id)}"
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
2025-05-12 15:43:21 +08:00
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
logger.info(f"正在封口任务: {task_id}")
|
|
|
|
|
|
2025-08-13 15:27:04 +08:00
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
2025-04-30 16:57:46 +08:00
|
|
|
|
async with session.put(
|
|
|
|
|
url,
|
2025-08-13 15:27:04 +08:00
|
|
|
|
headers=headers
|
2025-04-30 16:57:46 +08:00
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.json()
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
if response_text.get("success", False):
|
|
|
|
|
logger.info(f"成功封口任务: {task_id}")
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(f"封口任务失败: {response_text.get('message', '未知错误')}")
|
|
|
|
|
|
|
|
|
|
return response_text
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"调用封口任务接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
async def get_task_block_detail(task_block_id: str, token: str = None) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
获取任务块详情
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_block_id: 任务块ID
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[Dict[str, Any]]: 接口响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.GET_TASK_BLOCK_PATH.format(id=task_block_id)}"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
2025-05-12 15:43:21 +08:00
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
logger.info(f"正在获取任务块详情: {task_block_id}")
|
|
|
|
|
|
2025-08-13 15:27:04 +08:00
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
2025-04-30 16:57:46 +08:00
|
|
|
|
async with session.get(
|
|
|
|
|
url,
|
2025-08-13 15:27:04 +08:00
|
|
|
|
headers=headers
|
2025-04-30 16:57:46 +08:00
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.text()
|
|
|
|
|
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
response_data = json.loads(response_text)
|
|
|
|
|
|
|
|
|
|
if response_data.get("success", False):
|
|
|
|
|
logger.info(f"成功获取任务块详情: {task_block_id} 具体详情: {response_data}")
|
|
|
|
|
else:
|
2025-07-14 10:29:37 +08:00
|
|
|
|
logger.info(f"获取任务块详情失败: {response_data.get('message', '未知错误')}")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
return response_data
|
|
|
|
|
except json.JSONDecodeError:
|
2025-07-14 10:29:37 +08:00
|
|
|
|
logger.info(f"解析响应JSON失败: {response_text}")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"调用获取任务块详情接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def get_task_block_action_detail(task_block_id: str, token: str = None) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
获取任务块动作详情
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_block_id: 任务块ID
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[Dict[str, Any]]: 接口响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.GET_TASK_BLOCK_ACTION_PATH.format(id=task_block_id)}"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
2025-05-12 15:43:21 +08:00
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
|
|
|
|
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
logger.info(f"正在获取任务块动作详情: {task_block_id}")
|
|
|
|
|
|
2025-08-13 15:27:04 +08:00
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
2025-04-30 16:57:46 +08:00
|
|
|
|
async with session.get(
|
|
|
|
|
url,
|
2025-08-13 15:27:04 +08:00
|
|
|
|
headers=headers
|
2025-04-30 16:57:46 +08:00
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.text()
|
|
|
|
|
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
response_data = json.loads(response_text)
|
|
|
|
|
|
|
|
|
|
if response_data.get("success", False):
|
|
|
|
|
logger.info(f"成功获取任务块动作详情: {task_block_id} 具体详情: {response_data}")
|
|
|
|
|
else:
|
2025-07-14 10:29:37 +08:00
|
|
|
|
logger.info(f"获取任务块动作详情失败: {response_data.get('message', '未知错误')}")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
|
|
|
|
return response_data
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"调用获取任务块动作详情接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
2025-07-14 10:29:37 +08:00
|
|
|
|
async def wait_for_amr_selection(task_block_id: str, token: str = None, context = None) -> Optional[Dict[str, Any]]:
|
2025-04-30 16:57:46 +08:00
|
|
|
|
"""
|
|
|
|
|
等待AMR选择完成,轮询任务块详情直到获取到AMR ID
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_block_id: 任务块ID
|
|
|
|
|
token: 认证令牌
|
2025-07-14 10:29:37 +08:00
|
|
|
|
context: 任务上下文
|
2025-04-30 16:57:46 +08:00
|
|
|
|
Returns:
|
|
|
|
|
Optional[Dict[str, Any]]: 包含AMR ID的响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
logger.info(f"开始等待任务块 {task_block_id} 的AMR选择结果")
|
|
|
|
|
|
|
|
|
|
retry_count = 0
|
|
|
|
|
# 使用固定的重试间隔0.5秒
|
2025-07-14 10:29:37 +08:00
|
|
|
|
actual_retry_interval = 1
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
|
|
|
|
# 无限循环,直到获取到结果
|
|
|
|
|
while True:
|
|
|
|
|
retry_count += 1
|
2025-07-14 10:29:37 +08:00
|
|
|
|
if context:
|
2025-08-13 15:27:04 +08:00
|
|
|
|
|
2025-07-14 10:29:37 +08:00
|
|
|
|
is_canceled = await context.is_task_canceled_async()
|
2025-08-13 15:27:04 +08:00
|
|
|
|
# is_canceled = await context.is_task_canceled_async()
|
|
|
|
|
if context.is_failed:
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": context.failure_reason,
|
|
|
|
|
"is_failed": True,
|
|
|
|
|
"is_canceled": is_canceled
|
|
|
|
|
}
|
2025-07-14 10:29:37 +08:00
|
|
|
|
if is_canceled:
|
|
|
|
|
logger.info(f"检测到任务已被取消,停止等待任务块 {task_block_id} 的AMR选择")
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": "任务已被取消",
|
|
|
|
|
"is_canceled": True
|
|
|
|
|
}
|
2025-04-30 16:57:46 +08:00
|
|
|
|
response = await get_task_block_detail(task_block_id, token)
|
2025-07-30 15:11:59 +08:00
|
|
|
|
# print("response", response, "====")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
if not response or not response.get("success", False):
|
2025-07-14 10:29:37 +08:00
|
|
|
|
logger.info(f"获取任务块详情失败,继续重试,当前尝试次数: {retry_count}")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
await asyncio.sleep(actual_retry_interval)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 从响应中获取关键字段
|
|
|
|
|
result = response.get("result", {})
|
|
|
|
|
amr_id = result.get("amrId", "")
|
|
|
|
|
|
|
|
|
|
# 符合以下条件之一就可以返回:
|
|
|
|
|
# 1. amrId有值 - 表示已经分配了AMR
|
|
|
|
|
# 2. appointAmrId有值 - 表示指定了特定AMR
|
|
|
|
|
# 3. appointAmrGroupId有值 - 表示指定了AMR组
|
|
|
|
|
if amr_id:
|
|
|
|
|
if amr_id:
|
|
|
|
|
logger.info(f"任务块 {task_block_id} 已选择AMR: {amr_id},共尝试 {retry_count} 次")
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
# 否则继续等待
|
|
|
|
|
if retry_count % 10 == 0:
|
|
|
|
|
# 每10次请求记录一次INFO级别日志
|
|
|
|
|
logger.info(f"任务块 {task_block_id} AMR选择未完成(amrId/appointAmrId/appointAmrGroupId均为空),已尝试 {retry_count} 次,继续等待...")
|
|
|
|
|
else:
|
|
|
|
|
# 其他时候记录DEBUG级别日志,减少日志数量
|
|
|
|
|
logger.debug(f"任务块 {task_block_id} AMR选择未完成,已尝试 {retry_count} 次")
|
|
|
|
|
|
|
|
|
|
# 等待0.5秒后继续尝试
|
|
|
|
|
await asyncio.sleep(actual_retry_interval)
|
|
|
|
|
|
|
|
|
|
|
2025-07-14 10:29:37 +08:00
|
|
|
|
async def wait_for_task_block_action_completion(task_block_id: str, token: str = None, context = None) -> Optional[Dict[str, Any]]:
|
2025-04-30 16:57:46 +08:00
|
|
|
|
"""
|
|
|
|
|
等待任务块动作完成,轮询任务块动作详情直到获取到动作完成
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_block_id: 任务块ID
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[Dict[str, Any]]: 包含AMR ID的响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
logger.info(f"开始等待任务块 {task_block_id} 的动作完成")
|
|
|
|
|
|
|
|
|
|
retry_count = 0
|
|
|
|
|
# 使用固定的重试间隔0.5秒
|
2025-07-14 10:29:37 +08:00
|
|
|
|
actual_retry_interval = 1
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
|
|
|
|
# 无限循环,直到获取到结果
|
|
|
|
|
while True:
|
2025-07-14 10:29:37 +08:00
|
|
|
|
if context:
|
|
|
|
|
is_canceled = await context.is_task_canceled_async()
|
2025-08-13 15:27:04 +08:00
|
|
|
|
if context.is_failed:
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": context.failure_reason,
|
|
|
|
|
"is_failed": True,
|
|
|
|
|
"is_canceled": is_canceled
|
|
|
|
|
}
|
2025-07-14 10:29:37 +08:00
|
|
|
|
if is_canceled:
|
|
|
|
|
logger.info(f"检测到任务已被取消,停止等待任务块 {task_block_id} 的AMR选择")
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": "任务已被取消",
|
|
|
|
|
"is_canceled": True
|
|
|
|
|
}
|
|
|
|
|
# break
|
2025-04-30 16:57:46 +08:00
|
|
|
|
retry_count += 1
|
|
|
|
|
response = await get_task_block_action_detail(task_block_id, token)
|
2025-07-30 15:11:59 +08:00
|
|
|
|
# print("response", response, "=============")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
if not response or not response.get("success", False):
|
2025-07-14 10:29:37 +08:00
|
|
|
|
logger.info(f"获取任务块动作 详情失败,继续重试,当前尝试次数: {retry_count}")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
await asyncio.sleep(actual_retry_interval)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 从响应中获取关键字段
|
|
|
|
|
result = response.get("result", {})
|
|
|
|
|
action_status = result.get("status", "")
|
|
|
|
|
|
|
|
|
|
if action_status in [3, 4, 5]:
|
|
|
|
|
logger.info(f"任务块 {task_block_id} 动作已完成,共尝试 {retry_count} 次")
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
# 否则继续等待
|
|
|
|
|
if retry_count % 10 == 0:
|
|
|
|
|
# 每10次请求记录一次INFO级别日志
|
|
|
|
|
logger.info(f"任务块 {task_block_id} 动作未完成(actionStatus为空),已尝试 {retry_count} 次,继续等待...")
|
|
|
|
|
else:
|
|
|
|
|
# 其他时候记录DEBUG级别日志,减少日志数量
|
|
|
|
|
logger.debug(f"任务块 {task_block_id} 动作未完成,已尝试 {retry_count} 次")
|
|
|
|
|
|
|
|
|
|
# 等待0.5秒后继续尝试
|
|
|
|
|
await asyncio.sleep(actual_retry_interval)
|
|
|
|
|
|
|
|
|
|
async def set_task_in_progress(task_id: str, token: str = None) -> Optional[ApiResponse]:
|
|
|
|
|
"""
|
|
|
|
|
设置系统任务状态为执行中
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_id: 系统任务ID
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[ApiResponse]: 接口响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.SET_TASK_IN_PROGRESS_PATH.format(id=task_id)}"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
logger.info(f"正在设置系统任务状态为执行中: {task_id}")
|
|
|
|
|
|
2025-08-13 15:27:04 +08:00
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
2025-04-30 16:57:46 +08:00
|
|
|
|
async with session.put(
|
|
|
|
|
url,
|
2025-08-13 15:27:04 +08:00
|
|
|
|
headers=headers
|
2025-04-30 16:57:46 +08:00
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.json()
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
if response_text.get("success", False):
|
|
|
|
|
logger.info(f"成功设置系统任务状态为执行中: {task_id}")
|
|
|
|
|
else:
|
2025-07-14 10:29:37 +08:00
|
|
|
|
logger.error(f"设置系统任务状态为执行中失败: {response_text.get('message', '未知错误')}")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
|
|
|
|
return response_text
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"调用设置任务状态接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
async def set_task_completed(task_id: str, token: str = None) -> Optional[ApiResponse]:
|
|
|
|
|
"""
|
|
|
|
|
设置系统任务状态为已完成
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_id: 系统任务ID
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[ApiResponse]: 接口响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.SET_TASK_COMPLETED_PATH.format(id=task_id)}"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
logger.info(f"正在设置系统任务状态为已完成: {task_id}")
|
2025-08-13 15:27:04 +08:00
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
2025-04-30 16:57:46 +08:00
|
|
|
|
async with session.put(
|
|
|
|
|
url,
|
2025-08-13 15:27:04 +08:00
|
|
|
|
headers=headers
|
2025-04-30 16:57:46 +08:00
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.json()
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
if response_text.get("success", False):
|
|
|
|
|
logger.info(f"成功设置系统任务状态为已完成: {task_id}")
|
|
|
|
|
else:
|
2025-07-14 10:29:37 +08:00
|
|
|
|
logger.error(f"设置系统任务状态为已完成失败: {response_text.get('message', '未知错误')}")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
|
|
|
|
return response_text
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"调用设置系统任务状态为已完成接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
async def set_task_terminated(task_id: str, token: str = None) -> Optional[ApiResponse]:
|
|
|
|
|
"""
|
|
|
|
|
设置系统任务状态为已终止
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_id: 系统任务ID
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[ApiResponse]: 接口响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.SET_TASK_TERMINATED_PATH.format(id=task_id)}"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
logger.info(f"正在设置系统任务状态为已终止: {task_id}")
|
|
|
|
|
|
2025-08-13 15:27:04 +08:00
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
2025-04-30 16:57:46 +08:00
|
|
|
|
async with session.put(
|
|
|
|
|
url,
|
2025-08-13 15:27:04 +08:00
|
|
|
|
headers=headers
|
2025-04-30 16:57:46 +08:00
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.json()
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
if response_text.get("success", False):
|
|
|
|
|
logger.info(f"成功设置系统任务状态为已终止: {task_id}")
|
|
|
|
|
else:
|
2025-07-14 10:29:37 +08:00
|
|
|
|
logger.error(f"设置系统任务状态为已终止失败: {response_text.get('message', '未知错误')}")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
|
|
|
|
return response_text
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"调用设置任务状态接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
async def set_task_failed(task_id: str, token: str = None) -> Optional[ApiResponse]:
|
|
|
|
|
"""
|
|
|
|
|
设置系统任务状态为已失败
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_id: 系统任务ID
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[ApiResponse]: 接口响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.SET_TASK_FAILED_PATH.format(id=task_id)}"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
|
|
|
|
try:
|
|
|
|
|
logger.info(f"正在设置任务状态为已失败: {task_id}")
|
|
|
|
|
|
2025-08-13 15:27:04 +08:00
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
2025-04-30 16:57:46 +08:00
|
|
|
|
async with session.put(
|
|
|
|
|
url,
|
2025-08-13 15:27:04 +08:00
|
|
|
|
headers=headers
|
2025-04-30 16:57:46 +08:00
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.json()
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
if response_text.get("success", False):
|
|
|
|
|
logger.info(f"成功设置系统任务状态为已失败: {task_id}")
|
|
|
|
|
else:
|
2025-07-14 10:29:37 +08:00
|
|
|
|
logger.error(f"设置系统任务状态为已失败失败: {response_text.get('message', '未知错误')}")
|
2025-04-30 16:57:46 +08:00
|
|
|
|
|
|
|
|
|
return response_text
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"调用设置任务状态接口失败: {str(e)}")
|
|
|
|
|
return None
|
2025-07-30 15:11:59 +08:00
|
|
|
|
|
2025-08-13 15:27:04 +08:00
|
|
|
|
async def set_task_description(task_id: str, description: str, token: str = None) -> Optional[ApiResponse]:
|
|
|
|
|
"""
|
|
|
|
|
设置VWED任务描述
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_id: VWED任务ID
|
|
|
|
|
description: 任务描述
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[ApiResponse]: 接口响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.SET_TASK_DESCRIPTION_PATH.format(id=task_id)}"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
|
|
|
|
headers["Content-Type"] = "application/json"
|
|
|
|
|
|
|
|
|
|
# 构建请求体
|
|
|
|
|
request_data = {"description": description}
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
logger.info(f"正在设置VWED任务描述: {task_id}, 描述: {description}")
|
|
|
|
|
|
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
|
|
|
async with session.put(
|
|
|
|
|
url,
|
|
|
|
|
json=request_data,
|
|
|
|
|
headers=headers
|
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.text()
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
response_data = json.loads(response_text)
|
|
|
|
|
|
|
|
|
|
if response_data.get("success", False):
|
|
|
|
|
logger.info(f"成功设置VWED任务描述: {task_id}")
|
|
|
|
|
else:
|
|
|
|
|
logger.error(f"设置VWED任务描述失败: {response_data.get('message', '未知错误')}")
|
|
|
|
|
|
|
|
|
|
return response_data
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"调用设置任务描述接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
2025-07-30 15:11:59 +08:00
|
|
|
|
|
2025-09-09 10:41:27 +08:00
|
|
|
|
async def get_amr_info(token: str = None) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""
|
2025-09-29 09:35:08 +08:00
|
|
|
|
获取AMR信息(旧接口)
|
2025-09-09 10:41:27 +08:00
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[Dict[str, Any]]: AMR信息响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.GET_AMR_INFO_PATH}"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
|
|
|
|
if token:
|
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
logger.info("正在获取AMR信息")
|
|
|
|
|
|
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
|
|
|
async with session.get(
|
|
|
|
|
url,
|
|
|
|
|
headers=headers
|
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.text()
|
|
|
|
|
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
response_data = json.loads(response_text)
|
|
|
|
|
|
|
|
|
|
if response_data.get("success", False):
|
|
|
|
|
logger.info(f"成功获取AMR信息,获取到 {len(response_data.get('result', []))} 个AMR")
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(f"获取AMR信息失败: {response_data.get('message', '未知错误')}")
|
|
|
|
|
|
|
|
|
|
return response_data
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"调用获取AMR信息接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
2025-09-29 09:35:08 +08:00
|
|
|
|
async def get_amr_list(token: str = None, **params) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
获取AMR列表(新接口)
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
**params: 查询参数
|
|
|
|
|
- brandId: string(20), 非必填, 品牌id
|
|
|
|
|
- typeId: string(20), 非必填, 类型id
|
|
|
|
|
- name: string(32), 非必填, amr名称
|
|
|
|
|
- isAvailable: int(1), 非必填, 0不可用 1可用
|
|
|
|
|
- isAcceptTask: int(1), 非必填, 是否接单: 0不接单 1可接单
|
|
|
|
|
- pageNo: int, 非必填, 当前页码 默认1
|
|
|
|
|
- pageSize: int, 非必填, 每页显示条数 默认20
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[Dict[str, Any]]: AMR列表响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}/amr"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
|
|
|
|
if token:
|
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
logger.info("正在获取AMR列表")
|
|
|
|
|
|
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
|
|
|
async with session.get(
|
|
|
|
|
url,
|
|
|
|
|
params=params,
|
|
|
|
|
headers=headers
|
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.text()
|
|
|
|
|
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
response_data = json.loads(response_text)
|
|
|
|
|
|
|
|
|
|
if response_data.get("success", False):
|
|
|
|
|
records = response_data.get("result", {}).get("records", [])
|
|
|
|
|
logger.info(f"成功获取AMR列表,获取到 {len(records)} 个AMR")
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(f"获取AMR列表失败: {response_data.get('message', '未知错误')}")
|
|
|
|
|
|
|
|
|
|
return response_data
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"调用获取AMR列表接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
2025-09-09 10:41:27 +08:00
|
|
|
|
# Token管理相关变量
|
|
|
|
|
_cached_token = None
|
|
|
|
|
_token_expire_time = None
|
|
|
|
|
|
2025-09-20 16:50:45 +08:00
|
|
|
|
# 登录频率限制相关变量
|
|
|
|
|
_login_attempts = [] # 登录尝试时间记录
|
|
|
|
|
_max_login_attempts = 3 # 最多尝试次数
|
|
|
|
|
_login_window_seconds = 20 # 时间窗口(秒)
|
|
|
|
|
|
|
|
|
|
def _check_login_rate_limit() -> bool:
|
|
|
|
|
"""
|
|
|
|
|
检查登录频率限制
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: True表示可以登录,False表示被限制
|
|
|
|
|
"""
|
|
|
|
|
global _login_attempts, _max_login_attempts, _login_window_seconds
|
|
|
|
|
|
|
|
|
|
current_time = datetime.now().timestamp()
|
|
|
|
|
|
|
|
|
|
# 清理超过时间窗口的记录
|
|
|
|
|
_login_attempts = [t for t in _login_attempts if current_time - t < _login_window_seconds]
|
|
|
|
|
|
|
|
|
|
# 检查是否超过限制
|
|
|
|
|
if len(_login_attempts) >= _max_login_attempts:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def _record_login_attempt():
|
|
|
|
|
"""记录登录尝试"""
|
|
|
|
|
global _login_attempts
|
|
|
|
|
_login_attempts.append(datetime.now().timestamp())
|
|
|
|
|
|
2025-09-09 10:41:27 +08:00
|
|
|
|
async def get_login_token(username: str = "vwed", password: str = "vwed_123456") -> Optional[str]:
|
|
|
|
|
"""
|
|
|
|
|
获取登录token
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
username: 用户名,默认为"vwed"
|
|
|
|
|
password: 密码,默认为"vwed_123456"
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[str]: 认证令牌,如果获取失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
global _cached_token, _token_expire_time
|
|
|
|
|
|
2025-09-20 16:50:45 +08:00
|
|
|
|
# 检查登录频率限制
|
|
|
|
|
if not _check_login_rate_limit():
|
|
|
|
|
logger.info(f"登录频率过高,{_login_window_seconds}秒内已尝试{_max_login_attempts}次,请稍后再试")
|
|
|
|
|
return None
|
|
|
|
|
|
2025-09-09 10:41:27 +08:00
|
|
|
|
# 检查是否有缓存的token且未过期
|
|
|
|
|
if _cached_token and _token_expire_time:
|
|
|
|
|
current_time = datetime.now().timestamp() * 1000 # 转换为毫秒
|
|
|
|
|
if current_time < _token_expire_time - 60000: # 提前1分钟刷新
|
|
|
|
|
logger.debug("使用缓存的token")
|
|
|
|
|
return _cached_token
|
|
|
|
|
|
|
|
|
|
# 构造请求参数
|
|
|
|
|
request_data = LoginRequest(
|
|
|
|
|
username=username,
|
|
|
|
|
password=password
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.LOGIN_PATH}"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {
|
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try:
|
2025-09-20 16:50:45 +08:00
|
|
|
|
# 记录登录尝试
|
|
|
|
|
_record_login_attempt()
|
|
|
|
|
|
2025-09-09 10:41:27 +08:00
|
|
|
|
logger.info(f"正在获取登录token,用户: {username}")
|
|
|
|
|
logger.debug(f"登录请求参数: {request_data.model_dump_json()}")
|
|
|
|
|
|
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
|
|
|
async with session.post(
|
|
|
|
|
url,
|
|
|
|
|
json=request_data.model_dump(),
|
|
|
|
|
headers=headers
|
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.text()
|
|
|
|
|
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
response_data = json.loads(response_text)
|
|
|
|
|
|
|
|
|
|
if response_data.get("success", False):
|
|
|
|
|
token = response_data.get("result", {}).get("token")
|
|
|
|
|
if token:
|
|
|
|
|
# 缓存token,设置过期时间为1小时后
|
|
|
|
|
_cached_token = token
|
|
|
|
|
_token_expire_time = datetime.now().timestamp() * 1000 + 3600000 # 1小时后过期
|
|
|
|
|
|
|
|
|
|
logger.info("成功获取登录token")
|
|
|
|
|
logger.debug(f"Token: {token[:20]}...") # 只记录token的前20个字符
|
|
|
|
|
return token
|
|
|
|
|
else:
|
|
|
|
|
logger.error("登录响应中未找到token")
|
|
|
|
|
return None
|
|
|
|
|
else:
|
|
|
|
|
logger.error(f"登录失败: {response_data.get('message', '未知错误')}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析登录响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
except aiohttp.ClientError as e:
|
|
|
|
|
logger.error(f"调用登录接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"获取登录token时发生错误: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def refresh_token_if_needed() -> Optional[str]:
|
|
|
|
|
"""
|
|
|
|
|
根据需要刷新token
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[str]: 有效的认证令牌,如果获取失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
global _cached_token, _token_expire_time
|
|
|
|
|
|
|
|
|
|
# 检查token是否即将过期(提前5分钟刷新)
|
|
|
|
|
if _token_expire_time:
|
|
|
|
|
current_time = datetime.now().timestamp() * 1000
|
|
|
|
|
if current_time >= _token_expire_time - 300000: # 5分钟内过期
|
|
|
|
|
logger.info("Token即将过期,正在刷新...")
|
|
|
|
|
return await get_login_token()
|
|
|
|
|
|
|
|
|
|
# 如果没有token,获取新的
|
|
|
|
|
if not _cached_token:
|
|
|
|
|
logger.info("没有缓存的token,正在获取...")
|
|
|
|
|
return await get_login_token()
|
|
|
|
|
|
|
|
|
|
return _cached_token
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def clear_cached_token():
|
|
|
|
|
"""
|
|
|
|
|
清除缓存的token
|
|
|
|
|
"""
|
|
|
|
|
global _cached_token, _token_expire_time
|
|
|
|
|
_cached_token = None
|
|
|
|
|
_token_expire_time = None
|
|
|
|
|
logger.info("已清除缓存的token")
|
|
|
|
|
|
|
|
|
|
|
2025-09-10 15:45:35 +08:00
|
|
|
|
def get_login_token_sync(username: str = "vwed", password: str = "vwed_123456") -> Optional[str]:
|
|
|
|
|
"""
|
|
|
|
|
获取登录token (同步版本)
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
username: 用户名,默认为"vwed"
|
|
|
|
|
password: 密码,默认为"vwed_123456"
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[str]: 认证令牌,如果获取失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
global _cached_token, _token_expire_time
|
|
|
|
|
|
2025-09-20 16:50:45 +08:00
|
|
|
|
# 检查登录频率限制
|
|
|
|
|
if not _check_login_rate_limit():
|
|
|
|
|
logger.info(f"登录频率过高,{_login_window_seconds}秒内已尝试{_max_login_attempts}次,请稍后再试")
|
|
|
|
|
return None
|
|
|
|
|
|
2025-09-10 15:45:35 +08:00
|
|
|
|
# 检查是否有缓存的token且未过期
|
|
|
|
|
if _cached_token and _token_expire_time:
|
|
|
|
|
current_time = datetime.now().timestamp() * 1000 # 转换为毫秒
|
|
|
|
|
if current_time < _token_expire_time - 60000: # 提前1分钟刷新
|
|
|
|
|
logger.debug("使用缓存的token")
|
|
|
|
|
return _cached_token
|
|
|
|
|
|
|
|
|
|
# 构造请求参数
|
|
|
|
|
request_data = LoginRequest(
|
|
|
|
|
username=username,
|
|
|
|
|
password=password
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.LOGIN_PATH}"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {
|
|
|
|
|
"Content-Type": "application/json",
|
2025-09-12 16:15:13 +08:00
|
|
|
|
"X-Tenant-Id": "1000"
|
2025-09-10 15:45:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try:
|
2025-09-20 16:50:45 +08:00
|
|
|
|
# 记录登录尝试
|
|
|
|
|
_record_login_attempt()
|
|
|
|
|
|
2025-09-10 15:45:35 +08:00
|
|
|
|
logger.info(f"正在获取登录token,用户: {username}")
|
|
|
|
|
logger.debug(f"登录请求参数: {request_data.model_dump_json()}")
|
|
|
|
|
|
|
|
|
|
response = requests.post(
|
|
|
|
|
url,
|
|
|
|
|
json=request_data.model_dump(),
|
|
|
|
|
headers=headers,
|
|
|
|
|
timeout=TFApiConfig.TIMEOUT
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
response_data = response.json()
|
|
|
|
|
|
|
|
|
|
if response_data.get("success", False):
|
|
|
|
|
token = response_data.get("result", {}).get("token")
|
|
|
|
|
if token:
|
|
|
|
|
# 缓存token,设置过期时间为1小时后
|
|
|
|
|
_cached_token = token
|
|
|
|
|
_token_expire_time = datetime.now().timestamp() * 1000 + 3600000 # 1小时后过期
|
|
|
|
|
|
|
|
|
|
logger.info("成功获取登录token")
|
|
|
|
|
logger.debug(f"Token: {token[:20]}...") # 只记录token的前20个字符
|
|
|
|
|
return token
|
|
|
|
|
else:
|
|
|
|
|
logger.error("登录响应中未找到token")
|
|
|
|
|
return None
|
|
|
|
|
else:
|
|
|
|
|
logger.error(f"登录失败: {response_data.get('message', '未知错误')}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
logger.error(f"解析登录响应JSON失败: {response.text}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
except requests.RequestException as e:
|
|
|
|
|
logger.error(f"调用登录接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"获取登录token时发生错误: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def refresh_token_if_needed_sync() -> Optional[str]:
|
|
|
|
|
"""
|
|
|
|
|
根据需要刷新token (同步版本)
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[str]: 有效的认证令牌,如果获取失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
global _cached_token, _token_expire_time
|
|
|
|
|
|
|
|
|
|
# 检查token是否即将过期(提前5分钟刷新)
|
|
|
|
|
if _token_expire_time:
|
|
|
|
|
current_time = datetime.now().timestamp() * 1000
|
|
|
|
|
if current_time >= _token_expire_time - 300000: # 5分钟内过期
|
|
|
|
|
logger.info("Token即将过期,正在刷新...")
|
|
|
|
|
return get_login_token_sync()
|
|
|
|
|
|
|
|
|
|
# 如果没有token,获取新的
|
|
|
|
|
if not _cached_token:
|
|
|
|
|
logger.info("没有缓存的token,正在获取...")
|
|
|
|
|
return get_login_token_sync()
|
|
|
|
|
|
|
|
|
|
return _cached_token
|
|
|
|
|
|
|
|
|
|
|
2025-09-29 09:35:08 +08:00
|
|
|
|
async def get_amr_charge_params(token: str = None) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
查询所有机器人充电阈值
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[Dict[str, Any]]: 充电阈值信息响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
logger.info("正在查询所有机器人充电阈值")
|
|
|
|
|
|
|
|
|
|
# 使用新的AMR列表接口
|
|
|
|
|
response = await get_amr_list(token)
|
|
|
|
|
if not response or not response.get("success"):
|
|
|
|
|
logger.warning("获取AMR列表失败,无法查询充电阈值")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
# 提取充电相关参数并转换为蛇形命名
|
|
|
|
|
amr_list = response.get("result", {}).get("records", [])
|
|
|
|
|
charge_params = []
|
|
|
|
|
|
|
|
|
|
for amr in amr_list:
|
|
|
|
|
charge_info = {
|
|
|
|
|
"id": amr.get("id", ""),
|
|
|
|
|
"name": amr.get("name", ""),
|
|
|
|
|
"exchange_power": amr.get("exchangePower", 0),
|
|
|
|
|
"task_power": amr.get("taskPower", 0),
|
|
|
|
|
"charge_power": amr.get("chargePower", 0),
|
|
|
|
|
"min_power": amr.get("minPower", 0),
|
|
|
|
|
"max_power": amr.get("maxPower", 0)
|
|
|
|
|
}
|
|
|
|
|
charge_params.append(charge_info)
|
|
|
|
|
|
|
|
|
|
result = {
|
|
|
|
|
"success": True,
|
|
|
|
|
"message": "success",
|
|
|
|
|
"result": charge_params
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.info(f"成功查询到 {len(charge_params)} 个机器人的充电阈值")
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"查询所有机器人充电阈值异常: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def get_amr_charge_params_by_vehicles(vehicles: list, token: str = None) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
查询指定机器人充电阈值
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
vehicles: 机器人名称列表
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[Dict[str, Any]]: 充电阈值信息响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
logger.info(f"正在查询指定机器人充电阈值: {vehicles}")
|
|
|
|
|
|
|
|
|
|
# 使用新的AMR列表接口
|
|
|
|
|
response = await get_amr_list(token)
|
|
|
|
|
if not response or not response.get("success"):
|
|
|
|
|
logger.warning("获取AMR列表失败,无法查询充电阈值")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
# 提取充电相关参数并转换为蛇形命名,同时筛选指定的机器人
|
|
|
|
|
amr_list = response.get("result", {}).get("records", [])
|
|
|
|
|
filtered_params = []
|
|
|
|
|
|
|
|
|
|
for vehicle_name in vehicles:
|
|
|
|
|
for amr in amr_list:
|
|
|
|
|
if amr.get("name") == vehicle_name:
|
|
|
|
|
charge_info = {
|
|
|
|
|
"id": amr.get("id", ""),
|
|
|
|
|
"name": amr.get("name", ""),
|
|
|
|
|
"exchange_power": amr.get("exchangePower", 0),
|
|
|
|
|
"task_power": amr.get("taskPower", 0),
|
|
|
|
|
"charge_power": amr.get("chargePower", 0),
|
|
|
|
|
"min_power": amr.get("minPower", 0),
|
|
|
|
|
"max_power": amr.get("maxPower", 0)
|
|
|
|
|
}
|
|
|
|
|
filtered_params.append(charge_info)
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
result = {
|
|
|
|
|
"success": True,
|
|
|
|
|
"message": "success",
|
|
|
|
|
"result": filtered_params
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.info(f"成功查询到 {len(filtered_params)} 个指定机器人的充电阈值")
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"查询指定机器人充电阈值异常: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def get_amr_detail(amr_id: str, token: str = None) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
获取AMR详情
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
amr_id: 机器人ID
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[Dict[str, Any]]: AMR详情响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
# 调用接口
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}/amr/{amr_id}"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
|
|
|
|
if token:
|
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
logger.info(f"正在获取AMR详情: {amr_id}")
|
|
|
|
|
|
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
|
|
|
async with session.get(
|
|
|
|
|
url,
|
|
|
|
|
headers=headers
|
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.text()
|
|
|
|
|
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
response_data = json.loads(response_text)
|
|
|
|
|
|
|
|
|
|
if response_data.get("success", False):
|
|
|
|
|
logger.info(f"成功获取AMR详情: {amr_id}")
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(f"获取AMR详情失败: {response_data.get('message', '未知错误')}")
|
|
|
|
|
|
|
|
|
|
return response_data
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"调用获取AMR详情接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def modify_amr_charge_param(amr_id: str, charge_params: dict, token: str = None) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
修改机器人充电阈值
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
amr_id: 机器人ID
|
|
|
|
|
charge_params: 充电参数字典,包含要修改的参数
|
|
|
|
|
token: 认证令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Optional[Dict[str, Any]]: 修改结果响应,如果请求失败则返回None
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# 首先获取AMR详情,获取所有必填参数的当前值
|
|
|
|
|
amr_detail_response = await get_amr_detail(amr_id, token)
|
|
|
|
|
if not amr_detail_response or not amr_detail_response.get("success"):
|
|
|
|
|
logger.error(f"获取AMR详情失败,无法修改充电参数: {amr_id}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
amr_detail = amr_detail_response.get("result", {})
|
|
|
|
|
|
|
|
|
|
# 构建完整的修改参数,包含所有必填项
|
|
|
|
|
api_params = {
|
|
|
|
|
# 必填项 - 从AMR详情获取当前值
|
|
|
|
|
"brandId": amr_detail.get("brandId", ""),
|
|
|
|
|
"typeId": amr_detail.get("typeId", ""),
|
|
|
|
|
"mapId": amr_detail.get("sceneId", ""), # sceneId对应mapId
|
|
|
|
|
"ipAddress": amr_detail.get("ipAddress", ""),
|
|
|
|
|
"name": amr_detail.get("name", ""), # 添加名称字段
|
|
|
|
|
"isSimulation": amr_detail.get("isSimulation", 0),
|
|
|
|
|
"length": amr_detail.get("length", 0.0),
|
|
|
|
|
"width": amr_detail.get("width", 0.0),
|
|
|
|
|
# 充电参数 - 当前值作为默认值
|
|
|
|
|
"minPower": amr_detail.get("minPower", 0),
|
|
|
|
|
"maxPower": amr_detail.get("maxPower", 0),
|
|
|
|
|
"chargePower": amr_detail.get("chargePower", 0),
|
|
|
|
|
"taskPower": amr_detail.get("taskPower", 0),
|
|
|
|
|
"exchangePower": amr_detail.get("exchangePower", 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 将蛇形命名转换为驼峰命名,并用用户传入的参数覆盖对应字段
|
|
|
|
|
field_mapping = {
|
|
|
|
|
"exchange_power": "exchangePower",
|
|
|
|
|
"task_power": "taskPower",
|
|
|
|
|
"charge_power": "chargePower",
|
|
|
|
|
"min_power": "minPower",
|
|
|
|
|
"max_power": "maxPower"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 用用户传入的充电参数覆盖默认值
|
|
|
|
|
for key, value in charge_params.items():
|
|
|
|
|
if key in field_mapping:
|
|
|
|
|
api_params[field_mapping[key]] = value
|
|
|
|
|
else:
|
|
|
|
|
# 保持原有参数
|
|
|
|
|
api_params[key] = value
|
|
|
|
|
|
|
|
|
|
# 构建URL
|
|
|
|
|
url = f"{TFApiConfig.BASE_URL}/amr/{amr_id}"
|
|
|
|
|
|
|
|
|
|
# 构建请求头
|
|
|
|
|
headers = {}
|
|
|
|
|
if token:
|
|
|
|
|
headers[TFApiConfig.TOKEN_HEADER] = token
|
|
|
|
|
headers["x-tenant-id"] = "1000"
|
|
|
|
|
headers["Content-Type"] = "application/json"
|
|
|
|
|
|
|
|
|
|
logger.info(f"正在修改机器人参数: {amr_id}, 参数: {api_params}")
|
|
|
|
|
|
|
|
|
|
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
|
|
|
async with session.put(
|
|
|
|
|
url,
|
|
|
|
|
json=api_params,
|
|
|
|
|
headers=headers
|
|
|
|
|
) as response:
|
|
|
|
|
# 读取响应内容
|
|
|
|
|
response_text = await response.text()
|
|
|
|
|
|
|
|
|
|
# 尝试解析JSON
|
|
|
|
|
try:
|
|
|
|
|
response_data = json.loads(response_text)
|
|
|
|
|
|
|
|
|
|
if response_data.get("success", False):
|
|
|
|
|
logger.info(f"成功修改机器人参数: {amr_id}")
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(f"修改机器人参数失败: {response_data.get('message', '未知错误')}")
|
|
|
|
|
|
|
|
|
|
return response_data
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.error(f"解析响应JSON失败: {response_text}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"调用修改机器人参数接口失败: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
2025-07-30 15:11:59 +08:00
|
|
|
|
# import asyncio
|
|
|
|
|
# task_block_id = "1947858858043117570"
|
|
|
|
|
# station_name = "AP1"
|
|
|
|
|
# action = "drop"
|
|
|
|
|
# token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTE5MTc3MTQsInVzZXJuYW1lIjoiYWRtaW4ifQ.9NcGuKCeYxOBMU-3ppuF7sPVQvZ7YyqQGPipnOdlE94"
|
|
|
|
|
# param = {"param": "JackLoad"}
|
|
|
|
|
#
|
|
|
|
|
# # asyncio.run(create_task("task_record_id123", "名称", True, 1, "", token, "1936411520272753371", 1))
|
|
|
|
|
# asyncio.run(add_action(task_block_id, station_name, action, token, param))
|