367 lines
13 KiB
Python
367 lines
13 KiB
Python
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/health', methods=['GET'])
|
||
def health_check():
|
||
"""健康检查接口"""
|
||
return jsonify({
|
||
"status": "ok",
|
||
"service": "config-service",
|
||
"version": "2.0.0"
|
||
})
|
||
|
||
@self.app.route('/api/config', methods=['GET'])
|
||
def get_config():
|
||
"""获取配置文件"""
|
||
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/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=1, 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
|
||
|
||
# 原有的配置文件操作方法保持不变
|
||
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', 1),
|
||
kwargs.get('start_addr', 13),
|
||
kwargs.get('quantity', 1)
|
||
)
|
||
print("**********************result:", result)
|
||
# 解析寄存器值
|
||
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
|
||
|
||
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() |