From 505aa3f199b075e6bf2301321ab8eb1e9e5a3658 Mon Sep 17 00:00:00 2001 From: qinjian Date: Tue, 23 Dec 2025 15:48:57 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=A2=9E=E5=BC=BA=20WebSocket=20?= =?UTF-8?q?=E5=A4=84=E7=90=86=E6=9C=BA=E5=88=B6=EF=BC=8C=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E9=AB=98=E7=BA=A7=E8=AE=BE=E7=BD=AE=E7=AE=A1=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=20-=20=E6=96=B0=E5=A2=9E=20WebSocket=20=E5=B0=B1?= =?UTF-8?q?=E7=BB=AA=E7=8A=B6=E6=80=81=E6=A0=87=E8=AF=86=EF=BC=8C=E7=A1=AE?= =?UTF-8?q?=E4=BF=9D=E4=BB=85=E5=9C=A8=E8=BF=9E=E6=8E=A5=E5=AE=8C=E5=85=A8?= =?UTF-8?q?=E5=BB=BA=E7=AB=8B=E5=90=8E=E5=8F=91=E9=80=81=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E3=80=82=20-=20=E5=AE=9E=E7=8E=B0=E6=B6=88=E6=81=AF=E9=98=9F?= =?UTF-8?q?=E5=88=97=E5=8A=9F=E8=83=BD=EF=BC=8C=E7=94=A8=E4=BA=8E=E5=A4=84?= =?UTF-8?q?=E7=90=86=20WebSocket=20=E5=B0=B1=E7=BB=AA=E5=89=8D=E5=8F=91?= =?UTF-8?q?=E9=80=81=E7=9A=84=E6=B6=88=E6=81=AF=E3=80=82=20-=20=E5=8D=87?= =?UTF-8?q?=E7=BA=A7=20AdvancedSettings=20=E7=BB=84=E4=BB=B6=EF=BC=8C?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E8=87=AA=E5=AE=9A=E4=B9=89=E9=92=A9=E5=AD=90?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E7=AE=A1=E7=90=86=E8=AE=BE=E5=A4=87=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E6=B6=B5=E7=9B=96=E9=85=8D=E7=BD=AE=E7=9A=84?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E4=B8=8E=E4=BF=9D=E5=AD=98=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E3=80=82=20-=20=E6=96=B0=E5=A2=9E=E5=AE=8C=E5=96=84=E7=9A=84?= =?UTF-8?q?=20WebSocket=20=E6=8C=87=E4=BB=A4=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E6=94=AF=E6=8C=81=E8=AE=BE=E5=A4=87=E7=BC=96?= =?UTF-8?q?=E5=8F=B7=E3=80=81=E6=95=B0=E6=8D=AE=E5=B8=A7=E7=8E=87=E3=80=81?= =?UTF-8?q?=E5=91=8A=E8=AD=A6=E9=85=8D=E7=BD=AE=E3=80=81=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E8=A7=84=E5=88=99=E5=8F=8A=20MQTT=20=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E7=9A=84=E7=9B=B8=E5=85=B3=E6=8C=87=E4=BB=A4=E3=80=82=20-=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=94=A8=E6=88=B7=E5=8F=8D=E9=A6=88=E4=BD=93?= =?UTF-8?q?=E9=AA=8C=EF=BC=8C=E6=B7=BB=E5=8A=A0=E5=8A=A0=E8=BD=BD=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=8C=87=E7=A4=BA=E5=99=A8=E5=8F=8A=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E5=91=8A=E8=AD=A6=E6=8F=90=E7=A4=BA=E3=80=82?= =?UTF-8?q?=20-=20=E7=BC=96=E5=86=99=E9=AB=98=E7=BA=A7=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E4=B8=AD=20WebSocket=20=E5=8D=8F=E8=AE=AE=E7=9A=84=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E4=BD=BF=E7=94=A8=E6=96=87=E6=A1=A3=E3=80=82=20-=20?= =?UTF-8?q?=E5=B0=86=20package.json=20=E6=96=87=E4=BB=B6=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8F=B7=E6=9B=B4=E6=96=B0=E8=87=B3=201.1.0?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wuyuanbiaoba/actions/websocket.jsx | 113 ++++- .../components/AdvancedSettings.jsx | 338 ++++++++---- .../wuyuanbiaoba/components/CameraView.jsx | 1 - .../sections/wuyuanbiaoba/container/index.jsx | 3 +- .../wuyuanbiaoba/hooks/useAdvancedSettings.js | 479 ++++++++++++++++++ .../wuyuanbiaoba/hooks/高级配置说明.md | 246 +++++++++ package.json | 2 +- server/tcpProxy/index.js | 34 +- 8 files changed, 1083 insertions(+), 133 deletions(-) create mode 100644 client/src/sections/wuyuanbiaoba/hooks/useAdvancedSettings.js create mode 100644 client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md diff --git a/client/src/sections/wuyuanbiaoba/actions/websocket.jsx b/client/src/sections/wuyuanbiaoba/actions/websocket.jsx index f2d379d..f76b82c 100644 --- a/client/src/sections/wuyuanbiaoba/actions/websocket.jsx +++ b/client/src/sections/wuyuanbiaoba/actions/websocket.jsx @@ -125,6 +125,7 @@ const WebSocketContext = createContext(); // WebSocket Provider组件 export const WebSocketProvider = ({ children }) => { const [isConnected, setIsConnected] = useState(false); + const [isReady, setIsReady] = useState(false); // 新增:连接真正就绪状态 const [connectionStatus, setConnectionStatus] = useState("disconnected"); const [lastMessage, setLastMessage] = useState(null); // 最后接收的消息 const [messageHistory, setMessageHistory] = useState([]); // 消息历史 @@ -132,9 +133,12 @@ export const WebSocketProvider = ({ children }) => { const reconnectTimeoutRef = useRef(null); const reconnectAttemptsRef = useRef(0); const subscriptionsRef = useRef(new Map()); // 订阅器存储 + const messageQueueRef = useRef([]); // 新增:消息队列 + const readyCheckTimeoutRef = useRef(null); // 新增:就绪检查定时器 const maxReconnectAttempts = 5; const reconnectInterval = 3000; // 3秒 const maxHistoryLength = 100; // 最大历史消息数量 + const READY_TIMEOUT = 1500; // 连接建立后等待1.5秒确保TCP链路就绪 // 动态获取WebSocket连接地址,支持局域网访问 const getWebSocketUrl = () => { @@ -217,6 +221,24 @@ export const WebSocketProvider = ({ children }) => { return; } + // 处理代理服务器的就绪信号 + if (parsedData._from === 'proxy' && parsedData.cmd === 'ready') { + console.log('🟢 收到TCP连接就绪信号'); + + // 清除原有的定时器 + if (readyCheckTimeoutRef.current) { + clearTimeout(readyCheckTimeoutRef.current); + readyCheckTimeoutRef.current = null; + } + + // 立即设置为就绪状态 + setIsReady(true); + + // 发送队列中的消息 + flushMessageQueue(); + return; + } + // console.log(`收到消息 [${parsedData._from}:${parsedData.cmd}]:`, parsedData); } catch (error) { console.error("解析WebSocket消息失败:", error, data); @@ -243,22 +265,60 @@ export const WebSocketProvider = ({ children }) => { [notifySubscribers] ); + // 处理队列中的消息 + const flushMessageQueue = useCallback(() => { + if (messageQueueRef.current.length === 0) return; + + console.log(`📤 开始发送队列中的 ${messageQueueRef.current.length} 条消息`); + + const queue = [...messageQueueRef.current]; + messageQueueRef.current = []; + + queue.forEach((message, index) => { + setTimeout(() => { + if (socketRef.current?.readyState === WebSocket.OPEN) { + try { + socketRef.current.send(message); + console.log(`✅ 队列消息 ${index + 1}/${queue.length} 已发送`); + } catch (error) { + console.error(`❌ 发送队列消息 ${index + 1} 失败:`, error); + // 发送失败的消息重新加入队列 + messageQueueRef.current.push(message); + } + } + }, index * 100); // 每条消息间隔100ms发送,避免拥塞 + }); + }, []); + // 发送消息 const sendMessage = useCallback((message) => { - if (socketRef.current?.readyState === WebSocket.OPEN) { + // 如果连接就绪,直接发送 + if (socketRef.current?.readyState === WebSocket.OPEN && isReady) { try { socketRef.current.send(message); - // console.log('发送WebSocket消息:', message); return true; } catch (error) { console.error("发送WebSocket消息失败:", error); return false; } - } else { - console.warn("WebSocket未连接,无法发送消息"); - return false; } - }, []); + // 如果已连接但未就绪,加入队列 + else if (socketRef.current?.readyState === WebSocket.OPEN && !isReady) { + const cmdMatch = message.match(/"cmd"\s*:\s*"([^"]+)"/); + const cmd = cmdMatch ? cmdMatch[1] : 'unknown'; + console.log(`⏳ 连接尚未完全就绪,消息 [${cmd}] 已加入队列`); + messageQueueRef.current.push(message); + return true; + } + // 如果未连接,加入队列 + else { + const cmdMatch = message.match(/"cmd"\s*:\s*"([^"]+)"/); + const cmd = cmdMatch ? cmdMatch[1] : 'unknown'; + console.warn(`⚠️ WebSocket未连接,消息 [${cmd}] 已加入队列,等待连接建立`); + messageQueueRef.current.push(message); + return true; + } + }, [isReady]); // 连接WebSocket const connect = useCallback(() => { @@ -267,23 +327,24 @@ export const WebSocketProvider = ({ children }) => { } setConnectionStatus("connecting"); + setIsReady(false); // 重置就绪状态 console.log("尝试连接WebSocket:", websocketUrl); try { socketRef.current = new WebSocket(websocketUrl); socketRef.current.onopen = () => { - console.log("WebSocket连接已建立"); + console.log("✅ WebSocket连接已建立"); setIsConnected(true); setConnectionStatus("connected"); reconnectAttemptsRef.current = 0; - // 发送初始消息或心跳 - // sendMessage(JSON.stringify({ - // _from: 'setup', - // cmd: 'init', - // values: { timestamp: Date.now() } - // })); + // 等待一段时间确保底层TCP连接完全建立 + // 如果在此期间收到代理的 ready 信号,则会提前取消这个定时器 + readyCheckTimeoutRef.current = setTimeout(() => { + setIsReady(true); + flushMessageQueue(); + }, READY_TIMEOUT); }; socketRef.current.onmessage = (event) => { @@ -300,21 +361,28 @@ export const WebSocketProvider = ({ children }) => { }; socketRef.current.onclose = (event) => { - console.log("WebSocket连接已关闭:", event.code, event.reason); + console.log("❌ WebSocket连接已关闭:", event.code, event.reason); setIsConnected(false); + setIsReady(false); // 重置就绪状态 setConnectionStatus("disconnected"); + // 清除就绪检查定时器 + if (readyCheckTimeoutRef.current) { + clearTimeout(readyCheckTimeoutRef.current); + readyCheckTimeoutRef.current = null; + } + // 自动重连逻辑 if (reconnectAttemptsRef.current < maxReconnectAttempts) { reconnectAttemptsRef.current++; console.log( - `尝试重连 ${reconnectAttemptsRef.current}/${maxReconnectAttempts}` + `🔄 尝试重连 ${reconnectAttemptsRef.current}/${maxReconnectAttempts}` ); reconnectTimeoutRef.current = setTimeout(() => { connect(); }, reconnectInterval); } else { - console.log("达到最大重连次数,停止重连"); + console.log("🛑 达到最大重连次数,停止重连"); setConnectionStatus("error"); } }; @@ -326,8 +394,9 @@ export const WebSocketProvider = ({ children }) => { } catch (error) { console.error("创建WebSocket连接失败:", error); setConnectionStatus("error"); + setIsReady(false); } - }, [handleMessage, sendMessage]); + }, [handleMessage, flushMessageQueue]); // 断开连接 const disconnect = useCallback(() => { @@ -335,12 +404,18 @@ export const WebSocketProvider = ({ children }) => { clearTimeout(reconnectTimeoutRef.current); } + if (readyCheckTimeoutRef.current) { + clearTimeout(readyCheckTimeoutRef.current); + readyCheckTimeoutRef.current = null; + } + if (socketRef.current) { socketRef.current.close(1000, "用户主动断开"); socketRef.current = null; } setIsConnected(false); + setIsReady(false); setConnectionStatus("disconnected"); reconnectAttemptsRef.current = 0; }, []); @@ -373,6 +448,9 @@ export const WebSocketProvider = ({ children }) => { if (reconnectTimeoutRef.current) { clearTimeout(reconnectTimeoutRef.current); } + if (readyCheckTimeoutRef.current) { + clearTimeout(readyCheckTimeoutRef.current); + } if (socketRef.current) { socketRef.current.close(); } @@ -383,6 +461,7 @@ export const WebSocketProvider = ({ children }) => { const value = { // 连接状态 isConnected, + isReady, // 新增:暴露就绪状态 connectionStatus, // 连接控制 diff --git a/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx b/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx index 4f4eabe..5cd71ca 100644 --- a/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx +++ b/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect } from "react"; import { Card, Input, @@ -12,6 +12,8 @@ import { message, Space, Typography, + Spin, + Alert, } from "antd"; import { SettingOutlined, @@ -22,122 +24,251 @@ import { BellOutlined, FilterOutlined, CloudUploadOutlined, + SyncOutlined, } from "@ant-design/icons"; +import useAdvancedSettings from "../hooks/useAdvancedSettings"; const { Option } = Select; const { Title, Text } = Typography; const AdvancedSettings = ({ onLogout }) => { - const [deviceId, setDeviceId] = useState(""); - const [fps, setFps] = useState(5); - const [enableOfflineAlert, setEnableOfflineAlert] = useState(false); - const [offlineThreshold, setOfflineThreshold] = useState(60); - const [enableFiltering, setEnableFiltering] = useState(true); - const [filterMethod, setFilterMethod] = useState("median"); - const [windowSize, setWindowSize] = useState(5); - const [filterThreshold, setFilterThreshold] = useState(0.1); - const [flowThreshold, setFlowThreshold] = useState(10.0); - const [enableMqtt, setEnableMqtt] = useState(true); - const [brokerAddress, setBrokerAddress] = useState(""); - const [mqttPort, setMqttPort] = useState(""); - const [mqttTopic, setMqttTopic] = useState(""); - const [mqttClientId, setMqttClientId] = useState(""); - const [mqttUsername, setMqttUsername] = useState(""); - const [mqttPassword, setMqttPassword] = useState(""); + // 使用高级配置 Hook + const { + settings, + loading, + isConnected, + isReady, + fetchAllSettings, + saveAllSettings, + resetSettings, + updateLocalSettings, + } = useAdvancedSettings(); + + // 从 settings 中提取配置 + const deviceId = settings.deviceId; + const fps = settings.dataFps; + const enableOfflineAlert = settings.alertConfig?.enable ?? false; + const offlineThreshold = settings.alertConfig?.intervalSec ?? 60; + const enableFiltering = settings.filterConfig?.enable ?? true; + const filterMethod = settings.filterConfig?.method ?? 'median'; + const windowSize = settings.filterConfig?.size ?? 5; + const filterThreshold = Math.abs(settings.filterConfig?.threshold ?? 0.1); + const flowThreshold = settings.filterConfig?.imgThreshold ?? 10.0; + const enableMqtt = settings.mqttConfig?.enable ?? true; + const brokerAddress = settings.mqttConfig?.mqtt?.broker ?? ''; + const mqttPort = settings.mqttConfig?.mqtt?.port ?? 1883; + const mqttTopic = settings.mqttConfig?.mqtt?.topic ?? ''; + const mqttClientId = settings.mqttConfig?.mqtt?.client_id ?? ''; + const mqttUsername = settings.mqttConfig?.mqtt?.username ?? ''; + const mqttPassword = settings.mqttConfig?.mqtt?.password ?? ''; + + // 更新本地状态的辅助函数 + const setDeviceId = (value) => updateLocalSettings({ deviceId: value }); + const setFps = (value) => updateLocalSettings({ dataFps: value }); + const setEnableOfflineAlert = (value) => + updateLocalSettings({ + alertConfig: { + enable: value, + intervalSec: settings.alertConfig?.intervalSec ?? 60 + }, + }); + const setOfflineThreshold = (value) => + updateLocalSettings({ + alertConfig: { + enable: settings.alertConfig?.enable ?? false, + intervalSec: value + }, + }); + const setEnableFiltering = (value) => + updateLocalSettings({ + filterConfig: { ...settings.filterConfig, enable: value }, + }); + const setFilterMethod = (value) => + updateLocalSettings({ + filterConfig: { ...settings.filterConfig, method: value }, + }); + const setWindowSize = (value) => + updateLocalSettings({ + filterConfig: { ...settings.filterConfig, size: value }, + }); + const setFilterThreshold = (value) => + updateLocalSettings({ + filterConfig: { ...settings.filterConfig, threshold: -Math.abs(value) }, + }); + const setFlowThreshold = (value) => + updateLocalSettings({ + filterConfig: { ...settings.filterConfig, imgThreshold: value }, + }); + const setEnableMqtt = (value) => + updateLocalSettings({ + mqttConfig: { + enable: value, + mqtt: settings.mqttConfig?.mqtt ?? { + broker: '', + port: 1883, + topic: '', + username: '', + password: '', + client_id: '' + } + }, + }); + const setBrokerAddress = (value) => + updateLocalSettings({ + mqttConfig: { + ...settings.mqttConfig, + mqtt: { + ...(settings.mqttConfig?.mqtt ?? {}), + broker: value + }, + }, + }); + const setMqttPort = (value) => + updateLocalSettings({ + mqttConfig: { + ...settings.mqttConfig, + mqtt: { + ...(settings.mqttConfig?.mqtt ?? {}), + port: value + }, + }, + }); + const setMqttTopic = (value) => + updateLocalSettings({ + mqttConfig: { + ...settings.mqttConfig, + mqtt: { + ...(settings.mqttConfig?.mqtt ?? {}), + topic: value + }, + }, + }); + const setMqttClientId = (value) => + updateLocalSettings({ + mqttConfig: { + ...settings.mqttConfig, + mqtt: { + ...(settings.mqttConfig?.mqtt ?? {}), + client_id: value + }, + }, + }); + const setMqttUsername = (value) => + updateLocalSettings({ + mqttConfig: { + ...settings.mqttConfig, + mqtt: { + ...(settings.mqttConfig?.mqtt ?? {}), + username: value + }, + }, + }); + const setMqttPassword = (value) => + updateLocalSettings({ + mqttConfig: { + ...settings.mqttConfig, + mqtt: { + ...(settings.mqttConfig?.mqtt ?? {}), + password: value + }, + }, + }); + + const handleSave = () => { + saveAllSettings(); + }; - const handleSave = () => message.success("配置已保存"); const handleReset = () => { - setDeviceId(""); - setFps(5); - setEnableOfflineAlert(false); - setOfflineThreshold(60); - setEnableFiltering(true); - setFilterMethod("median"); - setWindowSize(5); - setFilterThreshold(0.1); - setFlowThreshold(10.0); - setEnableMqtt(true); - setBrokerAddress(""); - setMqttPort(""); - setMqttTopic(""); - setMqttClientId(""); - setMqttUsername(""); - setMqttPassword(""); - message.info("配置已重置"); + fetchAllSettings(); }; return ( -
- +
- - - - - <SettingOutlined /> - 高级参数配置 - - - 针对下位机运行的参数配置,修改后请及时保存(慎用) - - - - - - - - - - - + {/* WebSocket 连接状态提示 */} + {!isConnected && ( + + )} + + + + + + + <SettingOutlined /> + 高级参数配置 + + + 针对下位机运行的参数配置,修改后请及时保存(慎用) + + + + + + + + + + + + @@ -487,6 +618,7 @@ const AdvancedSettings = ({ onLogout }) => {
+ ); }; diff --git a/client/src/sections/wuyuanbiaoba/components/CameraView.jsx b/client/src/sections/wuyuanbiaoba/components/CameraView.jsx index 7a0db36..2fb077d 100644 --- a/client/src/sections/wuyuanbiaoba/components/CameraView.jsx +++ b/client/src/sections/wuyuanbiaoba/components/CameraView.jsx @@ -59,7 +59,6 @@ const CameraView = ({ if (window.env && window.env.FS_FLAG === "localdev") { streamUrl = `http://10.8.30.179:2240/video_flow`; //开发用 } - console.log(streamUrl,'测试') // 应用变换 const applyTransform = () => { diff --git a/client/src/sections/wuyuanbiaoba/container/index.jsx b/client/src/sections/wuyuanbiaoba/container/index.jsx index b21bb96..44e16dd 100644 --- a/client/src/sections/wuyuanbiaoba/container/index.jsx +++ b/client/src/sections/wuyuanbiaoba/container/index.jsx @@ -27,7 +27,7 @@ const { Title } = Typography; // 内部组件,使用WebSocket hook const WuyuanbiaobaContent = () => { - const { isConnected, sendMessage } = useWebSocket(); + const { isConnected, isReady, sendMessage } = useWebSocket(); // 订阅实时数据 const realtimeDataSubscription = useWebSocketSubscription("dev", "data"); @@ -513,6 +513,7 @@ const WuyuanbiaobaContent = () => { > 视觉位移计配置工具 + { + const { isConnected, isReady, sendMessage } = useWebSocket(); + + // 配置状态 + const [settings, setSettings] = useState(DEFAULT_SETTINGS); + const [loading, setLoading] = useState(false); + const [fetchStatus, setFetchStatus] = useState({}); + + // WebSocket 订阅 - 监听所有来自设备的响应 + const { latest: getIdResponse } = useWebSocketSubscription('dev', 'getId'); + const { latest: setIdResponse } = useWebSocketSubscription('dev', 'setId'); + const { latest: getDataFpsResponse } = useWebSocketSubscription('dev', 'getDataFps'); + const { latest: setDataFpsResponse } = useWebSocketSubscription('dev', 'setDataFps'); + const { latest: getAlertResponse } = useWebSocketSubscription('dev', 'getAlert'); + const { latest: setAlertResponse } = useWebSocketSubscription('dev', 'setAlert'); + const { latest: getWinResponse } = useWebSocketSubscription('dev', 'getWin'); + const { latest: setWinResponse } = useWebSocketSubscription('dev', 'setWin'); + const { latest: getMqttResponse } = useWebSocketSubscription('dev', 'getMqtt'); + const { latest: setMqttResponse } = useWebSocketSubscription('dev', 'setMqtt'); + + /** + * 处理设备编码读取响应 + */ + useEffect(() => { + if (getIdResponse?.values?.id !== undefined) { + console.log('📥 收到设备编码:', getIdResponse.values.id); + setSettings(prev => ({ + ...prev, + deviceId: getIdResponse.values.id + })); + setFetchStatus(prev => ({ ...prev, deviceId: 'success' })); + } + }, [getIdResponse]); + + /** + * 处理设备编码设置响应 + */ + useEffect(() => { + if (setIdResponse?.values?.operate !== undefined) { + if (setIdResponse.values.operate) { + message.success('设备编码设置成功'); + } else { + message.error('设备编码设置失败'); + } + } + }, [setIdResponse]); + + /** + * 处理数据帧率读取响应 + */ + useEffect(() => { + if (getDataFpsResponse?.values?.dataFps !== undefined) { + console.log('📥 收到数据帧率:', getDataFpsResponse.values.dataFps); + setSettings(prev => ({ + ...prev, + dataFps: getDataFpsResponse.values.dataFps + })); + setFetchStatus(prev => ({ ...prev, dataFps: 'success' })); + } + }, [getDataFpsResponse]); + + /** + * 处理数据帧率设置响应 + */ + useEffect(() => { + if (setDataFpsResponse?.values?.operate !== undefined) { + if (setDataFpsResponse.values.operate) { + message.success('数据帧率设置成功'); + } else { + message.error('数据帧率设置失败'); + } + } + }, [setDataFpsResponse]); + + /** + * 处理异常监控配置读取响应 + */ + useEffect(() => { + if (getAlertResponse?.values) { + console.log('📥 收到异常监控配置:', getAlertResponse.values); + setSettings(prev => ({ + ...prev, + alertConfig: getAlertResponse.values + })); + setFetchStatus(prev => ({ ...prev, alert: 'success' })); + } + }, [getAlertResponse]); + + /** + * 处理异常监控设置响应 + */ + useEffect(() => { + if (setAlertResponse?.values?.operate !== undefined) { + if (setAlertResponse.values.operate) { + message.success('异常监控配置设置成功'); + } else { + message.error('异常监控配置设置失败'); + } + } + }, [setAlertResponse]); + + /** + * 处理滤波配置读取响应 + */ + useEffect(() => { + if (getWinResponse?.values) { + console.log('📥 收到滤波配置:', getWinResponse.values); + setSettings(prev => ({ + ...prev, + filterConfig: getWinResponse.values + })); + setFetchStatus(prev => ({ ...prev, filter: 'success' })); + } + }, [getWinResponse]); + + /** + * 处理滤波配置设置响应 + */ + useEffect(() => { + if (setWinResponse?.values?.operate !== undefined) { + if (setWinResponse.values.operate) { + message.success('滤波配置设置成功'); + } else { + message.error('滤波配置设置失败'); + } + } + }, [setWinResponse]); + + /** + * 处理 MQTT 配置读取响应 + */ + useEffect(() => { + if (getMqttResponse?.values) { + console.log('📥 收到 MQTT 配置:', getMqttResponse.values); + setSettings(prev => ({ + ...prev, + mqttConfig: getMqttResponse.values + })); + setFetchStatus(prev => ({ ...prev, mqtt: 'success' })); + } + }, [getMqttResponse]); + + /** + * 处理 MQTT 配置设置响应 + */ + useEffect(() => { + if (setMqttResponse?.values?.operate !== undefined) { + if (setMqttResponse.values.operate) { + message.success('MQTT 配置设置成功'); + } else { + message.error('MQTT 配置设置失败'); + } + } + }, [setMqttResponse]); + + /** + * 发送 WebSocket 命令 + */ + const sendCommand = useCallback((cmd, values = {}) => { + if (!isConnected) { + message.warning('WebSocket 未连接,请稍后重试'); + return false; + } + + const command = { + _from: 'setup', + cmd, + values + }; + + console.log('📤 发送命令:', command); + return sendMessage(JSON.stringify(command)); + }, [isConnected, sendMessage]); + + /** + * 获取设备编码 + */ + const fetchDeviceId = useCallback(() => { + setFetchStatus(prev => ({ ...prev, deviceId: 'loading' })); + return sendCommand('getId', {}); + }, [sendCommand]); + + /** + * 更新设备编码 + */ + const updateDeviceId = useCallback((id) => { + if (!id || !id.trim()) { + message.warning('请输入设备编码'); + return false; + } + return sendCommand('setId', { id }); + }, [sendCommand]); + + /** + * 获取数据帧率 + */ + const fetchDataFps = useCallback(() => { + setFetchStatus(prev => ({ ...prev, dataFps: 'loading' })); + return sendCommand('getDataFps', {}); + }, [sendCommand]); + + /** + * 更新数据帧率 + */ + const updateDataFps = useCallback((dataFps) => { + if (dataFps < 1 || dataFps > 30) { + message.warning('数据帧率范围为 1-30 Hz'); + return false; + } + return sendCommand('setDataFps', { dataFps }); + }, [sendCommand]); + + /** + * 获取异常监控配置 + */ + const fetchAlertConfig = useCallback(() => { + setFetchStatus(prev => ({ ...prev, alert: 'loading' })); + return sendCommand('getAlert', {}); + }, [sendCommand]); + + /** + * 更新异常监控配置 + */ + const updateAlertConfig = useCallback((alertConfig) => { + const { enable, intervalSec } = alertConfig; + return sendCommand('setAlert', { enable, intervalSec }); + }, [sendCommand]); + + /** + * 获取滤波配置 + */ + const fetchFilterConfig = useCallback(() => { + setFetchStatus(prev => ({ ...prev, filter: 'loading' })); + return sendCommand('getWin', {}); + }, [sendCommand]); + + /** + * 更新滤波配置 + */ + const updateFilterConfig = useCallback((filterConfig) => { + const { enable, method, size, threshold, imgThreshold } = filterConfig; + return sendCommand('setWin', { + enable, + method, + size, + threshold, + imgThreshold + }); + }, [sendCommand]); + + /** + * 获取 MQTT 配置 + */ + const fetchMqttConfig = useCallback(() => { + setFetchStatus(prev => ({ ...prev, mqtt: 'loading' })); + return sendCommand('getMqtt', {}); + }, [sendCommand]); + + /** + * 更新 MQTT 配置 + */ + const updateMqttConfig = useCallback((mqttConfig) => { + const { enable, mqtt } = mqttConfig; + + // 验证必填字段 + if (enable) { + if (!mqtt.broker || !mqtt.broker.trim()) { + message.warning('请输入 Broker 地址'); + return false; + } + if (!mqtt.port) { + message.warning('请输入端口号'); + return false; + } + if (!mqtt.topic || !mqtt.topic.trim()) { + message.warning('请输入 Topic'); + return false; + } + if (!mqtt.client_id || !mqtt.client_id.trim()) { + message.warning('请输入 Client ID'); + return false; + } + } + + return sendCommand('setMqtt', { enable, mqtt }); + }, [sendCommand]); + + /** + * 获取所有配置 + */ + const fetchAllSettings = useCallback(async () => { + if (!isConnected) { + message.warning('WebSocket 未连接,无法获取配置'); + return; + } + + if (!isReady) { + message.info('连接正在建立中,指令已加入队列...'); + } + + setLoading(true); + setFetchStatus({}); + + console.log('🔄 开始获取所有高级配置...'); + + // 依次发送所有读取命令 + fetchDeviceId(); + + // 添加延迟避免命令过快 + setTimeout(() => fetchDataFps(), 100); + setTimeout(() => fetchAlertConfig(), 200); + setTimeout(() => fetchFilterConfig(), 300); + setTimeout(() => fetchMqttConfig(), 400); + + // 等待所有响应 + setTimeout(() => { + setLoading(false); + console.log('✅ 配置获取完成'); + }, 2000); + }, [isConnected, isReady, fetchDeviceId, fetchDataFps, fetchAlertConfig, fetchFilterConfig, fetchMqttConfig]); + + /** + * 保存所有配置 + */ + const saveAllSettings = useCallback(() => { + if (!isConnected) { + message.warning('WebSocket 未连接,无法保存配置'); + return false; + } + + if (!isReady) { + message.info('连接正在建立中,指令已加入队列...'); + } else { + message.info('正在保存配置...'); + } + + // 依次发送所有设置命令 + updateDeviceId(settings.deviceId); + setTimeout(() => updateDataFps(settings.dataFps), 100); + setTimeout(() => updateAlertConfig(settings.alertConfig), 200); + setTimeout(() => updateFilterConfig(settings.filterConfig), 300); + setTimeout(() => updateMqttConfig(settings.mqttConfig), 400); + + return true; + }, [isConnected, isReady, settings, updateDeviceId, updateDataFps, updateAlertConfig, updateFilterConfig, updateMqttConfig]); + + /** + * 重置配置到默认值 + */ + const resetSettings = useCallback(() => { + setSettings(DEFAULT_SETTINGS); + message.info('配置已重置为默认值'); + }, []); + + /** + * 更新本地设置(不发送到设备) + */ + const updateLocalSettings = useCallback((updates) => { + setSettings(prev => ({ + ...prev, + ...updates + })); + }, []); + + /** + * WebSocket 连接建立后自动获取配置 + */ + useEffect(() => { + if (isConnected) { + console.log('🔌 WebSocket 已连接,自动获取高级配置'); + // 延迟获取,避免连接刚建立时发送命令 + setTimeout(() => { + fetchAllSettings(); + }, 500); + } + }, [isConnected, fetchAllSettings]); + + return { + // 状态 + settings, + loading, + fetchStatus, + isConnected, + isReady, + + // 操作方法 + fetchAllSettings, + saveAllSettings, + resetSettings, + updateLocalSettings, + + // 单项操作 + fetchDeviceId, + updateDeviceId, + fetchDataFps, + updateDataFps, + fetchAlertConfig, + updateAlertConfig, + fetchFilterConfig, + updateFilterConfig, + fetchMqttConfig, + updateMqttConfig, + }; +}; + +export default useAdvancedSettings; diff --git a/client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md b/client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md new file mode 100644 index 0000000..046156f --- /dev/null +++ b/client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md @@ -0,0 +1,246 @@ +# 高级配置 WebSocket 协议对接文档 + +1. 主机编码读取指令 + +```json +{ + "_from": "setup", + "cmd": "getId", + "values": {} +} +``` + +响应示例 + +```json +{ + "_from": "dev", + "cmd": "getId", + "values": { + "id": "uu1234" + } +} +``` + +2. 主机编码设置指令 + +```json +{ + "_from": "setup", + "cmd": "setId", + "values": { "id": "uu1234" } +} +``` + +响应示例 + +```json +{ + "_from": "dev", + "cmd": "setId", + "values": { "operate": true } +} +``` + +3. 帧率读取指令 + +```json +{ + "_from": "setup", + "cmd": "getDataFps", + "values": {} +} +``` +响应示例 + +```json +{ + "_from": "dev", + "cmd": "getDataFps", + "values": { "dataFps": 10 } +} +``` + +4. 帧率设置指令 + +```json +{ + "_from": "setup", + "cmd": "setDataFps", + "values": { "dataFps": 10 } +} +``` +响应示例 + +```json +{ + "_from": "dev", + "cmd": "setDataFps", + "values": { "operate": true } +} +``` + +5. 异常超时监控-读取指令 +```json +{ + "_from": "setup", + "cmd": "getAlert", + "values": {} +} +``` +响应示例 +```json +{ + "_from": "dev", + "cmd": "getAlert", + "values": { + "enable": false, + "intervalSec": 6 + } +} +``` + +6. 异常超时监控-设置指令 +```json +{ + "_from": "setup", + "cmd": "setAlert", + "values": { + "enable": false, + "intervalSec": 6 + } +} +``` +响应示例 +```json +{ + "_from": "dev", + "cmd": "setAlert", + "values": { + "operate": true + } +} +``` + +7. 滤波-读取 +```json +{ + "_from": "setup", + "cmd":"getWin", + "values": {} +} +``` +响应示例 +```json +{ + "_from": "dev", + "cmd": "getWin", + "values": { + "enable": true, + "method": "median", + "size": 5, + "threshold": -0.1, + "imgThreshold": 10.0 + } +} +``` + +8. 滤波-设置 +```json +{ + "_from": "setup", + "cmd":"setWin", + "values": { + "enable": true, + "method": "median", + "size": 5, + "threshold": -0.1, + "imgThreshold": 10.0 + } +} +``` +响应示例 +```json +{ + "_from": "dev", + "cmd": "setWin", + "values": { + "operate": true + } +} +``` + +9. mqtt 上报读取 +```json +{ + "_from": "setup", + "cmd":"getMqtt", + "values": {} +} +``` +响应示例 +```json +{ + "_from": "dev", + "cmd": "getMqtt", + "values": { + "mqtt": { + "broker": "218.3.126.49", + "port": 1883, + "topic": "wybb/zj/mqtt110_debug", + "username": "", + "password": "", + "client_id": "wybb_debug" + }, + "enable": true + } +} +``` + +10. mqtt上报设置 +```json +{ + "_from": "setup", + "cmd": "setMqtt", + "values": { + "mqtt": { + "broker": "218.3.126.49", + "port": 1883, + "topic": "wybb/zj/mqtt110_debug", + "username": "", + "password": "", + "client_id": "wybb_debug" + }, + "enable": true + } +} +``` +响应示例 +```json +{ + "_from": "dev", + "cmd": "setMqtt", + "values": { + "operate": true + } +} +``` +10. 异常超时监控-读取 +```json +{ + "_from": "setup", + "cmd":"getAlert", + "values": {} +} +``` +响应示例 +```json +{ + "_from": "dev", + "cmd": "getAlert", + "values": { + "enable": false, + "intervalSec": 6 + } +} +``` diff --git a/package.json b/package.json index f8bfa1d..b49bf99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wuyuanbiaoba-web", - "version": "1.0.4", + "version": "1.1.0", "main": "index.html", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", diff --git a/server/tcpProxy/index.js b/server/tcpProxy/index.js index 64dcbb9..3c34b97 100644 --- a/server/tcpProxy/index.js +++ b/server/tcpProxy/index.js @@ -5,7 +5,7 @@ let TCP_HOST = '127.0.0.1'; // 因为下位机和上位机在同一台机器上 const TCP_PORT = 2230; // 创建独立的WebSocket服务器用于TCP代理 function setupTcpProxy(conf) { - if(conf && conf.flag === 'localdev') { + if (conf && conf.flag === 'localdev') { TCP_HOST = '10.8.30.179' //本地开发配置 } console.log(`TCP代理目标地址: ${TCP_HOST}:${TCP_PORT}`); @@ -21,17 +21,32 @@ function setupTcpProxy(conf) { }); wss.on('connection', (ws, request) => { - // console.log(`WebSocket连接建立,来自: ${request.socket.remoteAddress}`); + // console.log(`WebSocket连接建立,来自: ${request.socket.remoteAddress}`); // 创建TCP连接 const tcpClient = new net.Socket(); // TCP数据缓冲区 let tcpDataBuffer = ''; + let tcpConnected = false; // 新增:TCP连接状态标志 // TCP连接成功 tcpClient.connect(process.env.TCP_PORT || TCP_PORT, process.env.TCP_HOST || TCP_HOST, () => { // console.log(process.env); // console.log(`TCP连接已建立到 ${TCP_HOST}:${TCP_PORT}`); + tcpConnected = true; + + // 向WebSocket客户端发送就绪信号 + if (ws.readyState === WebSocket.OPEN) { + const readySignal = JSON.stringify({ + _from: 'proxy', + cmd: 'ready', + values: { + message: 'TCP connection established', + timestamp: Date.now() + } + }); + ws.send(readySignal); + } }); // TCP接收数据,转发到WebSocket @@ -40,9 +55,8 @@ function setupTcpProxy(conf) { // 尝试解析为文本 try { textData = data.toString('utf8'); - // console.log('收到TCP数据片段:', textData.length, '字节'); } catch (e) { - // console.log('TCP数据无法解析为文本'); + console.log('TCP数据无法解析为文本'); return; } @@ -94,7 +108,7 @@ function setupTcpProxy(conf) { // 转发字符串数据到TCP服务器 if (tcpClient.writable) { - console.log('准备发送数据到TCP服务器:', messageStr); + console.log('发送数据到TCP服务器:', messageStr); // 检查数据大小 const dataSize = Buffer.byteLength(messageStr, 'utf8'); @@ -150,14 +164,14 @@ function setupTcpProxy(conf) { }); wss.on('listening', () => { - // console.log(`TCP代理WebSocket服务器已启动在端口 ${wsPort},路径: /tcp-proxy`); - // console.log(`局域网连接地址: ws://[本机IP]:${wsPort}/tcp-proxy`); - // console.log(`本地连接地址: ws://localhost:${wsPort}/tcp-proxy`); - // console.log(`注意:请确保防火墙允许端口 ${wsPort} 的访问`); + // console.log(`TCP代理WebSocket服务器已启动在端口 ${wsPort},路径: /tcp-proxy`); + // console.log(`局域网连接地址: ws://[本机IP]:${wsPort}/tcp-proxy`); + // console.log(`本地连接地址: ws://localhost:${wsPort}/tcp-proxy`); + // console.log(`注意:请确保防火墙允许端口 ${wsPort} 的访问`); }); wss.on('error', (err) => { - console.error(`[${new Date().toLocaleString()}] WebSocket服务器错误:`, err); + console.error(`[${new Date().toLocaleString()}] WebSocket服务器错误:`, err); }); return wss;