289 lines
10 KiB
Python
289 lines
10 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
中继器核心服务模块
|
|||
|
|
实现持续运行的中继器服务,处理与平台的通信
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import json
|
|||
|
|
import logging
|
|||
|
|
import time
|
|||
|
|
import threading
|
|||
|
|
import asyncio
|
|||
|
|
import websockets
|
|||
|
|
from datetime import datetime
|
|||
|
|
from queue import Queue, Empty
|
|||
|
|
|
|||
|
|
from config import config
|
|||
|
|
from internal.protocol.lora import LoRaHandler
|
|||
|
|
from internal.protocol.websocket import WebSocketClient
|
|||
|
|
|
|||
|
|
# 配置日志
|
|||
|
|
logging.basicConfig(level=logging.INFO)
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class RelayService:
|
|||
|
|
"""中继器服务类"""
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
"""初始化中继器服务"""
|
|||
|
|
self.device_id = config._config.get('relay', {}).get('device_id', '1')
|
|||
|
|
self.name = config._config.get('relay', {}).get('name', '默认中继器')
|
|||
|
|
self.running = False
|
|||
|
|
self.lora_handler = None
|
|||
|
|
self.websocket_client = None
|
|||
|
|
self.command_queue = Queue()
|
|||
|
|
self.response_queue = Queue()
|
|||
|
|
self.loop = None
|
|||
|
|
|
|||
|
|
logger.info(f"初始化中继器服务: ID={self.device_id}, 名称={self.name}")
|
|||
|
|
|
|||
|
|
def initialize(self):
|
|||
|
|
"""初始化中继器服务"""
|
|||
|
|
logger.info("开始初始化中继器服务")
|
|||
|
|
|
|||
|
|
# 初始化LoRa协议处理器
|
|||
|
|
self.lora_handler = LoRaHandler(config)
|
|||
|
|
self.lora_handler.initialize()
|
|||
|
|
|
|||
|
|
logger.info("中继器服务初始化完成")
|
|||
|
|
|
|||
|
|
def start(self):
|
|||
|
|
"""启动中继器服务"""
|
|||
|
|
logger.info("启动中继器服务")
|
|||
|
|
self.running = True
|
|||
|
|
|
|||
|
|
# 在新线程中运行异步事件循环
|
|||
|
|
websocket_thread = threading.Thread(target=self._run_websocket_loop, daemon=True)
|
|||
|
|
websocket_thread.start()
|
|||
|
|
|
|||
|
|
# 启动命令处理线程
|
|||
|
|
command_thread = threading.Thread(target=self._command_loop, daemon=True)
|
|||
|
|
command_thread.start()
|
|||
|
|
|
|||
|
|
# 主循环
|
|||
|
|
self._main_loop()
|
|||
|
|
|
|||
|
|
def stop(self):
|
|||
|
|
"""停止中继器服务"""
|
|||
|
|
logger.info("停止中继器服务")
|
|||
|
|
self.running = False
|
|||
|
|
|
|||
|
|
# 断开WebSocket连接
|
|||
|
|
if self.websocket_client:
|
|||
|
|
asyncio.run_coroutine_threadsafe(
|
|||
|
|
self.websocket_client.disconnect(),
|
|||
|
|
self.loop
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def _run_websocket_loop(self):
|
|||
|
|
"""运行WebSocket事件循环"""
|
|||
|
|
# 创建新的事件循环
|
|||
|
|
self.loop = asyncio.new_event_loop()
|
|||
|
|
asyncio.set_event_loop(self.loop)
|
|||
|
|
|
|||
|
|
# 运行事件循环
|
|||
|
|
self.loop.run_until_complete(self._websocket_main())
|
|||
|
|
|
|||
|
|
async def _websocket_main(self):
|
|||
|
|
"""WebSocket主协程"""
|
|||
|
|
# 创建WebSocket客户端
|
|||
|
|
self.websocket_client = WebSocketClient(self)
|
|||
|
|
|
|||
|
|
# 连接到平台
|
|||
|
|
if await self.websocket_client.connect():
|
|||
|
|
# 启动心跳任务
|
|||
|
|
heartbeat_task = asyncio.create_task(self._heartbeat_routine())
|
|||
|
|
|
|||
|
|
# 监听平台消息
|
|||
|
|
await self.websocket_client.listen()
|
|||
|
|
|
|||
|
|
# 取消心跳任务
|
|||
|
|
heartbeat_task.cancel()
|
|||
|
|
else:
|
|||
|
|
logger.error("无法连接到平台")
|
|||
|
|
|
|||
|
|
async def _heartbeat_routine(self):
|
|||
|
|
"""心跳协程"""
|
|||
|
|
logger.info("心跳协程启动")
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
while self.running:
|
|||
|
|
if self.websocket_client and self.websocket_client.is_connected():
|
|||
|
|
await self.websocket_client.send_heartbeat()
|
|||
|
|
|
|||
|
|
# 每30秒发送一次心跳
|
|||
|
|
await asyncio.sleep(30)
|
|||
|
|
except asyncio.CancelledError:
|
|||
|
|
logger.info("心跳协程被取消")
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"心跳协程发生错误: {e}")
|
|||
|
|
|
|||
|
|
logger.info("心跳协程停止")
|
|||
|
|
|
|||
|
|
def _main_loop(self):
|
|||
|
|
"""主循环"""
|
|||
|
|
logger.info("中继器服务主循环开始")
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
while self.running:
|
|||
|
|
# 短暂休眠以避免过度占用CPU
|
|||
|
|
time.sleep(0.1)
|
|||
|
|
except KeyboardInterrupt:
|
|||
|
|
logger.info("收到中断信号,正在停止服务...")
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"主循环发生错误: {e}")
|
|||
|
|
finally:
|
|||
|
|
self.stop()
|
|||
|
|
logger.info("中继器服务已停止")
|
|||
|
|
|
|||
|
|
def _command_loop(self):
|
|||
|
|
"""命令处理循环"""
|
|||
|
|
logger.info("命令处理线程启动")
|
|||
|
|
|
|||
|
|
while self.running:
|
|||
|
|
try:
|
|||
|
|
# 处理命令队列中的指令
|
|||
|
|
try:
|
|||
|
|
command_data = self.command_queue.get(timeout=1)
|
|||
|
|
self._process_command(command_data)
|
|||
|
|
self.command_queue.task_done()
|
|||
|
|
except Empty:
|
|||
|
|
# 队列为空,继续循环
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"命令处理线程发生错误: {e}")
|
|||
|
|
|
|||
|
|
logger.info("命令处理线程停止")
|
|||
|
|
|
|||
|
|
def _process_command(self, command_data):
|
|||
|
|
"""处理平台指令"""
|
|||
|
|
try:
|
|||
|
|
logger.info(f"处理平台指令: {command_data}")
|
|||
|
|
|
|||
|
|
command_type = command_data.get('type')
|
|||
|
|
command_name = command_data.get('command')
|
|||
|
|
|
|||
|
|
if command_type == 'command':
|
|||
|
|
if command_name == 'control_device':
|
|||
|
|
self._handle_control_device(command_data)
|
|||
|
|
elif command_name == 'query_device_status':
|
|||
|
|
self._handle_query_device_status(command_data)
|
|||
|
|
elif command_name == 'query_all_device_status':
|
|||
|
|
self._handle_query_all_device_status(command_data)
|
|||
|
|
else:
|
|||
|
|
logger.warning(f"未知指令: {command_name}")
|
|||
|
|
self._send_error_response(command_data, f"未知指令: {command_name}")
|
|||
|
|
elif command_type == 'heartbeat':
|
|||
|
|
self._handle_heartbeat(command_data)
|
|||
|
|
elif command_type == 'system':
|
|||
|
|
# 处理系统消息
|
|||
|
|
logger.info(f"收到系统消息: {command_name}")
|
|||
|
|
else:
|
|||
|
|
logger.warning(f"未知消息类型: {command_type}")
|
|||
|
|
self._send_error_response(command_data, f"未知消息类型: {command_type}")
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"处理指令时发生错误: {e}")
|
|||
|
|
self._send_error_response(command_data, f"处理指令时发生错误: {str(e)}")
|
|||
|
|
|
|||
|
|
def _handle_control_device(self, command_data):
|
|||
|
|
"""处理设备控制指令"""
|
|||
|
|
data = command_data.get('data', {})
|
|||
|
|
device_id = data.get('device_id')
|
|||
|
|
action = data.get('action')
|
|||
|
|
|
|||
|
|
logger.info(f"控制设备: ID={device_id}, 动作={action}")
|
|||
|
|
|
|||
|
|
if self.lora_handler and config.simulation_enabled:
|
|||
|
|
result = self.lora_handler.send_command('control_device', data)
|
|||
|
|
# 发送响应到平台
|
|||
|
|
self._send_command_response(command_data, result)
|
|||
|
|
|
|||
|
|
def _handle_query_device_status(self, command_data):
|
|||
|
|
"""处理查询设备状态指令"""
|
|||
|
|
data = command_data.get('data', {})
|
|||
|
|
device_id = data.get('device_id')
|
|||
|
|
|
|||
|
|
logger.info(f"查询设备状态: ID={device_id}")
|
|||
|
|
|
|||
|
|
if self.lora_handler and config.simulation_enabled:
|
|||
|
|
result = self.lora_handler.send_command('query_device_status', data)
|
|||
|
|
# 发送响应到平台
|
|||
|
|
self._send_command_response(command_data, result)
|
|||
|
|
|
|||
|
|
def _handle_query_all_device_status(self, command_data):
|
|||
|
|
"""处理查询所有设备状态指令"""
|
|||
|
|
logger.info("查询所有设备状态")
|
|||
|
|
|
|||
|
|
if self.lora_handler and config.simulation_enabled:
|
|||
|
|
result = self.lora_handler.send_command('query_all_device_status', {})
|
|||
|
|
# 发送响应到平台
|
|||
|
|
self._send_command_response(command_data, result)
|
|||
|
|
|
|||
|
|
def _handle_heartbeat(self, command_data):
|
|||
|
|
"""处理心跳消息"""
|
|||
|
|
logger.info("收到平台心跳消息")
|
|||
|
|
|
|||
|
|
# 发送心跳响应
|
|||
|
|
response = {
|
|||
|
|
"type": "response",
|
|||
|
|
"command": "heartbeat",
|
|||
|
|
"status": "success",
|
|||
|
|
"message": "心跳响应",
|
|||
|
|
"timestamp": datetime.utcnow().isoformat() + 'Z'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self._send_response(response)
|
|||
|
|
|
|||
|
|
def _send_command_response(self, command_data, result_data):
|
|||
|
|
"""发送指令响应"""
|
|||
|
|
response = {
|
|||
|
|
"type": "response",
|
|||
|
|
"command": command_data.get('command'),
|
|||
|
|
"data": result_data,
|
|||
|
|
"timestamp": datetime.utcnow().isoformat() + 'Z'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self._send_response(response)
|
|||
|
|
|
|||
|
|
def _send_error_response(self, command_data, error_message):
|
|||
|
|
"""发送错误响应"""
|
|||
|
|
response = {
|
|||
|
|
"type": "response",
|
|||
|
|
"command": command_data.get('command', 'unknown'),
|
|||
|
|
"status": "failed",
|
|||
|
|
"message": error_message,
|
|||
|
|
"timestamp": datetime.utcnow().isoformat() + 'Z'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self._send_response(response)
|
|||
|
|
|
|||
|
|
def _send_response(self, response):
|
|||
|
|
"""发送响应到平台"""
|
|||
|
|
if self.websocket_client and self.websocket_client.is_connected():
|
|||
|
|
# 在事件循环中发送响应
|
|||
|
|
asyncio.run_coroutine_threadsafe(
|
|||
|
|
self.websocket_client.send_message(response),
|
|||
|
|
self.loop
|
|||
|
|
)
|
|||
|
|
else:
|
|||
|
|
logger.warning("WebSocket未连接,无法发送响应")
|
|||
|
|
|
|||
|
|
def handle_platform_command(self, command_json):
|
|||
|
|
"""
|
|||
|
|
处理来自平台的指令(供外部调用)
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
command_json (str): JSON格式的指令字符串
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
command_data = json.loads(command_json)
|
|||
|
|
self.command_queue.put(command_data)
|
|||
|
|
except json.JSONDecodeError as e:
|
|||
|
|
logger.error(f"无效的JSON格式: {e}")
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"处理平台指令时发生错误: {e}")
|