PCM_Report/config_service.py

536 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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()