You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

186 lines
7.0 KiB

import asyncio
import time
import serial
from typing import Optional
from app.core.device_manager import device_manager
from app.schemas.at import ATCommandRequest, ATCommandResponse, SerialConnectionInfo
from app.utils.log import get_logger
logger = get_logger(__name__)
class ATService:
"""AT指令服务类 - 实现串口AT指令相关操作"""
def __init__(self):
self.connections = {} # 存储串口连接
async def _get_connection(self, device_id: str) -> serial.Serial:
"""获取串口连接"""
if device_id in self.connections:
return self.connections[device_id]
# 从设备管理器获取连接信息
device = await device_manager.get_device(device_id)
if not device:
raise ValueError(f"设备 {device_id} 不存在")
connection_info = device.connection_info
# 创建串口连接
try:
ser = serial.Serial(
port=connection_info.get('port'),
baudrate=connection_info.get('baudrate', 115200),
timeout=connection_info.get('timeout', 5),
bytesize=connection_info.get('bytesize', 8),
parity=connection_info.get('parity', 'N'),
stopbits=connection_info.get('stopbits', 1)
)
# 存储连接
self.connections[device_id] = ser
logger.info(f"串口连接建立成功: {device_id} -> {connection_info.get('port')}")
return ser
except Exception as e:
logger.error(f"串口连接失败: {device_id}, 错误: {e}")
raise ValueError(f"串口连接失败: {str(e)}")
async def send_at_command(self, device_id: str, command: str, timeout: int = 5,
wait_response: bool = True) -> ATCommandResponse:
"""发送AT指令"""
start_time = time.time()
try:
ser = await self._get_connection(device_id)
# 确保命令以\r\n结尾
if not command.endswith('\r\n'):
command = command + '\r\n'
# 清空缓冲区
ser.reset_input_buffer()
ser.reset_output_buffer()
# 发送命令
ser.write(command.encode('utf-8'))
ser.flush()
response = ""
if wait_response:
# 等待响应
start_timeout = time.time()
while time.time() - start_timeout < timeout:
if ser.in_waiting > 0:
line = ser.readline().decode('utf-8', errors='ignore').strip()
if line:
response += line + '\n'
# 检查是否收到完整响应(通常以OK或ERROR结尾)
if 'OK' in line or 'ERROR' in line:
break
await asyncio.sleep(0.1)
execution_time = time.time() - start_time
# 判断是否成功(通常OK表示成功)
success = 'OK' in response and 'ERROR' not in response
logger.info(f"AT指令执行完成: {device_id} -> {command.strip()}, 成功: {success}")
return ATCommandResponse(
success=success,
command=command.strip(),
response=response.strip(),
execution_time=execution_time
)
except Exception as e:
execution_time = time.time() - start_time
logger.error(f"AT指令执行失败: {device_id} -> {command}, 错误: {e}")
return ATCommandResponse(
success=False,
command=command,
response=str(e),
execution_time=execution_time
)
async def send_multiple_commands(self, device_id: str, commands: list,
delay_between: float = 1.0) -> list:
"""发送多个AT指令"""
results = []
for command in commands:
result = await self.send_at_command(device_id, command)
results.append(result)
# 命令间延迟
if delay_between > 0:
await asyncio.sleep(delay_between)
return results
async def test_connection(self, device_id: str) -> bool:
"""测试串口连接"""
try:
ser = await self._get_connection(device_id)
# 发送测试命令
result = await self.send_at_command(device_id, "AT", timeout=3)
return result.success
except Exception as e:
logger.error(f"串口连接测试失败: {device_id}, 错误: {e}")
return False
async def get_device_info(self, device_id: str) -> dict:
"""获取设备信息"""
try:
# 发送AT+CGMI获取制造商信息
manufacturer = await self.send_at_command(device_id, "AT+CGMI")
# 发送AT+CGMM获取型号信息
model = await self.send_at_command(device_id, "AT+CGMM")
# 发送AT+CGSN获取序列号
serial_number = await self.send_at_command(device_id, "AT+CGSN")
return {
"device_id": device_id,
"manufacturer": manufacturer.response if manufacturer.success else "Unknown",
"model": model.response if model.success else "Unknown",
"serial_number": serial_number.response if serial_number.success else "Unknown",
"status": "online" if manufacturer.success else "error"
}
except Exception as e:
logger.error(f"获取设备信息失败: {device_id}, 错误: {e}")
return {
"device_id": device_id,
"manufacturer": "Unknown",
"model": "Unknown",
"serial_number": "Unknown",
"status": "error"
}
async def close_connection(self, device_id: str) -> bool:
"""关闭串口连接"""
try:
if device_id in self.connections:
ser = self.connections[device_id]
ser.close()
del self.connections[device_id]
logger.info(f"串口连接已关闭: {device_id}")
return True
return False
except Exception as e:
logger.error(f"关闭串口连接失败: {device_id}, 错误: {e}")
return False
async def list_available_ports(self) -> list:
"""列出可用的串口"""
try:
import serial.tools.list_ports
ports = serial.tools.list_ports.comports()
return [port.device for port in ports]
except Exception as e:
logger.error(f"获取可用串口失败: {e}")
return []