diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..63e2768 --- /dev/null +++ b/config/__init__.py @@ -0,0 +1,6 @@ +# 配置文件目录 +# 存放不同环境的配置文件 + +from .config import Config, config + +__all__ = ['Config', 'config'] \ No newline at end of file diff --git a/config/config.py b/config/config.py new file mode 100644 index 0000000..097631e --- /dev/null +++ b/config/config.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +配置模块,用于加载和管理中继器的配置 +""" + +import yaml +import os + + +class Config: + """配置类,用于加载和访问配置项""" + + def __init__(self, config_file="config/config.yaml"): + """ + 初始化配置类 + + Args: + config_file (str): 配置文件路径 + """ + # 获取项目根目录 + project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + config_path = os.path.join(project_root, config_file) + + # 加载配置文件 + with open(config_path, 'r', encoding='utf-8') as f: + self._config = yaml.safe_load(f) + + @property + def simulation_enabled(self): + """获取模拟模式是否启用""" + return self._config.get('simulation', {}).get('enabled', False) + + @property + def simulation_controllers(self): + """获取模拟区域主控设备列表""" + return self._config.get('simulation', {}).get('controllers', []) + + @property + def simulation_devices(self): + """获取模拟普通设备列表""" + return self._config.get('simulation', {}).get('devices', []) + + @property + def websocket_config(self): + """获取WebSocket配置""" + return self._config.get('websocket', {}) + + @property + def websocket_address(self): + """获取WebSocket服务器地址""" + return self.websocket_config.get('address', '127.0.0.1') + + @property + def websocket_port(self): + """获取WebSocket服务器端口""" + return self.websocket_config.get('port', 8080) + + +# 全局配置实例 +config = Config() \ No newline at end of file diff --git a/config/config.yaml b/config/config.yaml new file mode 100644 index 0000000..70cdf78 --- /dev/null +++ b/config/config.yaml @@ -0,0 +1,69 @@ +# 通信中继器配置文件 + +# 中继器配置 +relay: + # 中继设备唯一标识符,用于WebSocket连接时的身份标识 + device_id: "1" + # 中继器名称 + name: "猪场1" + +# WebSocket配置 +websocket: + # WebSocket服务器地址 + host: "localhost" + # WebSocket端口 + port: 8086 + # WebSocket请求超时时间(秒) + timeout: 5 + +# 模拟模式配置 +simulation: + # 是否开启模拟模式 + enabled: true + # 模拟区域主控设备列表(没有冒号的地址) + controllers: + - id: "2" + name: "猪舍1" + type: "pig_pen_controller" + lora_address: "121" + status: "running" + - id: "5" + name: "做料1" + type: "feed_mill_controller" + lora_address: "123" + status: "stopped" + - id: "8" + name: "猪舍2" + type: "pig_pen_controller" + lora_address: "122" + status: "running" + # 模拟普通设备列表(带冒号的地址,格式为总线号:总线地址) + devices: + - id: "3" + name: "风机1" + type: "fan" + rs485_bus: "1" + rs485_address: "12" + controller_id: "2" + status: "stopped" + - id: "9" + name: "风机2" + type: "fan" + rs485_bus: "1" + rs485_address: "11" + controller_id: "2" + status: "running" + - id: "10" + name: "水帘1" + type: "water_curtain" + rs485_bus: "1" + rs485_address: "11" + controller_id: "8" + status: "stopped" + - id: "11" + name: "风机3" + type: "fan" + rs485_bus: "2" + rs485_address: "21" + controller_id: "2" + status: "stopped" \ No newline at end of file diff --git a/internal/__init__.py b/internal/__init__.py new file mode 100644 index 0000000..1c5858b --- /dev/null +++ b/internal/__init__.py @@ -0,0 +1 @@ +# internal package \ No newline at end of file diff --git a/internal/core/__init__.py b/internal/core/__init__.py new file mode 100644 index 0000000..ecd2b36 --- /dev/null +++ b/internal/core/__init__.py @@ -0,0 +1,6 @@ +# 核心业务逻辑模块 +# 包含中继器的主要业务逻辑实现 + +from .relay import RelayService + +__all__ = ['RelayService'] \ No newline at end of file diff --git a/internal/core/relay.py b/internal/core/relay.py new file mode 100644 index 0000000..a099712 --- /dev/null +++ b/internal/core/relay.py @@ -0,0 +1,289 @@ +#!/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}") \ No newline at end of file diff --git a/internal/protocol/__init__.py b/internal/protocol/__init__.py new file mode 100644 index 0000000..4fea8ed --- /dev/null +++ b/internal/protocol/__init__.py @@ -0,0 +1,2 @@ +# 协议处理模块 +# 处理与各种通信协议相关的功能 \ No newline at end of file diff --git a/internal/protocol/base.py b/internal/protocol/base.py new file mode 100644 index 0000000..cf0d34c --- /dev/null +++ b/internal/protocol/base.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +协议处理基类模块 +""" + +import logging +from abc import ABC, abstractmethod + +# 配置日志 +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +class ProtocolHandler(ABC): + """协议处理基类""" + + def __init__(self, config): + """ + 初始化协议处理器 + + Args: + config: 配置对象 + """ + self.config = config + self.device_manager = None + logger.info(f"初始化 {self.__class__.__name__}") + + @abstractmethod + def initialize(self): + """初始化协议处理器""" + pass + + @abstractmethod + def send_command(self, command, data): + """ + 发送命令到设备 + + Args: + command (str): 命令类型 + data (dict): 命令数据 + + Returns: + dict: 命令执行结果 + """ + pass + + @abstractmethod + def receive_response(self): + """ + 接收设备响应 + + Returns: + dict: 设备响应数据 + """ + pass \ No newline at end of file diff --git a/internal/protocol/coap/__init__.py b/internal/protocol/coap/__init__.py new file mode 100644 index 0000000..635bf46 --- /dev/null +++ b/internal/protocol/coap/__init__.py @@ -0,0 +1,2 @@ +# CoAP协议处理模块 +# 处理受限应用协议,轻量级的RESTful协议,适用于资源受限设备 \ No newline at end of file diff --git a/internal/protocol/lora/__init__.py b/internal/protocol/lora/__init__.py new file mode 100644 index 0000000..05f3f11 --- /dev/null +++ b/internal/protocol/lora/__init__.py @@ -0,0 +1,6 @@ +# LoRa协议处理模块 +# 处理LoRa物理层通信相关功能 + +from .handler import LoRaHandler + +__all__ = ['LoRaHandler'] \ No newline at end of file diff --git a/internal/protocol/lora/handler.py b/internal/protocol/lora/handler.py new file mode 100644 index 0000000..78a70cd --- /dev/null +++ b/internal/protocol/lora/handler.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +LoRa协议处理模块 +处理LoRa物理层通信相关功能 +支持四级结构:平台->中继->区域主控->普通设备 +""" + +import logging +import time +import random +from ..base import ProtocolHandler + +# 配置日志 +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +class LoRaHandler(ProtocolHandler): + """LoRa协议处理器""" + + def __init__(self, config): + """ + 初始化LoRa协议处理器 + + Args: + config: 配置对象 + """ + super().__init__(config) + self.initialized = False + logger.info("初始化LoRa协议处理器") + + def initialize(self): + """初始化LoRa协议处理器""" + logger.info("开始初始化LoRa通信") + + # 模拟LoRa硬件初始化 + time.sleep(0.5) + self.initialized = True + + # 如果启用模拟模式,初始化设备管理器 + if self.config.simulation_enabled: + from internal.simulation import DeviceManager, AreaController, NormalDevice + self.device_manager = DeviceManager() + + # 根据配置创建模拟区域主控设备 + controllers = {} + for controller_config in self.config.simulation_controllers: + controller = AreaController( + device_id=controller_config['id'], + lora_address=controller_config['lora_address'], + status=controller_config.get('status', 'stopped') + ) + self.device_manager.add_controller(controller) + controllers[controller.device_id] = controller + + # 根据配置创建模拟普通设备,并关联到区域主控 + for device_config in self.config.simulation_devices: + device = NormalDevice( + device_id=device_config['id'], + device_type=device_config['type'], + rs485_bus=device_config['rs485_bus'], + rs485_address=device_config['rs485_address'], + controller_id=device_config['controller_id'], + status=device_config.get('status', 'stopped') + ) + self.device_manager.add_device(device) + + # 将设备添加到对应的区域主控中 + controller_id = device_config['controller_id'] + if controller_id in controllers: + controllers[controller_id].add_device(device) + + logger.info(f"模拟模式已启用,创建了 {len(controllers)} 个区域主控和 {len(self.config.simulation_devices)} 个普通设备") + else: + logger.info("真实模式,需要连接LoRa硬件") + + logger.info("LoRa协议处理器初始化完成") + + def send_command(self, command, data): + """ + 通过LoRa发送命令到设备 + + Args: + command (str): 命令类型 + data (dict): 命令数据 + + Returns: + dict: 命令执行结果 + """ + if not self.initialized: + logger.error("LoRa协议处理器未初始化") + return { + "status": "failed", + "message": "LoRa协议处理器未初始化" + } + + logger.info(f"通过LoRa发送命令: {command}, 数据: {data}") + + # 如果启用模拟模式,直接处理命令 + if self.config.simulation_enabled and self.device_manager: + result = self._handle_simulated_command(command, data) + logger.info(f"模拟命令处理结果: {result}") + return result + + # 真实模式下需要实际发送命令到LoRa设备 + logger.info("真实模式下发送命令到LoRa设备") + # 这里应该是实际的LoRa通信代码 + # 暂时返回模拟响应 + time.sleep(0.1) # 模拟通信延迟 + return { + "status": "success" if random.choice([True, False]) else "failed", + "message": "命令发送成功" if random.choice([True, False]) else "命令发送失败", + "data": { + "device_id": data.get("device_id", "unknown"), + "timestamp": time.time() + } + } + + def receive_response(self): + """ + 从LoRa接收设备响应 + + Returns: + dict: 设备响应数据 + """ + if not self.initialized: + logger.error("LoRa协议处理器未初始化") + return { + "status": "failed", + "message": "LoRa协议处理器未初始化" + } + + logger.info("从LoRa接收设备响应") + + # 如果启用模拟模式,返回模拟响应 + if self.config.simulation_enabled and self.device_manager: + # 在模拟模式下,我们假设命令已经直接处理完成,不需要单独接收响应 + logger.info("模拟模式下命令已直接处理完成") + return { + "status": "success", + "message": "模拟响应", + "data": {} + } + + # 真实模式下需要实际从LoRa设备接收响应 + logger.info("真实模式下从LoRa设备接收响应") + # 这里应该是实际的LoRa通信代码 + # 暂时返回模拟响应 + time.sleep(0.1) # 模拟通信延迟 + return { + "status": "success", + "message": "接收响应成功", + "data": { + "response_data": "sample_data", + "timestamp": time.time() + } + } + + def _handle_simulated_command(self, command, data): + """ + 处理模拟命令 + + Args: + command (str): 命令类型 + data (dict): 命令数据 + + Returns: + dict: 命令执行结果 + """ + logger.info(f"处理模拟命令: {command}") + + if command == "control_device": + device_id = data.get("device_id") + action = data.get("action") + return self.device_manager.control_device(device_id, action) + elif command == "query_device_status": + device_id = data.get("device_id") + return self.device_manager.query_device_status(device_id) + elif command == "query_all_device_status": + statuses = self.device_manager.query_all_device_status() + return { + "status": "success", + "message": "查询所有设备状态成功", + "data": statuses + } + else: + return { + "status": "failed", + "message": f"不支持的命令: {command}" + } \ No newline at end of file diff --git a/internal/protocol/lorawan/__init__.py b/internal/protocol/lorawan/__init__.py new file mode 100644 index 0000000..54a75d8 --- /dev/null +++ b/internal/protocol/lorawan/__init__.py @@ -0,0 +1,2 @@ +# LoRaWAN协议处理模块 +# 处理基于LoRa物理层的广域网协议功能,包括设备认证、加密和网络管理 \ No newline at end of file diff --git a/internal/protocol/lwm2m/__init__.py b/internal/protocol/lwm2m/__init__.py new file mode 100644 index 0000000..70b9850 --- /dev/null +++ b/internal/protocol/lwm2m/__init__.py @@ -0,0 +1,2 @@ +# LwM2M协议处理模块 +# 处理轻量级机器到机器协议,提供设备管理、固件更新等功能 \ No newline at end of file diff --git a/internal/protocol/senml/__init__.py b/internal/protocol/senml/__init__.py new file mode 100644 index 0000000..20c4d0d --- /dev/null +++ b/internal/protocol/senml/__init__.py @@ -0,0 +1,2 @@ +# SenML数据格式处理模块 +# 处理传感器标记语言,标准化的传感器数据表示格式 \ No newline at end of file diff --git a/internal/protocol/websocket/__init__.py b/internal/protocol/websocket/__init__.py new file mode 100644 index 0000000..699bea9 --- /dev/null +++ b/internal/protocol/websocket/__init__.py @@ -0,0 +1,6 @@ +# WebSocket通信处理模块 +# 处理与猪场主控的WebSocket通信 + +from .client import WebSocketClient + +__all__ = ['WebSocketClient'] \ No newline at end of file diff --git a/internal/protocol/websocket/client.py b/internal/protocol/websocket/client.py new file mode 100644 index 0000000..37e8a8e --- /dev/null +++ b/internal/protocol/websocket/client.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +WebSocket客户端模块 +用于与平台建立WebSocket连接并处理通信 +""" + +import asyncio +import json +import logging +import websockets +from datetime import datetime +from urllib.parse import urlencode + +from config import config + +# 配置日志 +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +class WebSocketClient: + """WebSocket客户端类""" + + def __init__(self, relay_service): + """ + 初始化WebSocket客户端 + + Args: + relay_service: 中继器服务实例 + """ + self.relay_service = relay_service + self.device_id = config._config.get('relay', {}).get('device_id', '1') + self.host = config._config.get('websocket', {}).get('host', 'localhost') + self.port = config._config.get('websocket', {}).get('port', 8086) + self.timeout = config._config.get('websocket', {}).get('timeout', 5) + self.websocket = None + self.connected = False + self.running = False + + logger.info(f"初始化WebSocket客户端: device_id={self.device_id}, host={self.host}, port={self.port}") + + async def connect(self): + """建立WebSocket连接""" + try: + # 构建连接URL + params = urlencode({'device_id': self.device_id}) + uri = f"ws://{self.host}:{self.port}/ws/device?{params}" + + logger.info(f"正在连接到平台: {uri}") + + # 建立WebSocket连接(移除了timeout参数以兼容新版本websockets库) + self.websocket = await websockets.connect(uri) + self.connected = True + self.running = True + + logger.info("成功连接到平台") + return True + + except Exception as e: + logger.error(f"连接平台失败: {e}") + self.connected = False + return False + + async def disconnect(self): + """断开WebSocket连接""" + logger.info("断开WebSocket连接") + self.running = False + + if self.websocket: + await self.websocket.close() + self.websocket = None + + self.connected = False + logger.info("WebSocket连接已断开") + + async def send_message(self, message): + """ + 发送消息到平台 + + Args: + message (dict): 要发送的消息 + """ + if not self.connected or not self.websocket: + logger.warning("WebSocket未连接,无法发送消息") + return False + + try: + message_str = json.dumps(message, ensure_ascii=False) + await self.websocket.send(message_str) + logger.debug(f"发送消息到平台: {message_str}") + return True + except Exception as e: + logger.error(f"发送消息失败: {e}") + return False + + async def listen(self): + """监听平台消息""" + if not self.connected or not self.websocket: + logger.error("WebSocket未连接,无法监听消息") + return + + logger.info("开始监听平台消息") + + try: + async for message in self.websocket: + try: + # 解析收到的消息 + message_data = json.loads(message) + logger.info(f"收到平台消息: {message_data}") + + # 将消息传递给中继器服务处理 + self.relay_service.handle_platform_command(message) + + except json.JSONDecodeError as e: + logger.error(f"解析平台消息失败: {e}") + except Exception as e: + logger.error(f"处理平台消息时发生错误: {e}") + + except websockets.exceptions.ConnectionClosed: + logger.warning("WebSocket连接已关闭") + self.connected = False + except Exception as e: + logger.error(f"监听平台消息时发生错误: {e}") + self.connected = False + + async def send_heartbeat(self): + """发送心跳消息""" + if not self.connected: + return False + + heartbeat_msg = { + "type": "heartbeat", + "device_id": self.device_id, + "timestamp": datetime.utcnow().isoformat() + 'Z' + } + + return await self.send_message(heartbeat_msg) + + def is_connected(self): + """检查是否已连接""" + return self.connected \ No newline at end of file diff --git a/internal/simulation/__init__.py b/internal/simulation/__init__.py new file mode 100644 index 0000000..9e8b7fa --- /dev/null +++ b/internal/simulation/__init__.py @@ -0,0 +1,6 @@ +# 模拟模块 +# 用于在没有实际设备的情况下模拟场内子系统设备 + +from .device_interface import SimulatedDevice, DeviceManager, AreaController, NormalDevice + +__all__ = ['SimulatedDevice', 'DeviceManager', 'AreaController', 'NormalDevice'] \ No newline at end of file diff --git a/internal/simulation/device_interface.py b/internal/simulation/device_interface.py new file mode 100644 index 0000000..04eecb7 --- /dev/null +++ b/internal/simulation/device_interface.py @@ -0,0 +1,480 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +设备模拟接口模块,用于模拟场内子系统设备 +支持四级结构:平台->中继->区域主控->普通设备 +""" + +import random +import time +import logging +from datetime import datetime +from enum import Enum + +# 配置日志 +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +class DeviceType(Enum): + """设备类型枚举""" + AREA_CONTROLLER = "area_controller" # 区域主控 + NORMAL_DEVICE = "normal_device" # 普通设备 + + +class SimulatedDevice: + """模拟设备基类""" + + def __init__(self, device_id, device_type, status="stopped"): + """ + 初始化模拟设备 + + Args: + device_id (str): 设备ID + device_type (DeviceType): 设备类型 + status (str): 初始状态 + """ + self.device_id = device_id + self.device_type = device_type + self.status = status + self.created_at = time.time() + logger.info(f"创建模拟设备: ID={device_id}, 类型={device_type.value}, 状态={status}") + + +class AreaController(SimulatedDevice): + """区域主控设备""" + + def __init__(self, device_id, lora_address, status="stopped"): + """ + 初始化区域主控设备 + + Args: + device_id (str): 设备ID + lora_address (str): LoRa地址 + status (str): 初始状态 + """ + super().__init__(device_id, DeviceType.AREA_CONTROLLER, status) + self.lora_address = lora_address + self.devices = {} # 管理的普通设备 + logger.info(f"创建区域主控: ID={device_id}, LoRa地址={lora_address}") + + def add_device(self, device): + """ + 添加普通设备到区域主控 + + Args: + device (NormalDevice): 普通设备实例 + """ + self.devices[device.device_id] = device + logger.info(f"区域主控 {self.device_id} 添加设备: {device.device_id}") + + def remove_device(self, device_id): + """ + 从区域主控移除普通设备 + + Args: + device_id (str): 设备ID + """ + if device_id in self.devices: + del self.devices[device_id] + logger.info(f"区域主控 {self.device_id} 移除设备: {device_id}") + + def get_device(self, device_id): + """ + 获取区域主控下的普通设备 + + Args: + device_id (str): 设备ID + + Returns: + NormalDevice: 普通设备实例,如果不存在则返回None + """ + return self.devices.get(device_id) + + def get_all_devices(self): + """ + 获取区域主控下的所有普通设备 + + Returns: + list: 所有普通设备实例的列表 + """ + return list(self.devices.values()) + + +class NormalDevice(SimulatedDevice): + """普通设备""" + + def __init__(self, device_id, device_type, rs485_bus, rs485_address, controller_id, status="stopped"): + """ + 初始化普通设备 + + Args: + device_id (str): 设备ID + device_type (str): 设备类型(如风机、水帘等) + rs485_bus (str): 485总线号 + rs485_address (str): 485总线地址 + controller_id (str): 所属区域主控ID + status (str): 初始状态 + """ + super().__init__(device_id, DeviceType.NORMAL_DEVICE, status) + self.device_type = device_type + self.rs485_bus = rs485_bus + self.rs485_address = rs485_address + self.controller_id = controller_id + logger.info(f"创建普通设备: ID={device_id}, 类型={device_type}, " + f"485总线={rs485_bus}, 485地址={rs485_address}, 所属主控={controller_id}") + + +class DeviceManager: + """设备管理器""" + + def __init__(self): + """初始化设备管理器""" + self.controllers = {} # 区域主控设备 + self.devices = {} # 所有普通设备 + logger.info("初始化设备管理器") + + def add_controller(self, controller): + """ + 添加区域主控设备 + + Args: + controller (AreaController): 区域主控设备实例 + """ + self.controllers[controller.device_id] = controller + logger.info(f"添加区域主控到管理器: {controller.device_id}") + + def remove_controller(self, controller_id): + """ + 移除区域主控设备 + + Args: + controller_id (str): 区域主控设备ID + """ + if controller_id in self.controllers: + # 同时移除该主控下的所有普通设备 + controller = self.controllers[controller_id] + for device_id in list(controller.devices.keys()): + if device_id in self.devices: + del self.devices[device_id] + del self.controllers[controller_id] + logger.info(f"从管理器移除区域主控: {controller_id}") + + def get_controller(self, controller_id): + """ + 获取区域主控设备 + + Args: + controller_id (str): 区域主控设备ID + + Returns: + AreaController: 区域主控设备实例,如果不存在则返回None + """ + controller = self.controllers.get(controller_id) + if controller: + logger.debug(f"获取区域主控: {controller_id}") + else: + logger.warning(f"尝试获取不存在的区域主控: {controller_id}") + return controller + + def get_all_controllers(self): + """ + 获取所有区域主控设备 + + Returns: + list: 所有区域主控设备实例的列表 + """ + logger.info(f"获取所有区域主控,共 {len(self.controllers)} 个") + return list(self.controllers.values()) + + def add_device(self, device): + """ + 添加普通设备 + + Args: + device (NormalDevice): 普通设备实例 + """ + self.devices[device.device_id] = device + logger.info(f"添加普通设备到管理器: {device.device_id}") + + def remove_device(self, device_id): + """ + 移除普通设备 + + Args: + device_id (str): 设备ID + """ + if device_id in self.devices: + del self.devices[device_id] + logger.info(f"从管理器移除普通设备: {device_id}") + + def get_device(self, device_id): + """ + 获取普通设备 + + Args: + device_id (str): 设备ID + + Returns: + NormalDevice: 普通设备实例,如果不存在则返回None + """ + device = self.devices.get(device_id) + if device: + logger.debug(f"获取普通设备: {device_id}") + else: + logger.warning(f"尝试获取不存在的普通设备: {device_id}") + return device + + def get_all_devices(self): + """ + 获取所有普通设备 + + Returns: + list: 所有普通设备实例的列表 + """ + logger.info(f"获取所有普通设备,共 {len(self.devices)} 个") + return list(self.devices.values()) + + def control_device(self, device_id, action): + """ + 控制指定设备 + + Args: + device_id (str): 设备ID + action (str): 控制动作 + + Returns: + dict: 控制结果 + """ + logger.info(f"控制设备: ID={device_id}, 动作={action}") + + # 先尝试查找普通设备 + device = self.get_device(device_id) + if device: + return self._control_normal_device(device, action) + + # 再尝试查找区域主控设备 + controller = self.get_controller(device_id) + if controller: + return self._control_area_controller(controller, action) + + result = { + "device_id": device_id, + "status": "failed", + "message": f"设备 {device_id} 不存在" + } + logger.error(f"控制设备失败: 设备 {device_id} 不存在") + return result + + def _control_normal_device(self, device, action): + """ + 控制普通设备 + + Args: + device (NormalDevice): 普通设备实例 + action (str): 控制动作 + + Returns: + dict: 控制结果 + """ + logger.info(f"控制普通设备 {device.device_id}: 动作={action}") + + if action == "on": + device.status = "running" + result = { + "device_id": device.device_id, + "status": "success", + "message": f"设备 {device.device_id} 已开启" + } + logger.info(f"普通设备 {device.device_id} 开启成功") + return result + elif action == "off": + device.status = "stopped" + result = { + "device_id": device.device_id, + "status": "success", + "message": f"设备 {device.device_id} 已关闭" + } + logger.info(f"普通设备 {device.device_id} 关闭成功") + return result + else: + result = { + "device_id": device.device_id, + "status": "failed", + "message": f"不支持的操作: {action}" + } + logger.warning(f"普通设备 {device.device_id} 不支持的操作: {action}") + return result + + def _control_area_controller(self, controller, action): + """ + 控制区域主控设备 + + Args: + controller (AreaController): 区域主控设备实例 + action (str): 控制动作 + + Returns: + dict: 控制结果 + """ + logger.info(f"控制区域主控 {controller.device_id}: 动作={action}") + + if action == "on": + controller.status = "running" + result = { + "device_id": controller.device_id, + "status": "success", + "message": f"区域主控 {controller.device_id} 已开启" + } + logger.info(f"区域主控 {controller.device_id} 开启成功") + return result + elif action == "off": + controller.status = "stopped" + result = { + "device_id": controller.device_id, + "status": "success", + "message": f"区域主控 {controller.device_id} 已关闭" + } + logger.info(f"区域主控 {controller.device_id} 关闭成功") + return result + else: + result = { + "device_id": controller.device_id, + "status": "failed", + "message": f"不支持的操作: {action}" + } + logger.warning(f"区域主控 {controller.device_id} 不支持的操作: {action}") + return result + + def query_device_status(self, device_id): + """ + 查询指定设备状态 + + Args: + device_id (str): 设备ID + + Returns: + dict: 设备状态信息 + """ + logger.info(f"查询设备状态: ID={device_id}") + + # 先尝试查找普通设备 + device = self.get_device(device_id) + if device: + return self._query_normal_device_status(device) + + # 再尝试查找区域主控设备 + controller = self.get_controller(device_id) + if controller: + return self._query_area_controller_status(controller) + + result = { + "device_id": device_id, + "status": "failed", + "message": f"设备 {device_id} 不存在" + } + logger.error(f"查询设备状态失败: 设备 {device_id} 不存在") + return result + + def _query_normal_device_status(self, device): + """ + 查询普通设备状态 + + Args: + device (NormalDevice): 普通设备实例 + + Returns: + dict: 设备状态信息 + """ + logger.info(f"查询普通设备 {device.device_id} 状态") + + # 模拟一些随机的设备数据 + if device.status == "running": + power = random.randint(200, 240) + current = random.uniform(4.0, 6.0) + result = { + "device_id": device.device_id, + "device_type": device.device_type, + "status": device.status, + "rs485_bus": device.rs485_bus, + "rs485_address": device.rs485_address, + "controller_id": device.controller_id, + "power": power, + "current": round(current, 2) + } + logger.info(f"普通设备 {device.device_id} 状态: 运行中, 功率={power}V, 电流={round(current, 2)}A") + return result + else: + result = { + "device_id": device.device_id, + "device_type": device.device_type, + "status": device.status, + "rs485_bus": device.rs485_bus, + "rs485_address": device.rs485_address, + "controller_id": device.controller_id, + "power": 0, + "current": 0.0 + } + logger.info(f"普通设备 {device.device_id} 状态: 已停止") + return result + + def _query_area_controller_status(self, controller): + """ + 查询区域主控状态 + + Args: + controller (AreaController): 区域主控设备实例 + + Returns: + dict: 设备状态信息 + """ + logger.info(f"查询区域主控 {controller.device_id} 状态") + + result = { + "device_id": controller.device_id, + "device_type": "area_controller", + "status": controller.status, + "lora_address": controller.lora_address, + "managed_devices": len(controller.devices) + } + logger.info(f"区域主控 {controller.device_id} 状态: {controller.status}, 管理设备数: {len(controller.devices)}") + return result + + def query_all_device_status(self): + """ + 查询所有设备状态 + + Returns: + list: 所有设备状态信息列表 + """ + logger.info("查询所有设备状态") + statuses = [] + + # 添加区域主控状态 + for controller in self.controllers.values(): + status_info = { + "device_id": controller.device_id, + "device_type": "area_controller", + "status": controller.status, + "lora_address": controller.lora_address, + "managed_devices": len(controller.devices) + } + statuses.append(status_info) + logger.debug(f"区域主控 {controller.device_id} 状态: {controller.status}") + + # 添加普通设备状态 + for device in self.devices.values(): + status_info = { + "device_id": device.device_id, + "device_type": device.device_type, + "status": device.status, + "rs485_bus": device.rs485_bus, + "rs485_address": device.rs485_address, + "controller_id": device.controller_id + } + statuses.append(status_info) + logger.debug(f"普通设备 {device.device_id} 状态: {device.status}") + + logger.info(f"查询所有设备状态完成,共 {len(statuses)} 个设备") + return statuses \ No newline at end of file diff --git a/internal/simulation/devices.py b/internal/simulation/devices.py new file mode 100644 index 0000000..4315dd0 --- /dev/null +++ b/internal/simulation/devices.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +模拟设备模块,用于创建和管理模拟设备 +""" + +import random +import time +import logging + +# 配置日志 +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +class SimulatedDevice: + """模拟设备类""" + + def __init__(self, device_id, device_type, status="stopped"): + """ + 初始化模拟设备 + + Args: + device_id (str): 设备ID + device_type (str): 设备类型 + status (str): 初始状态 + """ + self.device_id = device_id + self.device_type = device_type + self.status = status + self.created_at = time.time() + logger.info(f"创建模拟设备: ID={device_id}, 类型={device_type}, 状态={status}") + + def control(self, action): + """ + 控制设备 + + Args: + action (str): 控制动作 ('on' 或 'off') + + Returns: + dict: 控制结果 + """ + logger.info(f"控制设备 {self.device_id}: 动作={action}") + + if action == "on": + self.status = "running" + result = { + "device_id": self.device_id, + "status": "success", + "message": f"设备 {self.device_id} 已开启" + } + logger.info(f"设备 {self.device_id} 开启成功") + return result + elif action == "off": + self.status = "stopped" + result = { + "device_id": self.device_id, + "status": "success", + "message": f"设备 {self.device_id} 已关闭" + } + logger.info(f"设备 {self.device_id} 关闭成功") + return result + else: + result = { + "device_id": self.device_id, + "status": "failed", + "message": f"不支持的操作: {action}" + } + logger.warning(f"设备 {self.device_id} 不支持的操作: {action}") + return result + + def get_status(self): + """ + 获取设备状态 + + Returns: + dict: 设备状态信息 + """ + logger.info(f"查询设备 {self.device_id} 状态") + + # 模拟一些随机的设备数据 + if self.status == "running": + power = random.randint(200, 240) + current = random.uniform(4.0, 6.0) + result = { + "device_id": self.device_id, + "status": self.status, + "power": power, + "current": round(current, 2) + } + logger.info(f"设备 {self.device_id} 状态: 运行中, 功率={power}V, 电流={round(current, 2)}A") + return result + else: + result = { + "device_id": self.device_id, + "status": self.status, + "power": 0, + "current": 0.0 + } + logger.info(f"设备 {self.device_id} 状态: 已停止") + return result + + +class DeviceManager: + """设备管理器""" + + def __init__(self): + """初始化设备管理器""" + self.devices = {} + logger.info("初始化设备管理器") + + def add_device(self, device): + """ + 添加设备 + + Args: + device (SimulatedDevice): 模拟设备实例 + """ + self.devices[device.device_id] = device + logger.info(f"添加设备到管理器: {device.device_id}") + + def remove_device(self, device_id): + """ + 移除设备 + + Args: + device_id (str): 设备ID + """ + if device_id in self.devices: + del self.devices[device_id] + logger.info(f"从管理器移除设备: {device_id}") + + def get_device(self, device_id): + """ + 获取设备 + + Args: + device_id (str): 设备ID + + Returns: + SimulatedDevice: 设备实例,如果不存在则返回None + """ + device = self.devices.get(device_id) + if device: + logger.debug(f"获取设备: {device_id}") + else: + logger.warning(f"尝试获取不存在的设备: {device_id}") + return device + + def get_all_devices(self): + """ + 获取所有设备 + + Returns: + list: 所有设备实例的列表 + """ + logger.info(f"获取所有设备,共 {len(self.devices)} 个设备") + return list(self.devices.values()) + + def control_device(self, device_id, action): + """ + 控制指定设备 + + Args: + device_id (str): 设备ID + action (str): 控制动作 + + Returns: + dict: 控制结果 + """ + logger.info(f"控制设备: ID={device_id}, 动作={action}") + device = self.get_device(device_id) + if device: + return device.control(action) + else: + result = { + "device_id": device_id, + "status": "failed", + "message": f"设备 {device_id} 不存在" + } + logger.error(f"控制设备失败: 设备 {device_id} 不存在") + return result + + def query_device_status(self, device_id): + """ + 查询指定设备状态 + + Args: + device_id (str): 设备ID + + Returns: + dict: 设备状态信息 + """ + logger.info(f"查询设备状态: ID={device_id}") + device = self.get_device(device_id) + if device: + return device.get_status() + else: + result = { + "device_id": device_id, + "status": "failed", + "message": f"设备 {device_id} 不存在" + } + logger.error(f"查询设备状态失败: 设备 {device_id} 不存在") + return result + + def query_all_device_status(self): + """ + 查询所有设备状态 + + Returns: + list: 所有设备状态信息列表 + """ + logger.info("查询所有设备状态") + statuses = [] + for device in self.devices.values(): + status_info = { + "device_id": device.device_id, + "device_type": device.device_type, + "status": device.status + } + statuses.append(status_info) + logger.debug(f"设备 {device.device_id} 状态: {device.status}") + logger.info(f"查询所有设备状态完成,共 {len(statuses)} 个设备") + return statuses \ No newline at end of file diff --git a/internal/simulation/websocket.py b/internal/simulation/websocket.py new file mode 100644 index 0000000..8355a7b --- /dev/null +++ b/internal/simulation/websocket.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +模拟WebSocket通信模块,用于模拟与猪场主控的WebSocket通信 +""" + +import json +import time +import logging +from datetime import datetime +from .devices import DeviceManager, SimulatedDevice + +# 配置日志 +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +class SimulatedWebSocketServer: + """模拟WebSocket服务器""" + + def __init__(self, config): + """ + 初始化模拟WebSocket服务器 + + Args: + config: 配置对象 + """ + self.config = config + self.device_manager = DeviceManager() + self._initialize_devices() + logger.info("模拟WebSocket服务器初始化完成") + + def _initialize_devices(self): + """初始化模拟设备""" + logger.info("开始初始化模拟设备") + # 根据配置创建模拟设备 + for device_config in self.config.simulation_devices: + device = SimulatedDevice( + device_id=device_config['id'], + device_type=device_config['type'], + status=device_config.get('status', 'stopped') + ) + self.device_manager.add_device(device) + logger.info(f"模拟设备初始化完成,共创建 {len(self.config.simulation_devices)} 个设备") + + def handle_message(self, message_str): + """ + 处理接收到的消息 + + Args: + message_str (str): JSON格式的消息字符串 + + Returns: + dict: 响应消息 + """ + logger.info(f"收到消息: {message_str}") + + try: + message = json.loads(message_str) + msg_type = message.get('type') + logger.info(f"消息类型: {msg_type}") + + if msg_type == 'heartbeat': + return self._handle_heartbeat(message) + elif msg_type == 'command': + command = message.get('command') + logger.info(f"命令类型: {command}") + if command == 'control_device': + return self._handle_control_device(message) + elif command == 'query_device_status': + return self._handle_query_device_status(message) + elif command == 'query_all_device_status': + return self._handle_query_all_device_status(message) + + # 未知消息类型 + result = { + "type": "response", + "status": "failed", + "message": f"未知消息类型: {msg_type}", + "timestamp": datetime.utcnow().isoformat() + 'Z' + } + logger.warning(f"未知消息类型: {msg_type}") + return result + except json.JSONDecodeError: + result = { + "type": "response", + "status": "failed", + "message": "无效的JSON格式", + "timestamp": datetime.utcnow().isoformat() + 'Z' + } + logger.error("无效的JSON格式") + return result + + def _handle_heartbeat(self, message): + """ + 处理心跳消息 + + Args: + message (dict): 心跳消息 + + Returns: + dict: 心跳响应 + """ + logger.info("处理心跳消息") + result = { + "type": "response", + "command": "heartbeat", + "status": "success", + "message": "心跳响应", + "timestamp": datetime.utcnow().isoformat() + 'Z' + } + logger.info("心跳响应已发送") + return result + + def _handle_control_device(self, message): + """ + 处理设备控制命令 + + Args: + message (dict): 控制命令消息 + + Returns: + dict: 控制响应 + """ + data = message.get('data', {}) + device_id = data.get('device_id') + action = data.get('action') + + logger.info(f"处理设备控制命令: 设备ID={device_id}, 动作={action}") + result = self.device_manager.control_device(device_id, action) + + response = { + "type": "response", + "command": "control_device", + "data": result, + "timestamp": datetime.utcnow().isoformat() + 'Z' + } + logger.info(f"设备控制命令处理完成: {response}") + return response + + def _handle_query_device_status(self, message): + """ + 处理查询设备状态命令 + + Args: + message (dict): 查询命令消息 + + Returns: + dict: 状态响应 + """ + data = message.get('data', {}) + device_id = data.get('device_id') + + logger.info(f"处理查询设备状态命令: 设备ID={device_id}") + result = self.device_manager.query_device_status(device_id) + + response = { + "type": "response", + "command": "query_device_status", + "data": result, + "timestamp": datetime.utcnow().isoformat() + 'Z' + } + logger.info(f"设备状态查询完成: {response}") + return response + + def _handle_query_all_device_status(self, message): + """ + 处理查询所有设备状态命令 + + Args: + message (dict): 查询命令消息 + + Returns: + dict: 状态响应 + """ + logger.info("处理查询所有设备状态命令") + result = self.device_manager.query_all_device_status() + + response = { + "type": "response", + "command": "query_all_device_status", + "data": result, + "timestamp": datetime.utcnow().isoformat() + 'Z' + } + logger.info(f"所有设备状态查询完成,共 {len(result)} 个设备") + return response \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..f41e1a7 --- /dev/null +++ b/main.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +通信中继器主程序入口 +支持四级结构:平台->中继->区域主控->普通设备 +""" + +import logging +import time + +# 配置日志 +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +from internal.core import RelayService + + +def main(): + """主函数""" + logger.info("通信中继器启动") + + # 创建并初始化中继器服务 + relay_service = RelayService() + relay_service.initialize() + + try: + # 启动中继器服务 + relay_service.start() + except KeyboardInterrupt: + logger.info("收到中断信号,正在停止服务...") + relay_service.stop() + except Exception as e: + logger.error(f"中继器服务发生错误: {e}") + relay_service.stop() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3b00ab8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +# 项目依赖包 +PyYAML>=5.4.1 +websockets>=10.0,<15.0 \ No newline at end of file diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..94d68f9 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,8 @@ +# 脚本目录 + +该目录存放项目的各种脚本文件,包括: + +1. 启动脚本 +2. 停止脚本 +3. 部署脚本 +4. 其他运维脚本 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..c3cb212 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,2 @@ +# 测试文件目录 +# 存放项目的所有测试文件 \ No newline at end of file diff --git a/tests/test_relay.py b/tests/test_relay.py new file mode 100644 index 0000000..9babeb3 --- /dev/null +++ b/tests/test_relay.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +中继器测试脚本 +用于模拟平台向中继器发送指令 +""" + +import json +import time +import threading +from internal.core import RelayService + +def test_relay_commands(): + """测试中继器指令处理""" + print("启动中继器测试") + + # 创建中继器服务实例(仅用于访问其方法,不启动完整服务) + relay_service = RelayService() + relay_service.initialize() + + # 模拟平台发送各种指令 + test_commands = [ + # 控制设备指令 + { + "type": "command", + "command": "control_device", + "data": { + "device_id": "3", + "action": "on" + }, + "timestamp": "2023-01-01T12:00:00Z" + }, + + # 查询单个设备状态指令 + { + "type": "command", + "command": "query_device_status", + "data": { + "device_id": "3" + }, + "timestamp": "2023-01-01T12:00:05Z" + }, + + # 查询所有设备状态指令 + { + "type": "command", + "command": "query_all_device_status", + "timestamp": "2023-01-01T12:00:10Z" + } + ] + + print("开始发送测试指令...") + for i, command in enumerate(test_commands): + print(f"发送指令 {i+1}: {command['command']}") + command_json = json.dumps(command) + relay_service.handle_platform_command(command_json) + time.sleep(1) + + print("测试指令发送完成") + +if __name__ == "__main__": + test_relay_commands() \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..94a0efe --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1,2 @@ +# 工具函数目录 +# 存放项目中使用的各种工具函数 \ No newline at end of file