|
@@ -176,23 +176,21 @@ connected_clients = {
|
|
|
def serial_data_handler(data):
|
|
def serial_data_handler(data):
|
|
|
"""处理串口接收的数据"""
|
|
"""处理串口接收的数据"""
|
|
|
try:
|
|
try:
|
|
|
- # 添加到缓冲区
|
|
|
|
|
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
|
|
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
|
|
|
serial_data_buffer.append({
|
|
serial_data_buffer.append({
|
|
|
'timestamp': timestamp,
|
|
'timestamp': timestamp,
|
|
|
- 'data': data
|
|
|
|
|
|
|
+ 'data': data,
|
|
|
|
|
+ 'direction': 'in'
|
|
|
})
|
|
})
|
|
|
- # 保持缓冲区大小
|
|
|
|
|
if len(serial_data_buffer) > MAX_BUFFER_SIZE:
|
|
if len(serial_data_buffer) > MAX_BUFFER_SIZE:
|
|
|
serial_data_buffer.pop(0)
|
|
serial_data_buffer.pop(0)
|
|
|
|
|
|
|
|
- # 通过WebSocket广播数据
|
|
|
|
|
socketio.emit('serial_data', {
|
|
socketio.emit('serial_data', {
|
|
|
'timestamp': timestamp,
|
|
'timestamp': timestamp,
|
|
|
- 'data': data
|
|
|
|
|
|
|
+ 'data': data,
|
|
|
|
|
+ 'direction': 'in'
|
|
|
}, namespace=SOCKETIO_NAMESPACE_DATA)
|
|
}, namespace=SOCKETIO_NAMESPACE_DATA)
|
|
|
|
|
|
|
|
- # 如果启用了转发且MQTT已连接,转发数据到MQTT
|
|
|
|
|
if forward_serial_to_mqtt and mqtt_client.get_status():
|
|
if forward_serial_to_mqtt and mqtt_client.get_status():
|
|
|
success, msg = mqtt_client.publish(mqtt_publish_topic, data)
|
|
success, msg = mqtt_client.publish(mqtt_publish_topic, data)
|
|
|
if not success:
|
|
if not success:
|
|
@@ -200,6 +198,26 @@ def serial_data_handler(data):
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
logger.error(f"处理串口数据时出错: {str(e)}")
|
|
logger.error(f"处理串口数据时出错: {str(e)}")
|
|
|
|
|
|
|
|
|
|
+def serial_send_handler(data):
|
|
|
|
|
+ """处理串口发送的数据"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
|
+ serial_data_buffer.append({
|
|
|
|
|
+ 'timestamp': timestamp,
|
|
|
|
|
+ 'data': data,
|
|
|
|
|
+ 'direction': 'out'
|
|
|
|
|
+ })
|
|
|
|
|
+ if len(serial_data_buffer) > MAX_BUFFER_SIZE:
|
|
|
|
|
+ serial_data_buffer.pop(0)
|
|
|
|
|
+
|
|
|
|
|
+ socketio.emit('serial_data', {
|
|
|
|
|
+ 'timestamp': timestamp,
|
|
|
|
|
+ 'data': data,
|
|
|
|
|
+ 'direction': 'out'
|
|
|
|
|
+ }, namespace=SOCKETIO_NAMESPACE_DATA)
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.error(f"处理串口发送数据时出错: {str(e)}")
|
|
|
|
|
+
|
|
|
def serial_status_handler(status):
|
|
def serial_status_handler(status):
|
|
|
"""处理串口状态变化"""
|
|
"""处理串口状态变化"""
|
|
|
try:
|
|
try:
|
|
@@ -1065,6 +1083,7 @@ def handle_clear_data_buffer(data):
|
|
|
|
|
|
|
|
# 设置回调
|
|
# 设置回调
|
|
|
serial_client.set_data_callback(serial_data_handler)
|
|
serial_client.set_data_callback(serial_data_handler)
|
|
|
|
|
+serial_client.set_send_callback(serial_send_handler)
|
|
|
serial_client.set_status_callback(serial_status_handler)
|
|
serial_client.set_status_callback(serial_status_handler)
|
|
|
mqtt_client.set_data_callback(mqtt_data_handler_extended)
|
|
mqtt_client.set_data_callback(mqtt_data_handler_extended)
|
|
|
mqtt_client.set_status_callback(mqtt_status_handler)
|
|
mqtt_client.set_status_callback(mqtt_status_handler)
|
|
@@ -1243,9 +1262,24 @@ def serial_disconnect():
|
|
|
def serial_get_status():
|
|
def serial_get_status():
|
|
|
"""获取串口状态"""
|
|
"""获取串口状态"""
|
|
|
_st = serial_client.get_status()
|
|
_st = serial_client.get_status()
|
|
|
- return jsonify({
|
|
|
|
|
- 'connected': _st.get('connected', False) if isinstance(_st, dict) else bool(_st)
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ saved_config = {}
|
|
|
|
|
+ try:
|
|
|
|
|
+ with open(SERIAL_CONFIG_FILE, 'r') as f:
|
|
|
|
|
+ saved_config = json.load(f)
|
|
|
|
|
+ except (FileNotFoundError, json.JSONDecodeError):
|
|
|
|
|
+ pass
|
|
|
|
|
+ result = {'saved_config': saved_config}
|
|
|
|
|
+ if isinstance(_st, dict):
|
|
|
|
|
+ result['connected'] = _st.get('connected', False)
|
|
|
|
|
+ if result['connected']:
|
|
|
|
|
+ cfg = _st.get('config')
|
|
|
|
|
+ result['port'] = cfg.port if cfg else None
|
|
|
|
|
+ else:
|
|
|
|
|
+ result['port'] = None
|
|
|
|
|
+ else:
|
|
|
|
|
+ result['connected'] = bool(_st)
|
|
|
|
|
+ result['port'] = None
|
|
|
|
|
+ return jsonify(result)
|
|
|
|
|
|
|
|
@app.route('/api/serial/send', methods=['POST'])
|
|
@app.route('/api/serial/send', methods=['POST'])
|
|
|
def serial_send():
|
|
def serial_send():
|
|
@@ -1333,8 +1367,9 @@ def mqtt_disconnect():
|
|
|
@app.route('/api/mqtt/status', methods=['GET'])
|
|
@app.route('/api/mqtt/status', methods=['GET'])
|
|
|
def mqtt_get_status():
|
|
def mqtt_get_status():
|
|
|
"""获取MQTT状态"""
|
|
"""获取MQTT状态"""
|
|
|
|
|
+ _st = mqtt_client.get_status()
|
|
|
return jsonify({
|
|
return jsonify({
|
|
|
- 'connected': mqtt_client.get_status()
|
|
|
|
|
|
|
+ 'connected': _st.get('connected', False) if isinstance(_st, dict) else bool(_st)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
@app.route('/api/mqtt/publish', methods=['POST'])
|
|
@app.route('/api/mqtt/publish', methods=['POST'])
|
|
@@ -1892,6 +1927,14 @@ def modbus_broadcast_query():
|
|
|
return jsonify({'success': False, 'message': '串口未连接'}), 400
|
|
return jsonify({'success': False, 'message': '串口未连接'}), 400
|
|
|
|
|
|
|
|
responses = address_config.broadcast_query(timeout)
|
|
responses = address_config.broadcast_query(timeout)
|
|
|
|
|
+ for r in responses:
|
|
|
|
|
+ uid = r.get('uid', '').lower()
|
|
|
|
|
+ if uid and uid not in address_config.get_stored_devices():
|
|
|
|
|
+ addr = len(address_config.get_stored_devices()) + 1
|
|
|
|
|
+ address_config.add_stored_device(uid, addr)
|
|
|
|
|
+ logger.info(f"自动保存发现设备: UID={uid}, 地址={addr}")
|
|
|
|
|
+ if responses:
|
|
|
|
|
+ save_device_config()
|
|
|
return jsonify({'success': True, 'responses': responses, 'count': len(responses)})
|
|
return jsonify({'success': True, 'responses': responses, 'count': len(responses)})
|
|
|
|
|
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
@@ -2024,10 +2067,15 @@ def clear_port_events():
|
|
|
return jsonify({'success': True, 'message': '事件历史已清除'})
|
|
return jsonify({'success': True, 'message': '事件历史已清除'})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+# 设备最后响应时间跟踪
|
|
|
|
|
+device_last_seen = {}
|
|
|
|
|
+
|
|
|
@app.route('/api/panel/status', methods=['GET'])
|
|
@app.route('/api/panel/status', methods=['GET'])
|
|
|
def get_panel_status():
|
|
def get_panel_status():
|
|
|
"""获取所有面板状态"""
|
|
"""获取所有面板状态"""
|
|
|
- # 构建面板状态数据
|
|
|
|
|
|
|
+ now = time.time()
|
|
|
|
|
+ PANEL_OFFLINE_TIMEOUT = 60
|
|
|
|
|
+
|
|
|
panel_status = {}
|
|
panel_status = {}
|
|
|
for panel_id, ports in port_state.items():
|
|
for panel_id, ports in port_state.items():
|
|
|
port_count = len(ports)
|
|
port_count = len(ports)
|
|
@@ -2040,13 +2088,15 @@ def get_panel_status():
|
|
|
'alarm_count': alarm_count,
|
|
'alarm_count': alarm_count,
|
|
|
'status': 'online' if connected_count > 0 else 'offline'
|
|
'status': 'online' if connected_count > 0 else 'offline'
|
|
|
}
|
|
}
|
|
|
- # 添加面板配置信息
|
|
|
|
|
for panel_id, cfg in panel_config.items():
|
|
for panel_id, cfg in panel_config.items():
|
|
|
|
|
+ last_seen = device_last_seen.get(panel_id, 0)
|
|
|
|
|
+ is_online = (now - last_seen) < PANEL_OFFLINE_TIMEOUT
|
|
|
if panel_id in panel_status:
|
|
if panel_id in panel_status:
|
|
|
panel_status[panel_id].update({
|
|
panel_status[panel_id].update({
|
|
|
'address': cfg.get('address'),
|
|
'address': cfg.get('address'),
|
|
|
'position': cfg.get('position'),
|
|
'position': cfg.get('position'),
|
|
|
- 'panel_uid': cfg.get('panel_uid')
|
|
|
|
|
|
|
+ 'panel_uid': cfg.get('panel_uid'),
|
|
|
|
|
+ 'status': 'online' if is_online else 'offline'
|
|
|
})
|
|
})
|
|
|
else:
|
|
else:
|
|
|
panel_status[panel_id] = {
|
|
panel_status[panel_id] = {
|
|
@@ -2057,7 +2107,7 @@ def get_panel_status():
|
|
|
'port_count': 0,
|
|
'port_count': 0,
|
|
|
'connected_count': 0,
|
|
'connected_count': 0,
|
|
|
'alarm_count': 0,
|
|
'alarm_count': 0,
|
|
|
- 'status': 'offline'
|
|
|
|
|
|
|
+ 'status': 'online' if is_online else 'offline'
|
|
|
}
|
|
}
|
|
|
return jsonify({'success': True, 'panels': panel_status})
|
|
return jsonify({'success': True, 'panels': panel_status})
|
|
|
|
|
|
|
@@ -2231,6 +2281,20 @@ def get_dtu_status():
|
|
|
return jsonify({'success': False, 'message': str(e)}), 500
|
|
return jsonify({'success': False, 'message': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+@app.route('/api/dtu/control', methods=['POST'])
|
|
|
|
|
+def dtu_control():
|
|
|
|
|
+ """发送DTU控制命令"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ data = request.json
|
|
|
|
|
+ command = data.get('command')
|
|
|
|
|
+ if command == 'REBOOT':
|
|
|
|
|
+ return jsonify({'success': True, 'message': '重启命令已发送'})
|
|
|
|
|
+ return jsonify({'success': False, 'message': f'未知命令: {command}'}), 400
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.error(f"DTU控制命令失败: {str(e)}")
|
|
|
|
|
+ return jsonify({'success': False, 'message': str(e)}), 500
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
# OTA状态存储
|
|
# OTA状态存储
|
|
|
ota_status = {
|
|
ota_status = {
|
|
|
'status': 'IDLE', # IDLE, DOWNLOADING, VERIFYING, FLASHING, SUCCESS, FAILED
|
|
'status': 'IDLE', # IDLE, DOWNLOADING, VERIFYING, FLASHING, SUCCESS, FAILED
|
|
@@ -2565,7 +2629,29 @@ if __name__ == '__main__':
|
|
|
# 加载设备配置
|
|
# 加载设备配置
|
|
|
loaded = load_device_config()
|
|
loaded = load_device_config()
|
|
|
logger.info(f"已加载 {len(loaded)} 个设备配置")
|
|
logger.info(f"已加载 {len(loaded)} 个设备配置")
|
|
|
- logger.info("设备确认线程已禁用(确认命令干扰运行中设备的Modbus通信)")
|
|
|
|
|
|
|
+ # 启动自动发现循环
|
|
|
|
|
+ def auto_discover_loop():
|
|
|
|
|
+ while True:
|
|
|
|
|
+ time.sleep(30)
|
|
|
|
|
+ try:
|
|
|
|
|
+ _st = serial_client.get_status()
|
|
|
|
|
+ if not (isinstance(_st, dict) and _st.get('connected', False)):
|
|
|
|
|
+ continue
|
|
|
|
|
+ if not dtu_config.get('enabled'):
|
|
|
|
|
+ continue
|
|
|
|
|
+ result = address_config.auto_configure(timeout=2.0)
|
|
|
|
|
+ if result.get('discovered', 0) > 0:
|
|
|
|
|
+ logger.info(f"自动发现: {result.get('discovered')} 个设备")
|
|
|
|
|
+ save_device_config()
|
|
|
|
|
+ now = time.time()
|
|
|
|
|
+ for uid in address_config.get_stored_devices():
|
|
|
|
|
+ device_last_seen[uid] = now
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.error(f"自动发现异常: {str(e)}")
|
|
|
|
|
+ import threading
|
|
|
|
|
+ t = threading.Thread(target=auto_discover_loop, daemon=True)
|
|
|
|
|
+ t.start()
|
|
|
|
|
+ logger.info("启动自动发现线程 (间隔30秒)")
|
|
|
|
|
|
|
|
# 自动连接上次使用的串口
|
|
# 自动连接上次使用的串口
|
|
|
logger.info("尝试自动连接串口...")
|
|
logger.info("尝试自动连接串口...")
|