PCM_Report/config_service.py

536 lines
19 KiB
Python
Raw Normal View History

2025-12-11 14:32:31 +08:00
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()