|
|
@@ -44,6 +44,7 @@ logger = logging.getLogger('serial_mqtt_gateway')
|
|
|
from modules.serial_port import SerialPort
|
|
|
from modules.mqtt_client import MQTTClient
|
|
|
from modules.network_config import network_manager
|
|
|
+from modules.modbus_rtu import ModbusRTUClient, ANTENNA_ADDRESSES, AddressConfigProtocol, build_broadcast_query, build_confirm_address, build_assign_address
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
@@ -66,6 +67,8 @@ socketio = SocketIO(
|
|
|
# 初始化串口和MQTT客户端
|
|
|
serial_client = SerialPort()
|
|
|
mqtt_client = MQTTClient()
|
|
|
+modbus_client = ModbusRTUClient(serial_client)
|
|
|
+address_config = AddressConfigProtocol(serial_client)
|
|
|
|
|
|
# 转发标志
|
|
|
forward_serial_to_mqtt = DEFAULT_FORWARD_SERIAL_TO_MQTT
|
|
|
@@ -270,7 +273,7 @@ def handle_serial_send(data):
|
|
|
})
|
|
|
return
|
|
|
|
|
|
- if not serial_client.get_status():
|
|
|
+ if not (isinstance(serial_client.get_status(), dict) and serial_client.get_status().get("connected", False)):
|
|
|
emit('serial_send_response', {
|
|
|
'success': False,
|
|
|
'message': '串口未连接',
|
|
|
@@ -438,6 +441,78 @@ serial_client.set_status_callback(serial_status_handler)
|
|
|
mqtt_client.set_data_callback(mqtt_data_handler)
|
|
|
mqtt_client.set_status_callback(mqtt_status_handler)
|
|
|
|
|
|
+# 设备配置文件路径
|
|
|
+DEVICE_CONFIG_FILE = '/root/dzxj_dtu/devices.json'
|
|
|
+confirm_loop_running = False
|
|
|
+
|
|
|
+
|
|
|
+def save_device_config():
|
|
|
+ """保存设备配置到文件"""
|
|
|
+ try:
|
|
|
+ filepath = DEVICE_CONFIG_FILE
|
|
|
+ success = address_config.save_config(filepath)
|
|
|
+ if success:
|
|
|
+ logger.info(f"设备配置已保存到: {filepath}")
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"保存设备配置失败: {str(e)}")
|
|
|
+
|
|
|
+
|
|
|
+def load_device_config():
|
|
|
+ """加载设备配置"""
|
|
|
+ try:
|
|
|
+ filepath = DEVICE_CONFIG_FILE
|
|
|
+ if os.path.exists(filepath):
|
|
|
+ success = address_config.load_config(filepath)
|
|
|
+ if success:
|
|
|
+ devices = address_config.get_stored_devices()
|
|
|
+ logger.info(f"已加载 {len(devices)} 个设备配置")
|
|
|
+ return devices
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"加载设备配置失败: {str(e)}")
|
|
|
+ return {}
|
|
|
+
|
|
|
+
|
|
|
+def confirm_loop():
|
|
|
+ """后台确认线程:每10秒对所有已存储设备发送 confirm_address"""
|
|
|
+ global confirm_loop_running
|
|
|
+ confirm_loop_running = True
|
|
|
+ logger.info("启动设备确认线程 (间隔10秒)")
|
|
|
+
|
|
|
+ while confirm_loop_running:
|
|
|
+ try:
|
|
|
+ devices = address_config.get_stored_devices()
|
|
|
+ if not devices:
|
|
|
+ time.sleep(10)
|
|
|
+ continue
|
|
|
+
|
|
|
+ if not serial_client.get_status():
|
|
|
+ time.sleep(10)
|
|
|
+ continue
|
|
|
+
|
|
|
+ _st = serial_client.get_status()
|
|
|
+ if not (isinstance(_st, dict) and _st.get('connected', False)):
|
|
|
+ time.sleep(10)
|
|
|
+ continue
|
|
|
+
|
|
|
+ for uid_hex, addr in devices.items():
|
|
|
+ try:
|
|
|
+ uid_bytes = bytes.fromhex(uid_hex)
|
|
|
+ cmd = build_confirm_address(addr, uid_bytes)
|
|
|
+ success, msg = serial_client.send_raw(cmd)
|
|
|
+ if success:
|
|
|
+ logger.debug(f"确认设备: 地址={addr}, UID={uid_hex[:16]}...")
|
|
|
+ else:
|
|
|
+ logger.warning(f"确认失败 地址={addr}: {msg}")
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"确认异常 地址={addr}: {str(e)}")
|
|
|
+ time.sleep(0.1)
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"确认线程异常: {str(e)}")
|
|
|
+
|
|
|
+ time.sleep(10)
|
|
|
+
|
|
|
+
|
|
|
# API路由
|
|
|
# 移除静态文件服务,前端由nginx提供服务
|
|
|
|
|
|
@@ -491,13 +566,15 @@ def serial_connect():
|
|
|
}), 400
|
|
|
|
|
|
# 先断开之前的连接
|
|
|
- if serial_client.get_status():
|
|
|
- logger.info(f"断开现有串口连接: {serial_client.port}")
|
|
|
+ _status = serial_client.get_status()
|
|
|
+ if isinstance(_status, dict) and _status.get('connected', False):
|
|
|
+ _port = serial_client.current_config.port if serial_client.current_config else 'unknown'
|
|
|
+ logger.info(f"断开现有串口连接: {_port}")
|
|
|
serial_client.disconnect()
|
|
|
|
|
|
# 连接新的串口
|
|
|
logger.info(f"尝试连接串口: {port}, 波特率: {baudrate}")
|
|
|
- success, message = serial_client.connect(port, baudrate, bytesize, parity, stopbits, timeout)
|
|
|
+ success, message = serial_client.connect(port, baudrate=baudrate, timeout=timeout, bytesize=bytesize, parity=parity, stopbits=stopbits)
|
|
|
|
|
|
status_code = 200 if success else 400
|
|
|
error_code = ERROR_CODES['SUCCESS'] if success else ERROR_CODES['SERIAL_CONNECTION_ERROR']
|
|
|
@@ -535,8 +612,9 @@ def serial_disconnect():
|
|
|
@app.route('/api/serial/status', methods=['GET'])
|
|
|
def serial_get_status():
|
|
|
"""获取串口状态"""
|
|
|
+ _st = serial_client.get_status()
|
|
|
return jsonify({
|
|
|
- 'connected': serial_client.get_status()
|
|
|
+ 'connected': _st.get('connected', False) if isinstance(_st, dict) else bool(_st)
|
|
|
})
|
|
|
|
|
|
@app.route('/api/serial/send', methods=['POST'])
|
|
|
@@ -839,6 +917,437 @@ def restart_network_service():
|
|
|
logger.error(f'重启网络服务失败: {str(e)}')
|
|
|
return jsonify({'success': False, 'message': f'重启网络服务失败: {str(e)}'}), 500
|
|
|
|
|
|
+# Modbus RTU API
|
|
|
+@app.route('/api/modbus/antenna_addresses', methods=['GET'])
|
|
|
+def get_antenna_addresses():
|
|
|
+ """获取天线地址映射表"""
|
|
|
+ return jsonify({
|
|
|
+ 'success': True,
|
|
|
+ 'antennas': {str(k): f"0x{v:04x}" for k, v in ANTENNA_ADDRESSES.items()}
|
|
|
+ })
|
|
|
+
|
|
|
+
|
|
|
+@app.route('/api/modbus/read_antenna', methods=['POST'])
|
|
|
+def modbus_read_antenna():
|
|
|
+ """读取指定天线的卡号
|
|
|
+
|
|
|
+ 请求参数:
|
|
|
+ {
|
|
|
+ "device_address": 1, // 设备地址 (1-247)
|
|
|
+ "antenna": 1, // 天线编号 (1-24)
|
|
|
+ "timeout": 1.0 // 可选,超时时间(秒)
|
|
|
+ }
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ data = request.json
|
|
|
+ device_address = data.get('device_address', 1)
|
|
|
+ antenna = data.get('antenna', 1)
|
|
|
+ timeout = data.get('timeout')
|
|
|
+
|
|
|
+ if antenna < 1 or antenna > 24:
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message': f'无效的天线编号: {antenna}, 必须是1-24'
|
|
|
+ }), 400
|
|
|
+
|
|
|
+ if not (isinstance(serial_client.get_status(), dict) and serial_client.get_status().get("connected", False)):
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message': '串口未连接'
|
|
|
+ }), 400
|
|
|
+
|
|
|
+ result = modbus_client.read_antenna_card(
|
|
|
+ device_address=device_address,
|
|
|
+ antenna_num=antenna,
|
|
|
+ timeout=timeout
|
|
|
+ )
|
|
|
+
|
|
|
+ if 'error' in result:
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message': result['error'],
|
|
|
+ 'raw_data': result.get('raw_data', '')
|
|
|
+ }), 400
|
|
|
+
|
|
|
+ return jsonify({
|
|
|
+ 'success': True,
|
|
|
+ 'data': result
|
|
|
+ })
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f'读取天线数据失败: {str(e)}')
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message': str(e)
|
|
|
+ }), 500
|
|
|
+
|
|
|
+
|
|
|
+@app.route('/api/modbus/read_registers', methods=['POST'])
|
|
|
+def modbus_read_registers():
|
|
|
+ """读取保持寄存器
|
|
|
+
|
|
|
+ 请求参数:
|
|
|
+ {
|
|
|
+ "device_address": 1,
|
|
|
+ "start_address": 2, // 起始地址 (十六进制如0x0002或十进制如2)
|
|
|
+ "quantity": 4, // 寄存器数量
|
|
|
+ "timeout": 1.0
|
|
|
+ }
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ data = request.json
|
|
|
+ device_address = data.get('device_address', 1)
|
|
|
+ start_address = data.get('start_address', 0)
|
|
|
+ quantity = data.get('quantity', 1)
|
|
|
+ timeout = data.get('timeout')
|
|
|
+
|
|
|
+ # 支持十六进制字符串
|
|
|
+ if isinstance(start_address, str):
|
|
|
+ start_address = int(start_address, 16)
|
|
|
+ if isinstance(device_address, str):
|
|
|
+ device_address = int(device_address, 16)
|
|
|
+
|
|
|
+ if not (isinstance(serial_client.get_status(), dict) and serial_client.get_status().get("connected", False)):
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message': '串口未连接'
|
|
|
+ }), 400
|
|
|
+
|
|
|
+ result = modbus_client.read_holding_registers(
|
|
|
+ device_address=device_address,
|
|
|
+ start_address=start_address,
|
|
|
+ quantity=quantity,
|
|
|
+ timeout=timeout
|
|
|
+ )
|
|
|
+
|
|
|
+ if 'error' in result:
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message': result['error'],
|
|
|
+ 'raw_data': result.get('raw_data', '')
|
|
|
+ }), 400
|
|
|
+
|
|
|
+ return jsonify({
|
|
|
+ 'success': True,
|
|
|
+ 'data': result
|
|
|
+ })
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f'读取寄存器失败: {str(e)}')
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message': str(e)
|
|
|
+ }), 500
|
|
|
+
|
|
|
+
|
|
|
+@app.route('/api/modbus/write_register', methods=['POST'])
|
|
|
+def modbus_write_register():
|
|
|
+ """写单个寄存器
|
|
|
+
|
|
|
+ 请求参数:
|
|
|
+ {
|
|
|
+ "device_address": 1,
|
|
|
+ "register_address": 1, // 寄存器地址
|
|
|
+ "value": 256, // 写入的值
|
|
|
+ "timeout": 1.0
|
|
|
+ }
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ data = request.json
|
|
|
+ device_address = data.get('device_address', 1)
|
|
|
+ register_address = data.get('register_address', 1)
|
|
|
+ value = data.get('value', 0)
|
|
|
+ timeout = data.get('timeout')
|
|
|
+
|
|
|
+ if not (isinstance(serial_client.get_status(), dict) and serial_client.get_status().get("connected", False)):
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message':'串口未连接'
|
|
|
+ }), 400
|
|
|
+
|
|
|
+ result = modbus_client.write_single_register(
|
|
|
+ device_address=device_address,
|
|
|
+ register_address=register_address,
|
|
|
+ value=value,
|
|
|
+ timeout=timeout
|
|
|
+ )
|
|
|
+
|
|
|
+ if 'error' in result:
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message': result['error'],
|
|
|
+ 'raw_data': result.get('raw_data', '')
|
|
|
+ }), 400
|
|
|
+
|
|
|
+ return jsonify({
|
|
|
+ 'success': True,
|
|
|
+ 'data': result
|
|
|
+ })
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f'写寄存器失败: {str(e)}')
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message': str(e)
|
|
|
+ }), 500
|
|
|
+
|
|
|
+
|
|
|
+@app.route('/api/modbus/set_rgb_led', methods=['POST'])
|
|
|
+def modbus_set_rgb_led():
|
|
|
+ """设置RGB灯状态
|
|
|
+
|
|
|
+ 请求参数:
|
|
|
+ {
|
|
|
+ "device_address": 1,
|
|
|
+ "led_number": 1, // 灯编号 (1-24)
|
|
|
+ "color": 1, // 颜色: 0=灭, 1=红灯, 2=绿灯, 3=蓝灯
|
|
|
+ "timeout": 1.0
|
|
|
+ }
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ data = request.json
|
|
|
+ device_address = data.get('device_address', 1)
|
|
|
+ led_number = data.get('led_number', 1)
|
|
|
+ color = data.get('color', 0)
|
|
|
+ timeout = data.get('timeout')
|
|
|
+
|
|
|
+ if not (isinstance(serial_client.get_status(), dict) and serial_client.get_status().get("connected", False)):
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message': '串口未连接'
|
|
|
+ }), 400
|
|
|
+
|
|
|
+ result = modbus_client.set_rgb_led(
|
|
|
+ device_address=device_address,
|
|
|
+ led_number=led_number,
|
|
|
+ color=color,
|
|
|
+ timeout=timeout
|
|
|
+ )
|
|
|
+
|
|
|
+ if 'error' in result:
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message': result['error'],
|
|
|
+ 'raw_data': result.get('raw_data', '')
|
|
|
+ }), 400
|
|
|
+
|
|
|
+ # 更新 LED 状态追踪
|
|
|
+ led_states[led_number] = color
|
|
|
+
|
|
|
+ return jsonify({
|
|
|
+ 'success': True,
|
|
|
+ 'data': result
|
|
|
+ })
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f'设置RGB灯失败: {str(e)}')
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message': str(e)
|
|
|
+ }), 500
|
|
|
+
|
|
|
+
|
|
|
+@app.route('/api/modbus/scan', methods=['POST'])
|
|
|
+def modbus_scan_devices():
|
|
|
+ """扫描在线设备
|
|
|
+
|
|
|
+ 请求参数:
|
|
|
+ {
|
|
|
+ "max_address": 247, // 最大设备地址
|
|
|
+ "timeout": 0.2 // 单个设备超时时间
|
|
|
+ }
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ data = request.json or {}
|
|
|
+ max_address = data.get('max_address', 247)
|
|
|
+ timeout = data.get('timeout', 0.2)
|
|
|
+
|
|
|
+ if not (isinstance(serial_client.get_status(), dict) and serial_client.get_status().get("connected", False)):
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message': '串口未连接'
|
|
|
+ }), 400
|
|
|
+
|
|
|
+ # 异步扫描可能更好,但这里先同步实现
|
|
|
+ result = modbus_client.scan_devices(max_address)
|
|
|
+
|
|
|
+ return jsonify({
|
|
|
+ 'success': True,
|
|
|
+ 'devices': result
|
|
|
+ })
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f'扫描设备失败: {str(e)}')
|
|
|
+ return jsonify({
|
|
|
+ 'success': False,
|
|
|
+ 'message': str(e)
|
|
|
+ }), 500
|
|
|
+
|
|
|
+
|
|
|
+# ========== 地址配置协议 API ==========
|
|
|
+
|
|
|
+@app.route('/api/modbus/broadcast_query', methods=['POST'])
|
|
|
+def modbus_broadcast_query():
|
|
|
+ """发送广播查询指令"""
|
|
|
+ try:
|
|
|
+ data = request.json or {}
|
|
|
+ timeout = data.get('timeout')
|
|
|
+
|
|
|
+ if not (isinstance(serial_client.get_status(), dict) and serial_client.get_status().get("connected", False)):
|
|
|
+ return jsonify({'success': False, 'message': '串口未连接'}), 400
|
|
|
+
|
|
|
+ responses = address_config.broadcast_query(timeout)
|
|
|
+ return jsonify({'success': True, 'responses': responses, 'count': len(responses)})
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f'广播查询失败: {str(e)}')
|
|
|
+ return jsonify({'success': False, 'message': str(e)}), 500
|
|
|
+
|
|
|
+
|
|
|
+@app.route('/api/modbus/auto_configure', methods=['POST'])
|
|
|
+def modbus_auto_configure():
|
|
|
+ """自动配置设备地址"""
|
|
|
+ try:
|
|
|
+ data = request.json or {}
|
|
|
+ timeout = data.get('timeout')
|
|
|
+
|
|
|
+ if not (isinstance(serial_client.get_status(), dict) and serial_client.get_status().get("connected", False)):
|
|
|
+ return jsonify({'success': False, 'message': '串口未连接'}), 400
|
|
|
+
|
|
|
+ result = address_config.auto_configure(timeout)
|
|
|
+ if result.get('success') and (result.get('discovered', 0) > 0 or result.get('confirmed', 0) > 0 or result.get('assigned', 0) > 0):
|
|
|
+ save_device_config()
|
|
|
+ return jsonify(result)
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f'自动配置失败: {str(e)}')
|
|
|
+ return jsonify({'success': False, 'message': str(e)}), 500
|
|
|
+
|
|
|
+
|
|
|
+@app.route('/api/modbus/stored_devices', methods=['GET'])
|
|
|
+def get_stored_devices():
|
|
|
+ """获取已存储的设备列表"""
|
|
|
+ return jsonify({'success': True, 'devices': address_config.get_stored_devices()})
|
|
|
+
|
|
|
+
|
|
|
+@app.route('/api/modbus/stored_devices', methods=['POST'])
|
|
|
+def add_stored_device():
|
|
|
+ """添加已存储的设备"""
|
|
|
+ try:
|
|
|
+ data = request.json
|
|
|
+ uid = data.get('uid', '').lower()
|
|
|
+ address = data.get('address', 1)
|
|
|
+
|
|
|
+ if len(uid) != 24:
|
|
|
+ return jsonify({'success': False, 'message': 'UID长度必须是24个十六进制字符'}), 400
|
|
|
+
|
|
|
+ address_config.add_stored_device(uid, address)
|
|
|
+ save_device_config()
|
|
|
+ return jsonify({'success': True, 'message': f'已添加设备: UID={uid}, 地址={address}'})
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ return jsonify({'success': False, 'message': str(e)}), 500
|
|
|
+
|
|
|
+
|
|
|
+@app.route('/api/modbus/load_config', methods=['POST'])
|
|
|
+def modbus_load_config():
|
|
|
+ """从文件加载设备配置"""
|
|
|
+ try:
|
|
|
+ data = request.json
|
|
|
+ filepath = data.get('filepath', '/tmp/modbus_devices.json')
|
|
|
+ success = address_config.load_config(filepath)
|
|
|
+ return jsonify({'success': success, 'devices': address_config.get_stored_devices()})
|
|
|
+ except Exception as e:
|
|
|
+ return jsonify({'success': False, 'message': str(e)}), 500
|
|
|
+
|
|
|
+
|
|
|
+@app.route('/api/modbus/save_config', methods=['POST'])
|
|
|
+def modbus_save_config():
|
|
|
+ """保存设备配置到文件"""
|
|
|
+ try:
|
|
|
+ data = request.json
|
|
|
+ filepath = data.get('filepath', '/tmp/modbus_devices.json')
|
|
|
+ success = address_config.save_config(filepath)
|
|
|
+ return jsonify({'success': success, 'message': f'配置已保存到: {filepath}' if success else '保存失败'})
|
|
|
+ except Exception as e:
|
|
|
+ return jsonify({'success': False, 'message': str(e)}), 500
|
|
|
+
|
|
|
+
|
|
|
+@app.route('/api/modbus/confirm_devices', methods=['POST'])
|
|
|
+def confirm_devices():
|
|
|
+ """手动触发对所有已存储设备的确认"""
|
|
|
+ try:
|
|
|
+ devices = address_config.get_stored_devices()
|
|
|
+ if not devices:
|
|
|
+ return jsonify({'success': False, 'message': '没有已存储的设备'}), 400
|
|
|
+
|
|
|
+ if not (isinstance(serial_client.get_status(), dict) and serial_client.get_status().get("connected", False)):
|
|
|
+ return jsonify({'success': False, 'message': '串口未连接'}), 400
|
|
|
+
|
|
|
+ results = []
|
|
|
+ for uid_hex, addr in devices.items():
|
|
|
+ try:
|
|
|
+ uid_bytes = bytes.fromhex(uid_hex)
|
|
|
+ cmd = build_confirm_address(addr, uid_bytes)
|
|
|
+ success, msg = serial_client.send_raw(cmd)
|
|
|
+ logger.info(f"确认设备 地址={addr}: {'成功' if success else '失败 ' + msg}")
|
|
|
+ results.append({'address': addr, 'uid': uid_hex, 'success': success, 'message': msg})
|
|
|
+ except Exception as e:
|
|
|
+ results.append({'address': addr, 'uid': uid_hex, 'success': False, 'message': str(e)})
|
|
|
+ time.sleep(0.1)
|
|
|
+
|
|
|
+ return jsonify({'success': True, 'results': results, 'count': len(results)})
|
|
|
+ except Exception as e:
|
|
|
+ return jsonify({'success': False, 'message': str(e)}), 500
|
|
|
+
|
|
|
+
|
|
|
+# LED 状态追踪 (内存中跟踪每个灯的最后设置状态)
|
|
|
+led_states = {i: 0 for i in range(1, 25)} # 0=off, 1=red, 2=green, 3=blue
|
|
|
+
|
|
|
+
|
|
|
+@app.route('/api/modbus/led_status', methods=['GET'])
|
|
|
+def get_led_status():
|
|
|
+ """获取所有 LED 状态"""
|
|
|
+ return jsonify({'success': True, 'leds': led_states})
|
|
|
+
|
|
|
+
|
|
|
+@app.route('/api/modbus/set_all_leds', methods=['POST'])
|
|
|
+def set_all_leds():
|
|
|
+ """批量设置 LED
|
|
|
+ 请求: {"device_address": 1, "color": 1}
|
|
|
+ 设置所有 24 个 LED 到指定颜色
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ data = request.json
|
|
|
+ device_address = data.get('device_address', 1)
|
|
|
+ color = data.get('color', 0)
|
|
|
+ timeout = data.get('timeout')
|
|
|
+
|
|
|
+ if not (isinstance(serial_client.get_status(), dict) and serial_client.get_status().get("connected", False)):
|
|
|
+ return jsonify({'success': False, 'message': '串口未连接'}), 400
|
|
|
+
|
|
|
+ results = []
|
|
|
+ for led_num in range(1, 25):
|
|
|
+ result = modbus_client.set_rgb_led(
|
|
|
+ device_address=device_address,
|
|
|
+ led_number=led_num,
|
|
|
+ color=color,
|
|
|
+ timeout=timeout
|
|
|
+ )
|
|
|
+ if 'error' in result:
|
|
|
+ results.append({'led': led_num, 'success': False, 'error': result['error']})
|
|
|
+ else:
|
|
|
+ results.append({'led': led_num, 'success': True})
|
|
|
+ led_states[led_num] = color
|
|
|
+
|
|
|
+ return jsonify({'success': True, 'results': results})
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f'批量设置LED失败: {str(e)}')
|
|
|
+ return jsonify({'success': False, 'message': str(e)}), 500
|
|
|
+
|
|
|
+
|
|
|
+# 在 set_rgb_led 中更新 LED 状态追踪
|
|
|
# 不再需要静态文件目录,前端由nginx提供服务
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
@@ -846,6 +1355,11 @@ if __name__ == '__main__':
|
|
|
# 启动前的初始化工作
|
|
|
logger.info('启动串口-MQTT网关服务...')
|
|
|
logger.info(f"配置信息: 主机={FLASK_HOST}, 端口={FLASK_PORT}, 调试模式={FLASK_DEBUG}")
|
|
|
+
|
|
|
+ # 加载设备配置
|
|
|
+ loaded = load_device_config()
|
|
|
+ logger.info(f"已加载 {len(loaded)} 个设备配置")
|
|
|
+ logger.info("设备确认线程已禁用(确认命令干扰运行中设备的Modbus通信)")
|
|
|
|
|
|
# 启动服务
|
|
|
socketio.run(
|
|
|
@@ -854,13 +1368,14 @@ if __name__ == '__main__':
|
|
|
port=FLASK_PORT,
|
|
|
debug=FLASK_DEBUG,
|
|
|
use_reloader=False, # 禁用重载器以避免重复初始化问题
|
|
|
- log_output=False # 禁用Flask的日志输出,使用我们自己的日志配置
|
|
|
+ log_output=False, # 禁用Flask的日志输出,使用我们自己的日志配置
|
|
|
+ allow_unsafe_werkzeug=True
|
|
|
)
|
|
|
except KeyboardInterrupt:
|
|
|
# 优雅退出
|
|
|
logger.info('正在关闭应用...')
|
|
|
try:
|
|
|
- if serial_client.get_status():
|
|
|
+ if isinstance(serial_client.get_status(), dict) and serial_client.get_status().get("connected", False):
|
|
|
serial_client.disconnect()
|
|
|
logger.info('串口连接已断开')
|
|
|
if mqtt_client.get_status():
|