from flask import Flask, request, jsonify from flask_cors import CORS import json import os import threading import time from pathlib import Path from serial_manager import SerialManager class ConfigService: """配置服务类 - 专注于配置管理,通过回调与串口通信""" def __init__(self, default_config_path=None, host="127.0.0.1", port=5000, debug=False, logger=None, serial_command_callback=None): """ 初始化配置服务 Args: default_config_path: 默认配置文件路径 host: 监听地址 port: 监听端口 debug: 调试模式 logger: 日志记录器 serial_command_callback: 串口命令回调函数 """ self.app = Flask(__name__) CORS(self.app) # 服务配置 self.host = host self.port = port self.debug = debug self.logger = logger self.serial_command_callback = serial_command_callback # 配置文件管理 self.config_lock = threading.Lock() service_settings_file = os.path.join(os.path.dirname(__file__), "config_service_settings.json") self.default_config_path = default_config_path or self._load_service_settings(service_settings_file) # 服务状态 self._running = False self._server_thread = None # 注册路由 self._register_routes() def _log(self, message): """日志输出""" if self.logger: self.logger.info(message) else: print(f"[ConfigService] {message}") def _execute_serial_command(self, command_type: str, **kwargs) -> dict[str, any]: """ 执行串口命令(通过回调) Args: command_type: 命令类型("power_on", "power_off", "read_status") **kwargs: 命令参数 Returns: 操作结果 """ if not self.serial_command_callback: return {"success": False, "error": "串口回调未设置"} try: return self.serial_command_callback(command_type, **kwargs) except Exception as e: return {"success": False, "error": f"执行串口命令失败: {str(e)}"} def _register_routes(self): """注册API路由""" @self.app.route('/api/config', methods=['GET']) def get_config(): """ 获取配置文件内容 查询参数: path: 可选,指定配置文件路径 """ config_path = request.args.get('path', None) content = self.read_config_file(config_path) if "error" in content: return jsonify(content), 500 return jsonify({ "success": True, "config": content, "path": config_path or self.default_config_path }) @self.app.route('/api/config', methods=['POST']) def update_config(): """ 更新配置文件内容 请求体: { "config": {...}, # 配置内容 "path": "..." # 可选,配置文件路径 } """ data = request.get_json() if not data or "config" not in data: return jsonify({ "success": False, "error": "请求体必须包含config字段" }), 400 config_content = data["config"] config_path = data.get("path", None) result = self.write_config_file(config_content, config_path) if result.get("success"): return jsonify(result) else: return jsonify(result), 500 @self.app.route('/api/config/text', methods=['GET']) def get_config_text(): """ 获取配置文件的纯文本内容(格式化的JSON) 查询参数: path: 可选,指定配置文件路径 """ config_path = request.args.get('path', None) if config_path is None: config_path = self.default_config_path with self.config_lock: try: if not os.path.exists(config_path): return jsonify({"error": f"配置文件不存在: {config_path}"}), 404 with open(config_path, 'r', encoding='utf-8') as f: content = f.read() return jsonify({ "success": True, "text": content, "path": config_path }) except Exception as e: return jsonify({"error": f"读取配置文件失败: {str(e)}"}), 500 @self.app.route('/api/config/text', methods=['POST']) def update_config_text(): """ 以纯文本方式更新配置文件内容 请求体: { "text": "...", # 配置文件文本内容 "path": "..." # 可选,配置文件路径 } """ data = request.get_json() if not data or "text" not in data: return jsonify({ "success": False, "error": "请求体必须包含text字段" }), 400 config_text = data["text"] config_path = data.get("path", self.default_config_path) with self.config_lock: try: # 备份原文件 if os.path.exists(config_path): backup_path = f"{config_path}.backup" with open(config_path, 'r', encoding='utf-8') as f_src: with open(backup_path, 'w', encoding='utf-8') as f_dst: f_dst.write(f_src.read()) # 写入新配置 with open(config_path, 'w', encoding='utf-8') as f: f.write(config_text) return jsonify({ "success": True, "message": "配置文件已更新" }) except Exception as e: return jsonify({ "success": False, "error": f"写入配置文件失败: {str(e)}" }), 500 @self.app.route('/api/health', methods=['GET']) def health_check(): """健康检查接口""" return jsonify({ "status": "ok", "service": "config-service", "version": "1.0.0" }) @self.app.route('/api/power/on', methods=['POST']) def power_on(): """实验台上电""" try: result = self._execute_serial_command("power_on", unit=2, coil_addr=0x0001) if result.get("success"): return jsonify({ "success": True, "message": "实验台上电成功", "data": result }) else: return jsonify({ "success": False, "error": result.get("error", "上电操作失败") }), 500 except Exception as e: return jsonify({ "success": False, "error": f"上电操作异常: {str(e)}" }), 500 @self.app.route('/api/power/off', methods=['POST']) def power_off(): """实验台断电""" try: result = self._execute_serial_command("power_off", unit=2, coil_addr=0x0001) if result.get("success"): return jsonify({ "success": True, "message": "实验台断电成功", "data": result }) else: return jsonify({ "success": False, "error": result.get("error", "断电操作失败") }), 500 except Exception as e: return jsonify({ "success": False, "error": f"断电操作异常: {str(e)}" }), 500 @self.app.route('/api/power/status', methods=['GET']) def power_status(): """获取电源状态""" try: result = self._execute_serial_command("read_status", unit=2, start_addr=13, quantity=1) if result.get("success"): registers = result.get("registers", []) status_value = registers[0] if registers else 0 remote_close = (status_value == 0xFF00) return jsonify({ "success": True, "remote_close": remote_close, "value": status_value, "raw_data": result.get("raw_hex", "") }) else: return jsonify({ "success": False, "error": result.get("error", "状态读取失败") }), 500 except Exception as e: return jsonify({ "success": False, "error": f"状态读取异常: {str(e)}" }), 500 @self.app.route('/api/light/green/on', methods=['POST']) def green_light_on(): """开绿灯""" try: result = self._execute_serial_command("green_light_on", unit=1, coil_addr=0x0002) if result.get("success"): return jsonify({ "success": True, "message": "绿灯开启成功", "data": result }) else: return jsonify({ "success": False, "error": result.get("error", "开绿灯操作失败") }), 500 except Exception as e: return jsonify({ "success": False, "error": f"开绿灯操作异常: {str(e)}" }), 500 @self.app.route('/api/light/green/off', methods=['POST']) def green_light_off(): """关绿灯""" try: result = self._execute_serial_command("green_light_off", unit=1, coil_addr=0x0002) if result.get("success"): return jsonify({ "success": True, "message": "绿灯关闭成功", "data": result }) else: return jsonify({ "success": False, "error": result.get("error", "关绿灯操作失败") }), 500 except Exception as e: return jsonify({ "success": False, "error": f"关绿灯操作异常: {str(e)}" }), 500 # 原有的配置文件操作方法保持不变 def read_config_file(self, config_path: str = None) -> dict: """ 读取配置文件 Args: config_path: 配置文件路径,如果为None则使用默认路径 Returns: 配置文件内容(dict) """ if config_path is None: config_path = self.default_config_path with self.config_lock: try: if not os.path.exists(config_path): return {"error": f"配置文件不存在: {config_path}"} with open(config_path, 'r', encoding='utf-8') as f: content = json.load(f) return content except json.JSONDecodeError as e: return {"error": f"JSON解析错误: {str(e)}"} except Exception as e: return {"error": f"读取配置文件失败: {str(e)}"} def write_config_file(self, content: dict, config_path: str = None) -> dict: """ 写入配置文件 Args: content: 要写入的配置内容 config_path: 配置文件路径,如果为None则使用默认路径 Returns: 操作结果 """ if config_path is None: config_path = self.default_config_path with self.config_lock: try: # 备份原文件 if os.path.exists(config_path): backup_path = f"{config_path}.backup" with open(config_path, 'r', encoding='utf-8') as f_src: with open(backup_path, 'w', encoding='utf-8') as f_dst: f_dst.write(f_src.read()) # 写入新配置 with open(config_path, 'w', encoding='utf-8') as f: json.dump(content, f, ensure_ascii=False, indent=2) return {"success": True, "message": "配置文件已更新"} except Exception as e: return {"success": False, "error": f"写入配置文件失败: {str(e)}"} def start(self): """启动配置服务""" if self._running: self._log("配置服务已在运行中") return self._running = True self._server_thread = threading.Thread(target=self._run_server, daemon=True) self._server_thread.start() self._log(f"配置服务已启动,监听 {self.host}:{self.port}") def _run_server(self): """运行Flask服务器""" self.app.run(host=self.host, port=self.port, debug=self.debug, use_reloader=False) def stop(self): """停止配置服务""" self._running = False self._log("配置服务停止请求已发送") def _load_service_settings(self, settings_path: str) -> str: """ 加载服务设置文件 Args: settings_path: 设置文件路径 Returns: 默认配置文件路径 """ try: if os.path.exists(settings_path): with open(settings_path, 'r', encoding='utf-8') as f: settings = json.load(f) default_config = settings.get('paths', {}).get('default_config', 'config.json') # 返回相对于当前文件的路径 base_dir = os.path.dirname(__file__) return os.path.join(base_dir, default_config) except Exception as e: print(f"加载服务设置失败: {e}") # 默认返回当前目录下的 config.json base_dir = os.path.dirname(__file__) return os.path.join(base_dir, 'config.json') def main(): """主应用程序入口""" def serial_command_handler(command_type: str, **kwargs) -> dict[str, any]: """ 串口命令处理器 将ConfigService的命令转换为SerialManager的实际操作 """ serial_mgr = SerialManager() # 获取单例实例 if command_type == "power_on": return { "success": serial_mgr.write_single_coil( kwargs.get('unit', 2), kwargs.get('coil_addr', 0x0001), True ), "operation": "power_on" } elif command_type == "power_off": return { "success": serial_mgr.write_single_coil( kwargs.get('unit', 2), kwargs.get('coil_addr', 0x0001), False ), "operation": "power_off" } elif command_type == "read_status": result = serial_mgr.read_holding_registers( kwargs.get('unit', 2), kwargs.get('start_addr', 13), kwargs.get('quantity', 1) ) # 解析寄存器值 if result.get("success") and "raw" in result: raw_data = result["raw"] # Modbus响应格式: [unit, function_code, byte_count, data...] if len(raw_data) >= 5: byte_count = raw_data[2] registers = [] for i in range(0, byte_count, 2): if i + 3 < len(raw_data): high_byte = raw_data[i + 3] low_byte = raw_data[i + 4] if i + 4 < len(raw_data) else 0 value = (high_byte << 8) | low_byte registers.append(value) result["registers"] = registers return result elif command_type == "green_light_on": return { "success": serial_mgr.write_single_coil( kwargs.get('unit', 1), kwargs.get('coil_addr', 0x0002), True ), "operation": "green_light_on" } elif command_type == "green_light_off": return { "success": serial_mgr.write_single_coil( kwargs.get('unit', 1), kwargs.get('coil_addr', 0x0002), False ), "operation": "green_light_off" } else: return {"success": False, "error": f"未知命令类型: {command_type}"} # 创建串口管理器(单例,只会初始化一次) serial_manager = SerialManager(port="COM1", baudrate=9600) # 根据实际情况修改端口 # 创建配置服务,传入串口命令回调 config_service = ConfigService( host="127.0.0.1", port=5000, debug=False, serial_command_callback=serial_command_handler ) print("🚀 启动配置服务和串口管理...") print(f"串口状态: {serial_manager.get_status()}") try: config_service.start() # 保持主线程运行 while config_service._running: time.sleep(1) except KeyboardInterrupt: print("\n🛑 接收到中断信号,正在关闭服务...") finally: config_service.stop() serial_manager.close() print("✅ 服务已安全退出") if __name__ == "__main__": main()